Switched to spaces

This commit is contained in:
rr- 2015-06-28 10:07:11 +02:00
parent 79df9b56d3
commit 2702518e31
265 changed files with 14902 additions and 14902 deletions

View file

@ -2,130 +2,130 @@ var App = App || {};
App.API = function(_, jQuery, promise, appState) { App.API = function(_, jQuery, promise, appState) {
var baseUrl = '/api/'; var baseUrl = '/api/';
var AJAX_UNSENT = 0; var AJAX_UNSENT = 0;
var AJAX_OPENED = 1; var AJAX_OPENED = 1;
var AJAX_HEADERS_RECEIVED = 2; var AJAX_HEADERS_RECEIVED = 2;
var AJAX_LOADING = 3; var AJAX_LOADING = 3;
var AJAX_DONE = 4; var AJAX_DONE = 4;
var cache = {}; var cache = {};
function get(url, data) { function get(url, data) {
return request('GET', url, data); return request('GET', url, data);
} }
function post(url, data) { function post(url, data) {
return request('POST', url, data); return request('POST', url, data);
} }
function put(url, data) { function put(url, data) {
return request('PUT', url, data); return request('PUT', url, data);
} }
function _delete(url, data) { function _delete(url, data) {
return request('DELETE', url, data); return request('DELETE', url, data);
} }
function getCacheKey(method, url, data) { function getCacheKey(method, url, data) {
return JSON.stringify({method: method, url: url, data: data}); return JSON.stringify({method: method, url: url, data: data});
} }
function clearCache() { function clearCache() {
cache = {}; cache = {};
} }
function request(method, url, data) { function request(method, url, data) {
if (method === 'GET') { if (method === 'GET') {
return requestWithCache(method, url, data); return requestWithCache(method, url, data);
} }
clearCache(); clearCache();
return requestWithAjax(method, url, data); return requestWithAjax(method, url, data);
} }
function requestWithCache(method, url, data) { function requestWithCache(method, url, data) {
var cacheKey = getCacheKey(method, url, data); var cacheKey = getCacheKey(method, url, data);
if (_.has(cache, cacheKey)) { if (_.has(cache, cacheKey)) {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
resolve(cache[cacheKey]); resolve(cache[cacheKey]);
}); });
} }
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
promise.wait(requestWithAjax(method, url, data)) promise.wait(requestWithAjax(method, url, data))
.then(function(response) { .then(function(response) {
setCache(method, url, data, response); setCache(method, url, data, response);
resolve(response); resolve(response);
}).fail(function(response) { }).fail(function(response) {
reject(response); reject(response);
}); });
}); });
} }
function setCache(method, url, data, response) { function setCache(method, url, data, response) {
var cacheKey = getCacheKey(method, url, data); var cacheKey = getCacheKey(method, url, data);
cache[cacheKey] = response; cache[cacheKey] = response;
} }
function requestWithAjax(method, url, data) { function requestWithAjax(method, url, data) {
var fullUrl = baseUrl + '/' + url; var fullUrl = baseUrl + '/' + url;
fullUrl = fullUrl.replace(/\/{2,}/, '/'); fullUrl = fullUrl.replace(/\/{2,}/, '/');
var xhr = null; var xhr = null;
var apiPromise = promise.make(function(resolve, reject) { var apiPromise = promise.make(function(resolve, reject) {
var options = { var options = {
headers: { headers: {
'X-Authorization-Token': appState.get('loginToken') || '', 'X-Authorization-Token': appState.get('loginToken') || '',
}, },
success: function(data, textStatus, xhr) { success: function(data, textStatus, xhr) {
resolve({ resolve({
status: xhr.status, status: xhr.status,
json: stripMeta(data)}); json: stripMeta(data)});
}, },
error: function(xhr, textStatus, errorThrown) { error: function(xhr, textStatus, errorThrown) {
reject({ reject({
status: xhr.status, status: xhr.status,
json: xhr.responseJSON ? json: xhr.responseJSON ?
stripMeta(xhr.responseJSON) : stripMeta(xhr.responseJSON) :
{error: errorThrown}}); {error: errorThrown}});
}, },
type: method, type: method,
url: fullUrl, url: fullUrl,
data: data, data: data,
cache: false, cache: false,
}; };
if (data instanceof FormData) { if (data instanceof FormData) {
options.processData = false; options.processData = false;
options.contentType = false; options.contentType = false;
} }
xhr = jQuery.ajax(options); xhr = jQuery.ajax(options);
}); });
apiPromise.xhr = xhr; apiPromise.xhr = xhr;
return apiPromise; return apiPromise;
} }
function stripMeta(data) { function stripMeta(data) {
var result = {}; var result = {};
_.each(data, function(v, k) { _.each(data, function(v, k) {
if (!k.match(/^__/)) { if (!k.match(/^__/)) {
result[k] = v; result[k] = v;
} }
}); });
return result; return result;
} }
return { return {
get: get, get: get,
post: post, post: post,
put: put, put: put,
delete: _delete, delete: _delete,
AJAX_UNSENT: AJAX_UNSENT, AJAX_UNSENT: AJAX_UNSENT,
AJAX_OPENED: AJAX_OPENED, AJAX_OPENED: AJAX_OPENED,
AJAX_HEADERS_RECEIVED: AJAX_HEADERS_RECEIVED, AJAX_HEADERS_RECEIVED: AJAX_HEADERS_RECEIVED,
AJAX_LOADING: AJAX_LOADING, AJAX_LOADING: AJAX_LOADING,
AJAX_DONE: AJAX_DONE, AJAX_DONE: AJAX_DONE,
}; };
}; };

View file

@ -2,198 +2,198 @@ var App = App || {};
App.Auth = function(_, jQuery, util, api, appState, promise) { App.Auth = function(_, jQuery, util, api, appState, promise) {
var privileges = { var privileges = {
register: 'register', register: 'register',
listUsers: 'listUsers', listUsers: 'listUsers',
viewUsers: 'viewUsers', viewUsers: 'viewUsers',
viewAllAccessRanks: 'viewAllAccessRanks', viewAllAccessRanks: 'viewAllAccessRanks',
viewAllEmailAddresses: 'viewAllEmailAddresses', viewAllEmailAddresses: 'viewAllEmailAddresses',
changeAccessRank: 'changeAccessRank', changeAccessRank: 'changeAccessRank',
changeOwnAvatarStyle: 'changeOwnAvatarStyle', changeOwnAvatarStyle: 'changeOwnAvatarStyle',
changeOwnEmailAddress: 'changeOwnEmailAddress', changeOwnEmailAddress: 'changeOwnEmailAddress',
changeOwnName: 'changeOwnName', changeOwnName: 'changeOwnName',
changeOwnPassword: 'changeOwnPassword', changeOwnPassword: 'changeOwnPassword',
changeAllAvatarStyles: 'changeAllAvatarStyles', changeAllAvatarStyles: 'changeAllAvatarStyles',
changeAllEmailAddresses: 'changeAllEmailAddresses', changeAllEmailAddresses: 'changeAllEmailAddresses',
changeAllNames: 'changeAllNames', changeAllNames: 'changeAllNames',
changeAllPasswords: 'changeAllPasswords', changeAllPasswords: 'changeAllPasswords',
deleteOwnAccount: 'deleteOwnAccount', deleteOwnAccount: 'deleteOwnAccount',
deleteAllAccounts: 'deleteAllAccounts', deleteAllAccounts: 'deleteAllAccounts',
banUsers: 'banUsers', banUsers: 'banUsers',
listPosts: 'listPosts', listPosts: 'listPosts',
viewPosts: 'viewPosts', viewPosts: 'viewPosts',
uploadPosts: 'uploadPosts', uploadPosts: 'uploadPosts',
uploadPostsAnonymously: 'uploadPostsAnonymously', uploadPostsAnonymously: 'uploadPostsAnonymously',
deletePosts: 'deletePosts', deletePosts: 'deletePosts',
featurePosts: 'featurePosts', featurePosts: 'featurePosts',
changePostSafety: 'changePostSafety', changePostSafety: 'changePostSafety',
changePostSource: 'changePostSource', changePostSource: 'changePostSource',
changePostTags: 'changePostTags', changePostTags: 'changePostTags',
changePostContent: 'changePostContent', changePostContent: 'changePostContent',
changePostThumbnail: 'changePostThumbnail', changePostThumbnail: 'changePostThumbnail',
changePostRelations: 'changePostRelations', changePostRelations: 'changePostRelations',
changePostFlags: 'changePostFlags', changePostFlags: 'changePostFlags',
addPostNotes: 'addPostNotes', addPostNotes: 'addPostNotes',
editPostNotes: 'editPostNotes', editPostNotes: 'editPostNotes',
deletePostNotes: 'deletePostNotes', deletePostNotes: 'deletePostNotes',
listComments: 'listComments', listComments: 'listComments',
addComments: 'addComments', addComments: 'addComments',
editOwnComments: 'editOwnComments', editOwnComments: 'editOwnComments',
editAllComments: 'editAllComments', editAllComments: 'editAllComments',
deleteOwnComments: 'deleteOwnComments', deleteOwnComments: 'deleteOwnComments',
deleteAllComments: 'deleteAllComments', deleteAllComments: 'deleteAllComments',
deleteTags: 'deleteTags', deleteTags: 'deleteTags',
mergeTags: 'mergeTags', mergeTags: 'mergeTags',
listTags: 'listTags', listTags: 'listTags',
massTag: 'massTag', massTag: 'massTag',
changeTagName: 'changeTagName', changeTagName: 'changeTagName',
changeTagCategory: 'changeTagCategory', changeTagCategory: 'changeTagCategory',
changeTagImplications: 'changeTagImplications', changeTagImplications: 'changeTagImplications',
changeTagSuggestions: 'changeTagSuggestions', changeTagSuggestions: 'changeTagSuggestions',
banTags: 'banTags', banTags: 'banTags',
viewHistory: 'viewHistory', viewHistory: 'viewHistory',
}; };
function loginFromCredentials(userNameOrEmail, password, remember) { function loginFromCredentials(userNameOrEmail, password, remember) {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
promise.wait(api.post('/login', {userNameOrEmail: userNameOrEmail, password: password})) promise.wait(api.post('/login', {userNameOrEmail: userNameOrEmail, password: password}))
.then(function(response) { .then(function(response) {
updateAppState(response); updateAppState(response);
jQuery.cookie( jQuery.cookie(
'auth', 'auth',
response.json.token.name, response.json.token.name,
remember ? { expires: 365 } : {}); remember ? { expires: 365 } : {});
resolve(response); resolve(response);
}).fail(function(response) { }).fail(function(response) {
reject(response); reject(response);
}); });
}); });
} }
function loginFromToken(token, isFromCookie) { function loginFromToken(token, isFromCookie) {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
var fd = { var fd = {
token: token, token: token,
isFromCookie: isFromCookie isFromCookie: isFromCookie
}; };
promise.wait(api.post('/login', fd)) promise.wait(api.post('/login', fd))
.then(function(response) { .then(function(response) {
updateAppState(response); updateAppState(response);
resolve(response); resolve(response);
}).fail(function(response) { }).fail(function(response) {
reject(response); reject(response);
}); });
}); });
} }
function loginAnonymous() { function loginAnonymous() {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
promise.wait(api.post('/login')) promise.wait(api.post('/login'))
.then(function(response) { .then(function(response) {
updateAppState(response); updateAppState(response);
resolve(response); resolve(response);
}).fail(function(response) { }).fail(function(response) {
reject(response); reject(response);
}); });
}); });
} }
function logout() { function logout() {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
jQuery.removeCookie('auth'); jQuery.removeCookie('auth');
appState.set('loginToken', null); appState.set('loginToken', null);
return promise.wait(loginAnonymous()) return promise.wait(loginAnonymous())
.then(resolve) .then(resolve)
.fail(reject); .fail(reject);
}); });
} }
function tryLoginFromCookie() { function tryLoginFromCookie() {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
if (isLoggedIn()) { if (isLoggedIn()) {
resolve(); resolve();
return; return;
} }
var authCookie = jQuery.cookie('auth'); var authCookie = jQuery.cookie('auth');
if (!authCookie) { if (!authCookie) {
reject(); reject();
return; return;
} }
promise.wait(loginFromToken(authCookie, true)) promise.wait(loginFromToken(authCookie, true))
.then(function(response) { .then(function(response) {
resolve(); resolve();
}).fail(function(response) { }).fail(function(response) {
jQuery.removeCookie('auth'); jQuery.removeCookie('auth');
reject(); reject();
}); });
}); });
} }
function updateAppState(response) { function updateAppState(response) {
appState.set('privileges', response.json.privileges || []); appState.set('privileges', response.json.privileges || []);
appState.set('loginToken', response.json.token && response.json.token.name); appState.set('loginToken', response.json.token && response.json.token.name);
appState.set('loggedIn', response.json.user && !!response.json.user.id); appState.set('loggedIn', response.json.user && !!response.json.user.id);
appState.set('loggedInUser', response.json.user); appState.set('loggedInUser', response.json.user);
} }
function isLoggedIn(userName) { function isLoggedIn(userName) {
if (!appState.get('loggedIn')) { if (!appState.get('loggedIn')) {
return false; return false;
} }
if (typeof(userName) !== 'undefined') { if (typeof(userName) !== 'undefined') {
if (getCurrentUser().name !== userName) { if (getCurrentUser().name !== userName) {
return false; return false;
} }
} }
return true; return true;
} }
function getCurrentUser() { function getCurrentUser() {
return appState.get('loggedInUser'); return appState.get('loggedInUser');
} }
function getCurrentPrivileges() { function getCurrentPrivileges() {
return appState.get('privileges'); return appState.get('privileges');
} }
function updateCurrentUser(user) { function updateCurrentUser(user) {
if (user.id !== getCurrentUser().id) { if (user.id !== getCurrentUser().id) {
throw new Error('Cannot set current user to other user this way.'); throw new Error('Cannot set current user to other user this way.');
} }
appState.set('loggedInUser', user); appState.set('loggedInUser', user);
} }
function hasPrivilege(privilege) { function hasPrivilege(privilege) {
return _.contains(getCurrentPrivileges(), privilege); return _.contains(getCurrentPrivileges(), privilege);
} }
function startObservingLoginChanges(listenerName, callback) { function startObservingLoginChanges(listenerName, callback) {
appState.startObserving('loggedInUser', listenerName, callback); appState.startObserving('loggedInUser', listenerName, callback);
} }
return { return {
loginFromCredentials: loginFromCredentials, loginFromCredentials: loginFromCredentials,
loginFromToken: loginFromToken, loginFromToken: loginFromToken,
loginAnonymous: loginAnonymous, loginAnonymous: loginAnonymous,
tryLoginFromCookie: tryLoginFromCookie, tryLoginFromCookie: tryLoginFromCookie,
logout: logout, logout: logout,
startObservingLoginChanges: startObservingLoginChanges, startObservingLoginChanges: startObservingLoginChanges,
isLoggedIn: isLoggedIn, isLoggedIn: isLoggedIn,
getCurrentUser: getCurrentUser, getCurrentUser: getCurrentUser,
updateCurrentUser: updateCurrentUser, updateCurrentUser: updateCurrentUser,
getCurrentPrivileges: getCurrentPrivileges, getCurrentPrivileges: getCurrentPrivileges,
hasPrivilege: hasPrivilege, hasPrivilege: hasPrivilege,
privileges: privileges, privileges: privileges,
}; };
}; };

View file

@ -2,29 +2,29 @@ var App = App || {};
App.Bootstrap = function(auth, router, promise, presenterManager) { App.Bootstrap = function(auth, router, promise, presenterManager) {
promise.wait(presenterManager.init()) promise.wait(presenterManager.init())
.then(function() { .then(function() {
promise.wait(auth.tryLoginFromCookie()) promise.wait(auth.tryLoginFromCookie())
.then(startRouting) .then(startRouting)
.fail(function(error) { .fail(function(error) {
promise.wait(auth.loginAnonymous()) promise.wait(auth.loginAnonymous())
.then(startRouting) .then(startRouting)
.fail(function() { .fail(function() {
console.log(arguments); console.log(arguments);
window.alert('Fatal authentication error'); window.alert('Fatal authentication error');
}); });
}); });
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
}); });
function startRouting() { function startRouting() {
try { try {
router.start(); router.start();
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }
} }
}; };

View file

@ -1,96 +1,96 @@
var App = App || {}; var App = App || {};
App.BrowsingSettings = function( App.BrowsingSettings = function(
promise, promise,
auth, auth,
api) { api) {
var settings = getDefaultSettings(); var settings = getDefaultSettings();
auth.startObservingLoginChanges('browsing-settings', loginStateChanged); auth.startObservingLoginChanges('browsing-settings', loginStateChanged);
readFromLocalStorage(); readFromLocalStorage();
if (auth.isLoggedIn()) { if (auth.isLoggedIn()) {
loginStateChanged(); loginStateChanged();
} }
function setSettings(newSettings) { function setSettings(newSettings) {
settings = newSettings; settings = newSettings;
return save(); return save();
} }
function getSettings() { function getSettings() {
return settings; return settings;
} }
function getDefaultSettings() { function getDefaultSettings() {
return { return {
hideDownvoted: true, hideDownvoted: true,
endlessScroll: false, endlessScroll: false,
listPosts: { listPosts: {
safe: true, safe: true,
sketchy: true, sketchy: true,
unsafe: true, unsafe: true,
}, },
keyboardShortcuts: true, keyboardShortcuts: true,
}; };
} }
function loginStateChanged() { function loginStateChanged() {
readFromUser(auth.getCurrentUser()); readFromUser(auth.getCurrentUser());
} }
function readFromLocalStorage() { function readFromLocalStorage() {
readFromString(localStorage.getItem('browsingSettings')); readFromString(localStorage.getItem('browsingSettings'));
} }
function readFromUser(user) { function readFromUser(user) {
readFromString(user.browsingSettings); readFromString(user.browsingSettings);
} }
function readFromString(string) { function readFromString(string) {
if (!string) { if (!string) {
return; return;
} }
try { try {
if (typeof(string) === 'string' || string instanceof String) { if (typeof(string) === 'string' || string instanceof String) {
settings = JSON.parse(string); settings = JSON.parse(string);
} else { } else {
settings = string; settings = string;
} }
} catch (e) { } catch (e) {
} }
} }
function saveToLocalStorage() { function saveToLocalStorage() {
localStorage.setItem('browsingSettings', JSON.stringify(settings)); localStorage.setItem('browsingSettings', JSON.stringify(settings));
} }
function saveToUser(user) { function saveToUser(user) {
var formData = { var formData = {
browsingSettings: JSON.stringify(settings), browsingSettings: JSON.stringify(settings),
}; };
return api.post('/users/' + user.name, formData); return api.post('/users/' + user.name, formData);
} }
function save() { function save() {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
saveToLocalStorage(); saveToLocalStorage();
if (auth.isLoggedIn()) { if (auth.isLoggedIn()) {
promise.wait(saveToUser(auth.getCurrentUser())) promise.wait(saveToUser(auth.getCurrentUser()))
.then(resolve) .then(resolve)
.fail(reject); .fail(reject);
} else { } else {
resolve(); resolve();
} }
}); });
} }
return { return {
getSettings: getSettings, getSettings: getSettings,
setSettings: setSettings, setSettings: setSettings,
}; };
}; };

View file

@ -2,273 +2,273 @@ var App = App || {};
App.Controls = App.Controls || {}; App.Controls = App.Controls || {};
App.Controls.AutoCompleteInput = function($input) { App.Controls.AutoCompleteInput = function($input) {
var _ = App.DI.get('_'); var _ = App.DI.get('_');
var jQuery = App.DI.get('jQuery'); var jQuery = App.DI.get('jQuery');
var tagList = App.DI.get('tagList'); var tagList = App.DI.get('tagList');
var KEY_TAB = 9; var KEY_TAB = 9;
var KEY_RETURN = 13; var KEY_RETURN = 13;
var KEY_DELETE = 46; var KEY_DELETE = 46;
var KEY_ESCAPE = 27; var KEY_ESCAPE = 27;
var KEY_UP = 38; var KEY_UP = 38;
var KEY_DOWN = 40; var KEY_DOWN = 40;
var options = { var options = {
caseSensitive: false, caseSensitive: false,
source: null, source: null,
maxResults: 15, maxResults: 15,
minLengthToArbitrarySearch: 3, minLengthToArbitrarySearch: 3,
onApply: null, onApply: null,
onDelete: null, onDelete: null,
onRender: null, onRender: null,
additionalFilter: null, additionalFilter: null,
}; };
var showTimeout = null; var showTimeout = null;
var cachedSource = null; var cachedSource = null;
var results = []; var results = [];
var activeResult = -1; var activeResult = -1;
var monitorInputHidingInterval = null; var monitorInputHidingInterval = null;
if ($input.length === 0) { if ($input.length === 0) {
throw new Error('Input element was not found'); throw new Error('Input element was not found');
} }
if ($input.length > 1) { if ($input.length > 1) {
throw new Error('Cannot add autocompletion to more than one element at once'); throw new Error('Cannot add autocompletion to more than one element at once');
} }
if ($input.attr('data-autocomplete')) { if ($input.attr('data-autocomplete')) {
throw new Error('Autocompletion was already added for this element'); throw new Error('Autocompletion was already added for this element');
} }
$input.attr('data-autocomplete', true); $input.attr('data-autocomplete', true);
$input.attr('autocomplete', 'off'); $input.attr('autocomplete', 'off');
var $div = jQuery('<div>'); var $div = jQuery('<div>');
var $list = jQuery('<ul>'); var $list = jQuery('<ul>');
$div.addClass('autocomplete'); $div.addClass('autocomplete');
$div.append($list); $div.append($list);
jQuery(document.body).append($div); jQuery(document.body).append($div);
function getSource() { function getSource() {
if (cachedSource) { if (cachedSource) {
return cachedSource; return cachedSource;
} else { } else {
var source = tagList.getTags(); var source = tagList.getTags();
source = _.sortBy(source, function(a) { return -a.usages; }); source = _.sortBy(source, function(a) { return -a.usages; });
source = _.filter(source, function(a) { return a.usages >= 0; }); source = _.filter(source, function(a) { return a.usages >= 0; });
source = _.map(source, function(a) { source = _.map(source, function(a) {
return { return {
tag: a.name, tag: a.name,
caption: a.name + ' (' + a.usages + ')', caption: a.name + ' (' + a.usages + ')',
}; };
}); });
cachedSource = source; cachedSource = source;
return source; return source;
} }
} }
$input.bind('keydown', function(e) { $input.bind('keydown', function(e) {
var func = null; var func = null;
if (isShown() && e.which === KEY_ESCAPE) { if (isShown() && e.which === KEY_ESCAPE) {
func = hide; func = hide;
} else if (isShown() && e.which === KEY_TAB) { } else if (isShown() && e.which === KEY_TAB) {
if (e.shiftKey) { if (e.shiftKey) {
func = selectPrevious; func = selectPrevious;
} else { } else {
func = selectNext; func = selectNext;
} }
} else if (isShown() && e.which === KEY_DOWN) { } else if (isShown() && e.which === KEY_DOWN) {
func = selectNext; func = selectNext;
} else if (isShown() && e.which === KEY_UP) { } else if (isShown() && e.which === KEY_UP) {
func = selectPrevious; func = selectPrevious;
} else if (isShown() && e.which === KEY_RETURN && activeResult >= 0) { } else if (isShown() && e.which === KEY_RETURN && activeResult >= 0) {
func = function() { applyAutocomplete(); hide(); }; func = function() { applyAutocomplete(); hide(); };
} else if (isShown() && e.which === KEY_DELETE && activeResult >= 0) { } else if (isShown() && e.which === KEY_DELETE && activeResult >= 0) {
func = function() { applyDelete(); hide(); }; func = function() { applyDelete(); hide(); };
} }
if (func !== null) { if (func !== null) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
func(); func();
} else { } else {
window.clearTimeout(showTimeout); window.clearTimeout(showTimeout);
showTimeout = window.setTimeout(showOrHide, 250); showTimeout = window.setTimeout(showOrHide, 250);
} }
}); });
$input.blur(function(e) { $input.blur(function(e) {
window.clearTimeout(showTimeout); window.clearTimeout(showTimeout);
window.setTimeout(function() { hide(); }, 50); window.setTimeout(function() { hide(); }, 50);
}); });
function getSelectionStart(){ function getSelectionStart(){
var input = $input.get(0); var input = $input.get(0);
if (!input) { if (!input) {
return; return;
} }
if ('selectionStart' in input) { if ('selectionStart' in input) {
return input.selectionStart; return input.selectionStart;
} else if (document.selection) { } else if (document.selection) {
input.focus(); input.focus();
var sel = document.selection.createRange(); var sel = document.selection.createRange();
var selLen = document.selection.createRange().text.length; var selLen = document.selection.createRange().text.length;
sel.moveStart('character', -input.value.length); sel.moveStart('character', -input.value.length);
return sel.text.length - selLen; return sel.text.length - selLen;
} else { } else {
return 0; return 0;
} }
} }
function getTextToFind() { function getTextToFind() {
var val = $input.val(); var val = $input.val();
var start = getSelectionStart(); var start = getSelectionStart();
return val.substring(0, start).replace(/.*\s/, ''); return val.substring(0, start).replace(/.*\s/, '');
} }
function showOrHide() { function showOrHide() {
var textToFind = getTextToFind(); var textToFind = getTextToFind();
if (textToFind.length === 0) { if (textToFind.length === 0) {
hide(); hide();
} else { } else {
updateResults(textToFind); updateResults(textToFind);
refreshList(); refreshList();
} }
} }
function isShown() { function isShown() {
return $div.is(':visible'); return $div.is(':visible');
} }
function hide() { function hide() {
$div.hide(); $div.hide();
window.clearInterval(monitorInputHidingInterval); window.clearInterval(monitorInputHidingInterval);
} }
function selectPrevious() { function selectPrevious() {
select(activeResult === -1 ? results.length - 1 : activeResult - 1); select(activeResult === -1 ? results.length - 1 : activeResult - 1);
} }
function selectNext() { function selectNext() {
select(activeResult === -1 ? 0 : activeResult + 1); select(activeResult === -1 ? 0 : activeResult + 1);
} }
function select(newActiveResult) { function select(newActiveResult) {
if (newActiveResult >= 0 && newActiveResult < results.length) { if (newActiveResult >= 0 && newActiveResult < results.length) {
activeResult = newActiveResult; activeResult = newActiveResult;
refreshActiveResult(); refreshActiveResult();
} else { } else {
activeResult = - 1; activeResult = - 1;
refreshActiveResult(); refreshActiveResult();
} }
} }
function getResultsFilter(textToFind) { function getResultsFilter(textToFind) {
if (textToFind.length < options.minLengthToArbitrarySearch) { if (textToFind.length < options.minLengthToArbitrarySearch) {
return options.caseSensitive ? return options.caseSensitive ?
function(resultItem) { return resultItem.tag.indexOf(textToFind) === 0; } : function(resultItem) { return resultItem.tag.indexOf(textToFind) === 0; } :
function(resultItem) { return resultItem.tag.toLowerCase().indexOf(textToFind.toLowerCase()) === 0; }; function(resultItem) { return resultItem.tag.toLowerCase().indexOf(textToFind.toLowerCase()) === 0; };
} else { } else {
return options.caseSensitive ? return options.caseSensitive ?
function(resultItem) { return resultItem.tag.indexOf(textToFind) >= 0; } : function(resultItem) { return resultItem.tag.indexOf(textToFind) >= 0; } :
function(resultItem) { return resultItem.tag.toLowerCase().indexOf(textToFind.toLowerCase()) >= 0; }; function(resultItem) { return resultItem.tag.toLowerCase().indexOf(textToFind.toLowerCase()) >= 0; };
} }
} }
function updateResults(textToFind) { function updateResults(textToFind) {
var oldResults = results.slice(); var oldResults = results.slice();
var source = getSource(); var source = getSource();
var filter = getResultsFilter(textToFind); var filter = getResultsFilter(textToFind);
results = _.filter(source, filter); results = _.filter(source, filter);
if (options.additionalFilter) { if (options.additionalFilter) {
results = options.additionalFilter(results); results = options.additionalFilter(results);
} }
results = results.slice(0, options.maxResults); results = results.slice(0, options.maxResults);
if (!_.isEqual(oldResults, results)) { if (!_.isEqual(oldResults, results)) {
activeResult = -1; activeResult = -1;
} }
} }
function applyDelete() { function applyDelete() {
if (options.onDelete) { if (options.onDelete) {
options.onDelete(results[activeResult].tag); options.onDelete(results[activeResult].tag);
} }
} }
function applyAutocomplete() { function applyAutocomplete() {
if (options.onApply) { if (options.onApply) {
options.onApply(results[activeResult].tag); options.onApply(results[activeResult].tag);
} else { } else {
var val = $input.val(); var val = $input.val();
var start = getSelectionStart(); var start = getSelectionStart();
var prefix = ''; var prefix = '';
var suffix = val.substring(start); var suffix = val.substring(start);
var middle = val.substring(0, start); var middle = val.substring(0, start);
var index = middle.lastIndexOf(' '); var index = middle.lastIndexOf(' ');
if (index !== -1) { if (index !== -1) {
prefix = val.substring(0, index + 1); prefix = val.substring(0, index + 1);
middle = val.substring(index + 1); middle = val.substring(index + 1);
} }
$input.val(prefix + results[activeResult].tag + ' ' + suffix.trimLeft()); $input.val(prefix + results[activeResult].tag + ' ' + suffix.trimLeft());
$input.focus(); $input.focus();
} }
} }
function refreshList() { function refreshList() {
if (results.length === 0) { if (results.length === 0) {
hide(); hide();
return; return;
} }
$list.empty(); $list.empty();
_.each(results, function(resultItem, resultIndex) { _.each(results, function(resultItem, resultIndex) {
var $listItem = jQuery('<li/>'); var $listItem = jQuery('<li/>');
$listItem.text(resultItem.caption); $listItem.text(resultItem.caption);
$listItem.attr('data-key', resultItem.tag); $listItem.attr('data-key', resultItem.tag);
$listItem.hover(function(e) { $listItem.hover(function(e) {
e.preventDefault(); e.preventDefault();
activeResult = resultIndex; activeResult = resultIndex;
refreshActiveResult(); refreshActiveResult();
}); });
$listItem.mousedown(function(e) { $listItem.mousedown(function(e) {
e.preventDefault(); e.preventDefault();
activeResult = resultIndex; activeResult = resultIndex;
applyAutocomplete(); applyAutocomplete();
hide(); hide();
}); });
$list.append($listItem); $list.append($listItem);
}); });
if (options.onRender) { if (options.onRender) {
options.onRender($list); options.onRender($list);
} }
refreshActiveResult(); refreshActiveResult();
var x = $input.offset().left; var x = $input.offset().left;
var y = $input.offset().top + $input.outerHeight() - 2; var y = $input.offset().top + $input.outerHeight() - 2;
if (y + $div.height() > window.innerHeight) { if (y + $div.height() > window.innerHeight) {
y = $input.offset().top - $div.height(); y = $input.offset().top - $div.height();
} }
$div.css({ $div.css({
left: x + 'px', left: x + 'px',
top: y + 'px', top: y + 'px',
}); });
$div.show(); $div.show();
monitorInputHiding(); monitorInputHiding();
} }
function refreshActiveResult() { function refreshActiveResult() {
$list.find('li.active').removeClass('active'); $list.find('li.active').removeClass('active');
if (activeResult >= 0) { if (activeResult >= 0) {
$list.find('li').eq(activeResult).addClass('active'); $list.find('li').eq(activeResult).addClass('active');
} }
} }
function monitorInputHiding() { function monitorInputHiding() {
monitorInputHidingInterval = window.setInterval(function() { monitorInputHidingInterval = window.setInterval(function() {
if (!$input.is(':visible')) { if (!$input.is(':visible')) {
hide(); hide();
} }
}, 100); }, 100);
} }
return options; return options;
}; };

View file

@ -2,64 +2,64 @@ var App = App || {};
App.Controls = App.Controls || {}; App.Controls = App.Controls || {};
App.Controls.FileDropper = function($fileInput) { App.Controls.FileDropper = function($fileInput) {
var _ = App.DI.get('_'); var _ = App.DI.get('_');
var jQuery = App.DI.get('jQuery'); var jQuery = App.DI.get('jQuery');
var options = { var options = {
onChange: null, onChange: null,
setNames: false, setNames: false,
}; };
var $dropDiv = jQuery('<button type="button" class="file-handler"></button>'); var $dropDiv = jQuery('<button type="button" class="file-handler"></button>');
var allowMultiple = $fileInput.attr('multiple'); var allowMultiple = $fileInput.attr('multiple');
$dropDiv.html((allowMultiple ? 'Drop files here!' : 'Drop file here!') + '<br/>Or just click on this box.'); $dropDiv.html((allowMultiple ? 'Drop files here!' : 'Drop file here!') + '<br/>Or just click on this box.');
$dropDiv.insertBefore($fileInput); $dropDiv.insertBefore($fileInput);
$fileInput.attr('multiple', allowMultiple); $fileInput.attr('multiple', allowMultiple);
$fileInput.hide(); $fileInput.hide();
$fileInput.change(function(e) { $fileInput.change(function(e) {
addFiles(this.files); addFiles(this.files);
}); });
$dropDiv.on('dragenter', function(e) { $dropDiv.on('dragenter', function(e) {
$dropDiv.addClass('active'); $dropDiv.addClass('active');
}).on('dragleave', function(e) { }).on('dragleave', function(e) {
$dropDiv.removeClass('active'); $dropDiv.removeClass('active');
}).on('dragover', function(e) { }).on('dragover', function(e) {
e.preventDefault(); e.preventDefault();
}).on('drop', function(e) { }).on('drop', function(e) {
e.preventDefault(); e.preventDefault();
addFiles(e.originalEvent.dataTransfer.files); addFiles(e.originalEvent.dataTransfer.files);
}).on('click', function(e) { }).on('click', function(e) {
$fileInput.show().focus().trigger('click').hide(); $fileInput.show().focus().trigger('click').hide();
$dropDiv.addClass('active'); $dropDiv.addClass('active');
}); });
function addFiles(files) { function addFiles(files) {
$dropDiv.removeClass('active'); $dropDiv.removeClass('active');
if (!allowMultiple && files.length > 1) { if (!allowMultiple && files.length > 1) {
window.alert('Cannot select multiple files.'); window.alert('Cannot select multiple files.');
return; return;
} }
if (typeof(options.onChange) !== 'undefined') { if (typeof(options.onChange) !== 'undefined') {
options.onChange(files); options.onChange(files);
} }
if (options.setNames && !allowMultiple) { if (options.setNames && !allowMultiple) {
$dropDiv.text(files[0].name); $dropDiv.text(files[0].name);
} }
} }
function readAsDataURL(file, callback) { function readAsDataURL(file, callback) {
var reader = new FileReader(); var reader = new FileReader();
reader.onloadend = function() { reader.onloadend = function() {
callback(reader.result); callback(reader.result);
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
_.extend(options, { _.extend(options, {
readAsDataURL: readAsDataURL, readAsDataURL: readAsDataURL,
}); });
return options; return options;
}; };

View file

@ -2,419 +2,419 @@ var App = App || {};
App.Controls = App.Controls || {}; App.Controls = App.Controls || {};
App.Controls.TagInput = function($underlyingInput) { App.Controls.TagInput = function($underlyingInput) {
var _ = App.DI.get('_'); var _ = App.DI.get('_');
var jQuery = App.DI.get('jQuery'); var jQuery = App.DI.get('jQuery');
var promise = App.DI.get('promise'); var promise = App.DI.get('promise');
var api = App.DI.get('api'); var api = App.DI.get('api');
var tagList = App.DI.get('tagList'); var tagList = App.DI.get('tagList');
var KEY_RETURN = 13; var KEY_RETURN = 13;
var KEY_SPACE = 32; var KEY_SPACE = 32;
var KEY_BACKSPACE = 8; var KEY_BACKSPACE = 8;
var tagConfirmKeys = [KEY_RETURN, KEY_SPACE]; var tagConfirmKeys = [KEY_RETURN, KEY_SPACE];
var inputConfirmKeys = [KEY_RETURN]; var inputConfirmKeys = [KEY_RETURN];
var SOURCE_INITIAL_TEXT = 1; var SOURCE_INITIAL_TEXT = 1;
var SOURCE_AUTOCOMPLETION = 2; var SOURCE_AUTOCOMPLETION = 2;
var SOURCE_PASTE = 3; var SOURCE_PASTE = 3;
var SOURCE_IMPLICATIONS = 4; var SOURCE_IMPLICATIONS = 4;
var SOURCE_INPUT_BLUR = 5; var SOURCE_INPUT_BLUR = 5;
var SOURCE_INPUT_ENTER = 6; var SOURCE_INPUT_ENTER = 6;
var SOURCE_SUGGESTIONS = 7; var SOURCE_SUGGESTIONS = 7;
var tags = []; var tags = [];
var options = { var options = {
beforeTagAdded: null, beforeTagAdded: null,
beforeTagRemoved: null, beforeTagRemoved: null,
inputConfirmed: null, inputConfirmed: null,
}; };
var $wrapper = jQuery('<div class="tag-input">'); var $wrapper = jQuery('<div class="tag-input">');
var $tagList = jQuery('<ul class="tags">'); var $tagList = jQuery('<ul class="tags">');
var tagInputId = 'tags' + Math.random(); var tagInputId = 'tags' + Math.random();
var $label = jQuery('<label for="' + tagInputId + '" style="display: none">Tags:</label>'); var $label = jQuery('<label for="' + tagInputId + '" style="display: none">Tags:</label>');
var $input = jQuery('<input class="tag-real-input" type="text" id="' + tagInputId + '"/>'); var $input = jQuery('<input class="tag-real-input" type="text" id="' + tagInputId + '"/>');
var $siblings = jQuery('<div class="related-tags"><span>Sibling tags:</span><ul>'); var $siblings = jQuery('<div class="related-tags"><span>Sibling tags:</span><ul>');
var $suggestions = jQuery('<div class="related-tags"><span>Suggested tags:</span><ul>'); var $suggestions = jQuery('<div class="related-tags"><span>Suggested tags:</span><ul>');
init(); init();
render(); render();
initAutoComplete(); initAutoComplete();
function init() { function init() {
if ($underlyingInput.length === 0) { if ($underlyingInput.length === 0) {
throw new Error('Tag input element was not found'); throw new Error('Tag input element was not found');
} }
if ($underlyingInput.length > 1) { if ($underlyingInput.length > 1) {
throw new Error('Cannot set tag input to more than one element at once'); throw new Error('Cannot set tag input to more than one element at once');
} }
if ($underlyingInput.attr('data-tagged')) { if ($underlyingInput.attr('data-tagged')) {
throw new Error('Tag input was already initialized for this element'); throw new Error('Tag input was already initialized for this element');
} }
$underlyingInput.attr('data-tagged', true); $underlyingInput.attr('data-tagged', true);
} }
function render() { function render() {
$underlyingInput.hide(); $underlyingInput.hide();
$wrapper.append($tagList); $wrapper.append($tagList);
$wrapper.append($label); $wrapper.append($label);
$wrapper.append($input); $wrapper.append($input);
$wrapper.insertAfter($underlyingInput); $wrapper.insertAfter($underlyingInput);
$wrapper.click(function(e) { $wrapper.click(function(e) {
if (e.target.nodeName === 'LI') { if (e.target.nodeName === 'LI') {
return; return;
} }
e.preventDefault(); e.preventDefault();
$input.focus(); $input.focus();
}); });
$input.attr('placeholder', $underlyingInput.attr('placeholder')); $input.attr('placeholder', $underlyingInput.attr('placeholder'));
$siblings.insertAfter($wrapper); $siblings.insertAfter($wrapper);
$suggestions.insertAfter($wrapper); $suggestions.insertAfter($wrapper);
processText($underlyingInput.val(), SOURCE_INITIAL_TEXT); processText($underlyingInput.val(), SOURCE_INITIAL_TEXT);
$underlyingInput.val(''); $underlyingInput.val('');
} }
function initAutoComplete() { function initAutoComplete() {
var autoComplete = new App.Controls.AutoCompleteInput($input); var autoComplete = new App.Controls.AutoCompleteInput($input);
autoComplete.onDelete = function(text) { autoComplete.onDelete = function(text) {
removeTag(text); removeTag(text);
$input.val(''); $input.val('');
}; };
autoComplete.onApply = function(text) { autoComplete.onApply = function(text) {
processText(text, SOURCE_AUTOCOMPLETION); processText(text, SOURCE_AUTOCOMPLETION);
$input.val(''); $input.val('');
}; };
autoComplete.additionalFilter = function(results) { autoComplete.additionalFilter = function(results) {
return _.filter(results, function(resultItem) { return _.filter(results, function(resultItem) {
return !_.contains(getTags(), resultItem[0]); return !_.contains(getTags(), resultItem[0]);
}); });
}; };
autoComplete.onRender = function($list) { autoComplete.onRender = function($list) {
$list.find('li').each(function() { $list.find('li').each(function() {
var $li = jQuery(this); var $li = jQuery(this);
if (isTaggedWith($li.attr('data-key'))) { if (isTaggedWith($li.attr('data-key'))) {
$li.css('opacity', '0.5'); $li.css('opacity', '0.5');
} }
}); });
}; };
} }
$input.bind('focus', function(e) { $input.bind('focus', function(e) {
$wrapper.addClass('focused'); $wrapper.addClass('focused');
}); });
$input.bind('blur', function(e) { $input.bind('blur', function(e) {
$wrapper.removeClass('focused'); $wrapper.removeClass('focused');
var tagName = $input.val(); var tagName = $input.val();
addTag(tagName, SOURCE_INPUT_BLUR); addTag(tagName, SOURCE_INPUT_BLUR);
$input.val(''); $input.val('');
}); });
$input.bind('paste', function(e) { $input.bind('paste', function(e) {
e.preventDefault(); e.preventDefault();
var pastedText; var pastedText;
if (window.clipboardData) { if (window.clipboardData) {
pastedText = window.clipboardData.getData('Text'); pastedText = window.clipboardData.getData('Text');
} else { } else {
pastedText = (e.originalEvent || e).clipboardData.getData('text/plain'); pastedText = (e.originalEvent || e).clipboardData.getData('text/plain');
} }
if (pastedText.length > 2000) { if (pastedText.length > 2000) {
window.alert('Pasted text is too long.'); window.alert('Pasted text is too long.');
return; return;
} }
processTextWithoutLast(pastedText, SOURCE_PASTE); processTextWithoutLast(pastedText, SOURCE_PASTE);
}); });
$input.bind('keydown', function(e) { $input.bind('keydown', function(e) {
if (_.contains(inputConfirmKeys, e.which) && !$input.val()) { if (_.contains(inputConfirmKeys, e.which) && !$input.val()) {
e.preventDefault(); e.preventDefault();
if (typeof(options.inputConfirmed) !== 'undefined') { if (typeof(options.inputConfirmed) !== 'undefined') {
options.inputConfirmed(); options.inputConfirmed();
} }
} else if (_.contains(tagConfirmKeys, e.which)) { } else if (_.contains(tagConfirmKeys, e.which)) {
var tagName = $input.val(); var tagName = $input.val();
e.preventDefault(); e.preventDefault();
$input.val(''); $input.val('');
addTag(tagName, SOURCE_INPUT_ENTER); addTag(tagName, SOURCE_INPUT_ENTER);
} else if (e.which === KEY_BACKSPACE && jQuery(this).val().length === 0) { } else if (e.which === KEY_BACKSPACE && jQuery(this).val().length === 0) {
e.preventDefault(); e.preventDefault();
removeLastTag(); removeLastTag();
} }
}); });
function explodeText(text) { function explodeText(text) {
return _.filter(text.trim().split(/\s+/), function(item) { return _.filter(text.trim().split(/\s+/), function(item) {
return item.length > 0; return item.length > 0;
}); });
} }
function processText(text, source) { function processText(text, source) {
var tagNamesToAdd = explodeText(text); var tagNamesToAdd = explodeText(text);
_.map(tagNamesToAdd, function(tagName) { addTag(tagName, source); }); _.map(tagNamesToAdd, function(tagName) { addTag(tagName, source); });
} }
function processTextWithoutLast(text, source) { function processTextWithoutLast(text, source) {
var tagNamesToAdd = explodeText(text); var tagNamesToAdd = explodeText(text);
var lastTagName = tagNamesToAdd.pop(); var lastTagName = tagNamesToAdd.pop();
_.map(tagNamesToAdd, function(tagName) { addTag(tagName, source); }); _.map(tagNamesToAdd, function(tagName) { addTag(tagName, source); });
$input.val(lastTagName); $input.val(lastTagName);
} }
function addTag(tagName, source) { function addTag(tagName, source) {
tagName = tagName.trim(); tagName = tagName.trim();
if (tagName.length === 0) { if (tagName.length === 0) {
return; return;
} }
if (tagName.length > 64) { if (tagName.length > 64) {
//showing alert inside keydown event leads to mysterious behaviors //showing alert inside keydown event leads to mysterious behaviors
//in some browsers, hence the timeout //in some browsers, hence the timeout
window.setTimeout(function() { window.setTimeout(function() {
window.alert('Tag is too long.'); window.alert('Tag is too long.');
}, 10); }, 10);
return; return;
} }
if (isTaggedWith(tagName)) { if (isTaggedWith(tagName)) {
flashTagRed(tagName); flashTagRed(tagName);
} else { } else {
beforeTagAdded(tagName, source); beforeTagAdded(tagName, source);
var exportedTag = getExportedTag(tagName); var exportedTag = getExportedTag(tagName);
if (!exportedTag || !exportedTag.banned) { if (!exportedTag || !exportedTag.banned) {
tags.push(tagName); tags.push(tagName);
var $elem = createListElement(tagName); var $elem = createListElement(tagName);
$tagList.append($elem); $tagList.append($elem);
} }
afterTagAdded(tagName, source); afterTagAdded(tagName, source);
} }
} }
function beforeTagRemoved(tagName) { function beforeTagRemoved(tagName) {
if (typeof(options.beforeTagRemoved) === 'function') { if (typeof(options.beforeTagRemoved) === 'function') {
options.beforeTagRemoved(tagName); options.beforeTagRemoved(tagName);
} }
} }
function afterTagRemoved(tagName) { function afterTagRemoved(tagName) {
refreshShownSiblings(); refreshShownSiblings();
} }
function beforeTagAdded(tagName, source) { function beforeTagAdded(tagName, source) {
if (typeof(options.beforeTagAdded) === 'function') { if (typeof(options.beforeTagAdded) === 'function') {
options.beforeTagAdded(tagName); options.beforeTagAdded(tagName);
} }
} }
function afterTagAdded(tagName, source) { function afterTagAdded(tagName, source) {
if (source === SOURCE_IMPLICATIONS) { if (source === SOURCE_IMPLICATIONS) {
flashTagYellow(tagName); flashTagYellow(tagName);
} else if (source !== SOURCE_INITIAL_TEXT) { } else if (source !== SOURCE_INITIAL_TEXT) {
var tag = getExportedTag(tagName); var tag = getExportedTag(tagName);
if (tag) { if (tag) {
_.each(tag.implications, function(impliedTagName) { _.each(tag.implications, function(impliedTagName) {
if (!isTaggedWith(impliedTagName)) { if (!isTaggedWith(impliedTagName)) {
addTag(impliedTagName, SOURCE_IMPLICATIONS); addTag(impliedTagName, SOURCE_IMPLICATIONS);
} }
}); });
if (source !== SOURCE_IMPLICATIONS && source !== SOURCE_SUGGESTIONS) { if (source !== SOURCE_IMPLICATIONS && source !== SOURCE_SUGGESTIONS) {
showOrHideSuggestions(tagName); showOrHideSuggestions(tagName);
refreshShownSiblings(); refreshShownSiblings();
} }
} else { } else {
flashTagGreen(tagName); flashTagGreen(tagName);
} }
} }
} }
function getExportedTag(tagName) { function getExportedTag(tagName) {
return _.first(_.filter( return _.first(_.filter(
tagList.getTags(), tagList.getTags(),
function(t) { function(t) {
return t.name.toLowerCase() === tagName.toLowerCase(); return t.name.toLowerCase() === tagName.toLowerCase();
})); }));
} }
function removeTag(tagName) { function removeTag(tagName) {
var oldTagNames = getTags(); var oldTagNames = getTags();
var newTagNames = _.without(oldTagNames, tagName); var newTagNames = _.without(oldTagNames, tagName);
if (newTagNames.length !== oldTagNames.length) { if (newTagNames.length !== oldTagNames.length) {
beforeTagRemoved(tagName); beforeTagRemoved(tagName);
setTags(newTagNames); setTags(newTagNames);
afterTagRemoved(tagName); afterTagRemoved(tagName);
} }
} }
function isTaggedWith(tagName) { function isTaggedWith(tagName) {
var tagNames = _.map(getTags(), function(tagName) { var tagNames = _.map(getTags(), function(tagName) {
return tagName.toLowerCase(); return tagName.toLowerCase();
}); });
return _.contains(tagNames, tagName.toLowerCase()); return _.contains(tagNames, tagName.toLowerCase());
} }
function removeLastTag() { function removeLastTag() {
removeTag(_.last(getTags())); removeTag(_.last(getTags()));
} }
function flashTagRed(tagName) { function flashTagRed(tagName) {
flashTag(tagName, 'rgba(255, 200, 200, 1)'); flashTag(tagName, 'rgba(255, 200, 200, 1)');
} }
function flashTagYellow(tagName) { function flashTagYellow(tagName) {
flashTag(tagName, 'rgba(255, 255, 200, 1)'); flashTag(tagName, 'rgba(255, 255, 200, 1)');
} }
function flashTagGreen(tagName) { function flashTagGreen(tagName) {
flashTag(tagName, 'rgba(200, 255, 200, 1)'); flashTag(tagName, 'rgba(200, 255, 200, 1)');
} }
function flashTag(tagName, color) { function flashTag(tagName, color) {
var $elem = getListElement(tagName); var $elem = getListElement(tagName);
$elem.css({backgroundColor: color}); $elem.css({backgroundColor: color});
} }
function getListElement(tagName) { function getListElement(tagName) {
return $tagList.find('li[data-tag="' + tagName.toLowerCase() + '"]'); return $tagList.find('li[data-tag="' + tagName.toLowerCase() + '"]');
} }
function setTags(newTagNames) { function setTags(newTagNames) {
tags = newTagNames.slice(); tags = newTagNames.slice();
$tagList.empty(); $tagList.empty();
$underlyingInput.val(newTagNames.join(' ')); $underlyingInput.val(newTagNames.join(' '));
_.each(newTagNames, function(tagName) { _.each(newTagNames, function(tagName) {
var $elem = createListElement(tagName); var $elem = createListElement(tagName);
$tagList.append($elem); $tagList.append($elem);
}); });
} }
function createListElement(tagName) { function createListElement(tagName) {
var $elem = jQuery('<li/>'); var $elem = jQuery('<li/>');
$elem.attr('data-tag', tagName.toLowerCase()); $elem.attr('data-tag', tagName.toLowerCase());
var $tagLink = jQuery('<a class="tag">'); var $tagLink = jQuery('<a class="tag">');
$tagLink.text(tagName + ' ' /* for easy copying */); $tagLink.text(tagName + ' ' /* for easy copying */);
$tagLink.click(function(e) { $tagLink.click(function(e) {
e.preventDefault(); e.preventDefault();
showOrHideSiblings(tagName); showOrHideSiblings(tagName);
showOrHideSuggestions(tagName); showOrHideSuggestions(tagName);
}); });
$elem.append($tagLink); $elem.append($tagLink);
var $deleteButton = jQuery('<a class="close"><i class="fa fa-remove"></i></a>'); var $deleteButton = jQuery('<a class="close"><i class="fa fa-remove"></i></a>');
$deleteButton.click(function(e) { $deleteButton.click(function(e) {
e.preventDefault(); e.preventDefault();
removeTag(tagName); removeTag(tagName);
$input.focus(); $input.focus();
}); });
$elem.append($deleteButton); $elem.append($deleteButton);
return $elem; return $elem;
} }
function showOrHideSuggestions(tagName) { function showOrHideSuggestions(tagName) {
var tag = getExportedTag(tagName); var tag = getExportedTag(tagName);
var suggestions = tag ? tag.suggestions : []; var suggestions = tag ? tag.suggestions : [];
updateSuggestions($suggestions, suggestions); updateSuggestions($suggestions, suggestions);
} }
function showOrHideSiblings(tagName) { function showOrHideSiblings(tagName) {
if ($siblings.data('lastTag') === tagName && $siblings.is(':visible')) { if ($siblings.data('lastTag') === tagName && $siblings.is(':visible')) {
$siblings.slideUp('fast'); $siblings.slideUp('fast');
$siblings.data('lastTag', null); $siblings.data('lastTag', null);
return; return;
} }
promise.wait(getSiblings(tagName), promise.make(function(resolve, reject) { promise.wait(getSiblings(tagName), promise.make(function(resolve, reject) {
$siblings.slideUp('fast', resolve); $siblings.slideUp('fast', resolve);
})).then(function(siblings) { })).then(function(siblings) {
siblings = _.pluck(siblings, 'name'); siblings = _.pluck(siblings, 'name');
$siblings.data('lastTag', tagName); $siblings.data('lastTag', tagName);
$siblings.data('siblings', siblings); $siblings.data('siblings', siblings);
updateSuggestions($siblings, siblings); updateSuggestions($siblings, siblings);
}).fail(function() { }).fail(function() {
}); });
} }
function refreshShownSiblings() { function refreshShownSiblings() {
updateSuggestions($siblings, $siblings.data('siblings')); updateSuggestions($siblings, $siblings.data('siblings'));
} }
function updateSuggestions($target, suggestedTagNames) { function updateSuggestions($target, suggestedTagNames) {
function filterSuggestions(sourceTagNames) { function filterSuggestions(sourceTagNames) {
if (!sourceTagNames) { if (!sourceTagNames) {
return []; return [];
} }
var tagNames = _.filter(sourceTagNames.slice(), function(tagName) { var tagNames = _.filter(sourceTagNames.slice(), function(tagName) {
return !isTaggedWith(tagName); return !isTaggedWith(tagName);
}); });
tagNames = tagNames.slice(0, 20); tagNames = tagNames.slice(0, 20);
return tagNames; return tagNames;
} }
function attachTagsToSuggestionList($list, tagNames) { function attachTagsToSuggestionList($list, tagNames) {
$list.empty(); $list.empty();
_.each(tagNames, function(tagName) { _.each(tagNames, function(tagName) {
var $li = jQuery('<li>'); var $li = jQuery('<li>');
var $a = jQuery('<a href="#/posts/query=' + tagName + '">'); var $a = jQuery('<a href="#/posts/query=' + tagName + '">');
$a.text(tagName); $a.text(tagName);
$a.click(function(e) { $a.click(function(e) {
e.preventDefault(); e.preventDefault();
addTag(tagName, SOURCE_SUGGESTIONS); addTag(tagName, SOURCE_SUGGESTIONS);
$li.fadeOut('fast', function() { $li.fadeOut('fast', function() {
$li.remove(); $li.remove();
if ($list.children().length === 0) { if ($list.children().length === 0) {
$list.parent('div').slideUp('fast'); $list.parent('div').slideUp('fast');
} }
}); });
}); });
$li.append($a); $li.append($a);
$list.append($li); $list.append($li);
}); });
} }
var suggestions = filterSuggestions(suggestedTagNames); var suggestions = filterSuggestions(suggestedTagNames);
if (suggestions.length > 0) { if (suggestions.length > 0) {
attachTagsToSuggestionList($target.find('ul'), suggestions); attachTagsToSuggestionList($target.find('ul'), suggestions);
$target.slideDown('fast'); $target.slideDown('fast');
} else { } else {
$target.slideUp('fast'); $target.slideUp('fast');
} }
} }
function getSiblings(tagName) { function getSiblings(tagName) {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
promise.wait(api.get('/tags/' + tagName + '/siblings')) promise.wait(api.get('/tags/' + tagName + '/siblings'))
.then(function(response) { .then(function(response) {
resolve(response.json.data); resolve(response.json.data);
}).fail(function() { }).fail(function() {
reject(); reject();
}); });
}); });
} }
function getTags() { function getTags() {
return tags; return tags;
} }
function focus() { function focus() {
$input.focus(); $input.focus();
} }
function hideSuggestions() { function hideSuggestions() {
$siblings.hide(); $siblings.hide();
$suggestions.hide(); $suggestions.hide();
$siblings.data('siblings', []); $siblings.data('siblings', []);
} }
_.extend(options, { _.extend(options, {
setTags: setTags, setTags: setTags,
getTags: getTags, getTags: getTags,
removeTag: removeTag, removeTag: removeTag,
addTag: addTag, addTag: addTag,
focus: focus, focus: focus,
hideSuggestions: hideSuggestions, hideSuggestions: hideSuggestions,
}); });
return options; return options;
}; };

View file

@ -2,53 +2,53 @@ var App = App || {};
App.DI = (function() { App.DI = (function() {
var factories = {}; var factories = {};
var instances = {}; var instances = {};
function get(key) { function get(key) {
var instance = instances[key]; var instance = instances[key];
if (!instance) { if (!instance) {
var factory = factories[key]; var factory = factories[key];
if (!factory) { if (!factory) {
throw new Error('Unregistered key: ' + key); throw new Error('Unregistered key: ' + key);
} }
var objectInitializer = factory.initializer; var objectInitializer = factory.initializer;
var singleton = factory.singleton; var singleton = factory.singleton;
var deps = resolveDependencies(objectInitializer, factory.dependencies); var deps = resolveDependencies(objectInitializer, factory.dependencies);
instance = {}; instance = {};
instance = objectInitializer.apply(instance, deps); instance = objectInitializer.apply(instance, deps);
if (singleton) { if (singleton) {
instances[key] = instance; instances[key] = instance;
} }
} }
return instance; return instance;
} }
function resolveDependencies(objectIntializer, depKeys) { function resolveDependencies(objectIntializer, depKeys) {
var deps = []; var deps = [];
for (var i = 0; i < depKeys.length; i ++) { for (var i = 0; i < depKeys.length; i ++) {
deps[i] = get(depKeys[i]); deps[i] = get(depKeys[i]);
} }
return deps; return deps;
} }
function register(key, dependencies, objectInitializer) { function register(key, dependencies, objectInitializer) {
factories[key] = {initializer: objectInitializer, singleton: false, dependencies: dependencies}; factories[key] = {initializer: objectInitializer, singleton: false, dependencies: dependencies};
} }
function registerSingleton(key, dependencies, objectInitializer) { function registerSingleton(key, dependencies, objectInitializer) {
factories[key] = {initializer: objectInitializer, singleton: true, dependencies: dependencies}; factories[key] = {initializer: objectInitializer, singleton: true, dependencies: dependencies};
} }
function registerManual(key, objectInitializer) { function registerManual(key, objectInitializer) {
instances[key] = objectInitializer(); instances[key] = objectInitializer();
} }
return { return {
get: get, get: get,
register: register, register: register,
registerManual: registerManual, registerManual: registerManual,
registerSingleton: registerSingleton, registerSingleton: registerSingleton,
}; };
})(); })();

View file

@ -2,60 +2,60 @@ var App = App || {};
App.Keyboard = function(jQuery, mousetrap, browsingSettings) { App.Keyboard = function(jQuery, mousetrap, browsingSettings) {
var enabled = browsingSettings.getSettings().keyboardShortcuts; var enabled = browsingSettings.getSettings().keyboardShortcuts;
var oldStopCallback = mousetrap.stopCallback; var oldStopCallback = mousetrap.stopCallback;
mousetrap.stopCallback = function(e, element, combo, sequence) { mousetrap.stopCallback = function(e, element, combo, sequence) {
if (combo.indexOf('ctrl') === -1 && e.ctrlKey) { if (combo.indexOf('ctrl') === -1 && e.ctrlKey) {
return true; return true;
} }
if (combo.indexOf('alt') === -1 && e.altKey) { if (combo.indexOf('alt') === -1 && e.altKey) {
return true; return true;
} }
if (combo.indexOf('ctrl') !== -1) { if (combo.indexOf('ctrl') !== -1) {
return false; return false;
} }
var $focused = jQuery(':focus').eq(0); var $focused = jQuery(':focus').eq(0);
if ($focused.length) { if ($focused.length) {
if ($focused.prop('tagName').match(/embed|object/i)) { if ($focused.prop('tagName').match(/embed|object/i)) {
return true; return true;
} }
if ($focused.prop('tagName').toLowerCase() === 'input' && if ($focused.prop('tagName').toLowerCase() === 'input' &&
$focused.attr('type').match(/checkbox|radio/i)) { $focused.attr('type').match(/checkbox|radio/i)) {
return false; return false;
} }
} }
return oldStopCallback.apply(mousetrap, arguments); return oldStopCallback.apply(mousetrap, arguments);
}; };
function keyup(key, callback) { function keyup(key, callback) {
unbind(key); unbind(key);
if (enabled) { if (enabled) {
mousetrap.bind(key, callback, 'keyup'); mousetrap.bind(key, callback, 'keyup');
} }
} }
function keydown(key, callback) { function keydown(key, callback) {
unbind(key); unbind(key);
if (enabled) { if (enabled) {
mousetrap.bind(key, callback); mousetrap.bind(key, callback);
} }
} }
function reset() { function reset() {
mousetrap.reset(); mousetrap.reset();
} }
function unbind(key) { function unbind(key) {
mousetrap.unbind(key, 'keyup'); mousetrap.unbind(key, 'keyup');
mousetrap.unbind(key); mousetrap.unbind(key);
} }
return { return {
keydown: keydown, keydown: keydown,
keyup: keyup, keyup: keyup,
reset: reset, reset: reset,
unbind: unbind, unbind: unbind,
}; };
}; };
App.DI.register('keyboard', ['jQuery', 'mousetrap', 'browsingSettings'], App.Keyboard); App.DI.register('keyboard', ['jQuery', 'mousetrap', 'browsingSettings'], App.Keyboard);

View file

@ -1,142 +1,142 @@
var App = App || {}; var App = App || {};
App.Pager = function( App.Pager = function(
_, _,
promise, promise,
api) { api) {
var totalPages; var totalPages;
var pageNumber; var pageNumber;
var searchParams; var searchParams;
var url; var url;
var cache = {}; var cache = {};
function init(args) { function init(args) {
url = args.url; url = args.url;
setSearchParams(args.searchParams); setSearchParams(args.searchParams);
if (typeof(args.page) !== 'undefined') { if (typeof(args.page) !== 'undefined') {
setPage(args.page); setPage(args.page);
} else { } else {
setPage(1); setPage(1);
} }
} }
function getPage() { function getPage() {
return pageNumber; return pageNumber;
} }
function getTotalPages() { function getTotalPages() {
return totalPages; return totalPages;
} }
function prevPage() { function prevPage() {
if (pageNumber > 1) { if (pageNumber > 1) {
setPage(pageNumber - 1); setPage(pageNumber - 1);
return true; return true;
} }
return false; return false;
} }
function nextPage() { function nextPage() {
if (pageNumber < totalPages) { if (pageNumber < totalPages) {
setPage(pageNumber + 1); setPage(pageNumber + 1);
return true; return true;
} }
return false; return false;
} }
function setPage(newPageNumber) { function setPage(newPageNumber) {
pageNumber = parseInt(newPageNumber); pageNumber = parseInt(newPageNumber);
if (!pageNumber || isNaN(pageNumber)) { if (!pageNumber || isNaN(pageNumber)) {
throw new Error('Trying to set page to a non-number (' + newPageNumber + ')'); throw new Error('Trying to set page to a non-number (' + newPageNumber + ')');
} }
} }
function getSearchParams() { function getSearchParams() {
return searchParams; return searchParams;
} }
function setSearchParams(newSearchParams) { function setSearchParams(newSearchParams) {
setPage(1); setPage(1);
searchParams = _.extend({}, newSearchParams); searchParams = _.extend({}, newSearchParams);
delete searchParams.page; delete searchParams.page;
} }
function retrieve() { function retrieve() {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
promise.wait(api.get(url, _.extend({}, searchParams, {page: pageNumber}))) promise.wait(api.get(url, _.extend({}, searchParams, {page: pageNumber})))
.then(function(response) { .then(function(response) {
var pageSize = response.json.pageSize; var pageSize = response.json.pageSize;
var totalRecords = response.json.totalRecords; var totalRecords = response.json.totalRecords;
totalPages = Math.ceil(totalRecords / pageSize); totalPages = Math.ceil(totalRecords / pageSize);
resolve({ resolve({
entities: response.json.data, entities: response.json.data,
totalRecords: totalRecords, totalRecords: totalRecords,
totalPages: totalPages}); totalPages: totalPages});
}).fail(function(response) { }).fail(function(response) {
reject(response); reject(response);
}); });
}); });
} }
function retrieveCached() { function retrieveCached() {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
var cacheKey = JSON.stringify(_.extend({}, searchParams, {url: url, page: getPage()})); var cacheKey = JSON.stringify(_.extend({}, searchParams, {url: url, page: getPage()}));
if (cacheKey in cache) { if (cacheKey in cache) {
resolve.apply(this, cache[cacheKey]); resolve.apply(this, cache[cacheKey]);
} else { } else {
promise.wait(retrieve()) promise.wait(retrieve())
.then(function() { .then(function() {
cache[cacheKey] = arguments; cache[cacheKey] = arguments;
resolve.apply(this, arguments); resolve.apply(this, arguments);
}).fail(function() { }).fail(function() {
reject.apply(this, arguments); reject.apply(this, arguments);
}); });
} }
}); });
} }
function resetCache() { function resetCache() {
cache = {}; cache = {};
} }
function getVisiblePages() { function getVisiblePages() {
var pages = [1, totalPages || 1]; var pages = [1, totalPages || 1];
var pagesAroundCurrent = 2; var pagesAroundCurrent = 2;
for (var i = -pagesAroundCurrent; i <= pagesAroundCurrent; i ++) { for (var i = -pagesAroundCurrent; i <= pagesAroundCurrent; i ++) {
if (pageNumber + i >= 1 && pageNumber + i <= totalPages) { if (pageNumber + i >= 1 && pageNumber + i <= totalPages) {
pages.push(pageNumber + i); pages.push(pageNumber + i);
} }
} }
if (pageNumber - pagesAroundCurrent - 1 === 2) { if (pageNumber - pagesAroundCurrent - 1 === 2) {
pages.push(2); pages.push(2);
} }
if (pageNumber + pagesAroundCurrent + 1 === totalPages - 1) { if (pageNumber + pagesAroundCurrent + 1 === totalPages - 1) {
pages.push(totalPages - 1); pages.push(totalPages - 1);
} }
return pages.sort(function(a, b) { return a - b; }).filter(function(item, pos) { return pages.sort(function(a, b) { return a - b; }).filter(function(item, pos) {
return !pos || item !== pages[pos - 1]; return !pos || item !== pages[pos - 1];
}); });
} }
return { return {
init: init, init: init,
getPage: getPage, getPage: getPage,
getTotalPages: getTotalPages, getTotalPages: getTotalPages,
prevPage: prevPage, prevPage: prevPage,
nextPage: nextPage, nextPage: nextPage,
setPage: setPage, setPage: setPage,
getSearchParams: getSearchParams, getSearchParams: getSearchParams,
setSearchParams: setSearchParams, setSearchParams: setSearchParams,
retrieve: retrieve, retrieve: retrieve,
retrieveCached: retrieveCached, retrieveCached: retrieveCached,
getVisiblePages: getVisiblePages, getVisiblePages: getVisiblePages,
resetCache: resetCache, resetCache: resetCache,
}; };
}; };

View file

@ -2,53 +2,53 @@ var App = App || {};
App.PresenterManager = function(jQuery, promise, topNavigationPresenter, keyboard) { App.PresenterManager = function(jQuery, promise, topNavigationPresenter, keyboard) {
var lastContentPresenter = null; var lastContentPresenter = null;
function init() { function init() {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
initPresenter(topNavigationPresenter, [], resolve); initPresenter(topNavigationPresenter, [], resolve);
}); });
} }
function initPresenter(presenter, args, loaded) { function initPresenter(presenter, args, loaded) {
presenter.init.call(presenter, args, loaded); presenter.init.call(presenter, args, loaded);
} }
function switchContentPresenter(presenter, args) { function switchContentPresenter(presenter, args) {
if (lastContentPresenter === null || lastContentPresenter.name !== presenter.name) { if (lastContentPresenter === null || lastContentPresenter.name !== presenter.name) {
if (lastContentPresenter !== null && lastContentPresenter.deinit) { if (lastContentPresenter !== null && lastContentPresenter.deinit) {
lastContentPresenter.deinit(); lastContentPresenter.deinit();
} }
keyboard.reset(); keyboard.reset();
topNavigationPresenter.changeTitle(null); topNavigationPresenter.changeTitle(null);
topNavigationPresenter.focus(); topNavigationPresenter.focus();
presenter.init.call(presenter, args, function() {}); presenter.init.call(presenter, args, function() {});
lastContentPresenter = presenter; lastContentPresenter = presenter;
} else if (lastContentPresenter.reinit) { } else if (lastContentPresenter.reinit) {
lastContentPresenter.reinit.call(lastContentPresenter, args, function() {}); lastContentPresenter.reinit.call(lastContentPresenter, args, function() {});
} }
} }
function initPresenters(options, loaded) { function initPresenters(options, loaded) {
var count = 0; var count = 0;
var subPresenterLoaded = function() { var subPresenterLoaded = function() {
count ++; count ++;
if (count === options.length) { if (count === options.length) {
loaded(); loaded();
} }
}; };
for (var i = 0; i < options.length; i ++) { for (var i = 0; i < options.length; i ++) {
initPresenter(options[i][0], options[i][1], subPresenterLoaded); initPresenter(options[i][0], options[i][1], subPresenterLoaded);
} }
} }
return { return {
init: init, init: init,
initPresenter: initPresenter, initPresenter: initPresenter,
initPresenters: initPresenters, initPresenters: initPresenters,
switchContentPresenter: switchContentPresenter, switchContentPresenter: switchContentPresenter,
}; };
}; };

View file

@ -2,230 +2,230 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.CommentListPresenter = function( App.Presenters.CommentListPresenter = function(
_, _,
jQuery, jQuery,
util, util,
promise, promise,
api, api,
auth, auth,
topNavigationPresenter, topNavigationPresenter,
messagePresenter) { messagePresenter) {
var $el; var $el;
var privileges; var privileges;
var templates = {}; var templates = {};
var post; var post;
var comments = []; var comments = [];
function init(params, loaded) { function init(params, loaded) {
$el = params.$target; $el = params.$target;
post = params.post; post = params.post;
comments = params.comments || []; comments = params.comments || [];
privileges = { privileges = {
canListComments: auth.hasPrivilege(auth.privileges.listComments), canListComments: auth.hasPrivilege(auth.privileges.listComments),
canAddComments: auth.hasPrivilege(auth.privileges.addComments), canAddComments: auth.hasPrivilege(auth.privileges.addComments),
canEditOwnComments: auth.hasPrivilege(auth.privileges.editOwnComments), canEditOwnComments: auth.hasPrivilege(auth.privileges.editOwnComments),
canEditAllComments: auth.hasPrivilege(auth.privileges.editAllComments), canEditAllComments: auth.hasPrivilege(auth.privileges.editAllComments),
canDeleteOwnComments: auth.hasPrivilege(auth.privileges.deleteOwnComments), canDeleteOwnComments: auth.hasPrivilege(auth.privileges.deleteOwnComments),
canDeleteAllComments: auth.hasPrivilege(auth.privileges.deleteAllComments), canDeleteAllComments: auth.hasPrivilege(auth.privileges.deleteAllComments),
canViewUsers: auth.hasPrivilege(auth.privileges.viewUsers), canViewUsers: auth.hasPrivilege(auth.privileges.viewUsers),
canViewPosts: auth.hasPrivilege(auth.privileges.viewPosts), canViewPosts: auth.hasPrivilege(auth.privileges.viewPosts),
}; };
promise.wait( promise.wait(
util.promiseTemplate('comment-list'), util.promiseTemplate('comment-list'),
util.promiseTemplate('comment-list-item'), util.promiseTemplate('comment-list-item'),
util.promiseTemplate('comment-form')) util.promiseTemplate('comment-form'))
.then(function( .then(function(
commentListTemplate, commentListTemplate,
commentListItemTemplate, commentListItemTemplate,
commentFormTemplate) commentFormTemplate)
{ {
templates.commentList = commentListTemplate; templates.commentList = commentListTemplate;
templates.commentListItem = commentListItemTemplate; templates.commentListItem = commentListItemTemplate;
templates.commentForm = commentFormTemplate; templates.commentForm = commentFormTemplate;
render(); render();
loaded(); loaded();
if (comments.length === 0) { if (comments.length === 0) {
promise.wait(api.get('/comments/' + params.post.id)) promise.wait(api.get('/comments/' + params.post.id))
.then(function(response) { .then(function(response) {
comments = response.json.data; comments = response.json.data;
render(); render();
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
}); });
} }
}) })
.fail(function() { .fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function render() { function render() {
$el.html(templates.commentList( $el.html(templates.commentList(
_.extend( _.extend(
{ {
commentListItemTemplate: templates.commentListItem, commentListItemTemplate: templates.commentListItem,
commentFormTemplate: templates.commentForm, commentFormTemplate: templates.commentForm,
util: util, util: util,
comments: comments, comments: comments,
post: post, post: post,
}, },
privileges))); privileges)));
$el.find('.comment-add form button[type=submit]').click(function(e) { commentFormSubmitted(e, null); }); $el.find('.comment-add form button[type=submit]').click(function(e) { commentFormSubmitted(e, null); });
renderComments(comments); renderComments(comments);
} }
function renderComments(comments) { function renderComments(comments) {
var $target = $el.find('.comments'); var $target = $el.find('.comments');
var $targetList = $el.find('ul'); var $targetList = $el.find('ul');
if (comments.length > 0) { if (comments.length > 0) {
$target.show(); $target.show();
} else { } else {
$target.hide(); $target.hide();
} }
$targetList.empty(); $targetList.empty();
_.each(comments, function(comment) { _.each(comments, function(comment) {
renderComment($targetList, comment); renderComment($targetList, comment);
}); });
} }
function renderComment($targetList, comment) { function renderComment($targetList, comment) {
var $item = jQuery('<li>' + templates.commentListItem({ var $item = jQuery('<li>' + templates.commentListItem({
comment: comment, comment: comment,
util: util, util: util,
canVote: auth.isLoggedIn(), canVote: auth.isLoggedIn(),
canEditComment: auth.isLoggedIn(comment.user.name) ? privileges.canEditOwnComments : privileges.canEditAllComments, canEditComment: auth.isLoggedIn(comment.user.name) ? privileges.canEditOwnComments : privileges.canEditAllComments,
canDeleteComment: auth.isLoggedIn(comment.user.name) ? privileges.canDeleteOwnComments : privileges.canDeleteAllComments, canDeleteComment: auth.isLoggedIn(comment.user.name) ? privileges.canDeleteOwnComments : privileges.canDeleteAllComments,
canViewUsers: privileges.canViewUsers, canViewUsers: privileges.canViewUsers,
canViewPosts: privileges.canViewPosts, canViewPosts: privileges.canViewPosts,
}) + '</li>'); }) + '</li>');
util.loadImagesNicely($item.find('img')); util.loadImagesNicely($item.find('img'));
$targetList.append($item); $targetList.append($item);
$item.find('a.edit').click(function(e) { $item.find('a.edit').click(function(e) {
e.preventDefault(); e.preventDefault();
editCommentStart($item, comment); editCommentStart($item, comment);
}); });
$item.find('a.delete').click(function(e) { $item.find('a.delete').click(function(e) {
e.preventDefault(); e.preventDefault();
deleteComment(comment); deleteComment(comment);
}); });
$item.find('a.score-up').click(function(e) { $item.find('a.score-up').click(function(e) {
e.preventDefault(); e.preventDefault();
score(comment, jQuery(this).hasClass('active') ? 0 : 1); score(comment, jQuery(this).hasClass('active') ? 0 : 1);
}); });
$item.find('a.score-down').click(function(e) { $item.find('a.score-down').click(function(e) {
e.preventDefault(); e.preventDefault();
score(comment, jQuery(this).hasClass('active') ? 0 : -1); score(comment, jQuery(this).hasClass('active') ? 0 : -1);
}); });
} }
function commentFormSubmitted(e, comment) { function commentFormSubmitted(e, comment) {
e.preventDefault(); e.preventDefault();
var $button = jQuery(e.target); var $button = jQuery(e.target);
var $form = $button.parents('form'); var $form = $button.parents('form');
var sender = $button.val(); var sender = $button.val();
if (sender === 'preview') { if (sender === 'preview') {
previewComment($form); previewComment($form);
} else { } else {
submitComment($form, comment); submitComment($form, comment);
} }
} }
function previewComment($form) { function previewComment($form) {
var $preview = $form.find('.preview'); var $preview = $form.find('.preview');
$preview.slideUp('fast', function() { $preview.slideUp('fast', function() {
$preview.html(util.formatMarkdown($form.find('textarea').val())); $preview.html(util.formatMarkdown($form.find('textarea').val()));
$preview.slideDown('fast'); $preview.slideDown('fast');
}); });
} }
function updateComment(comment) { function updateComment(comment) {
comments = _.map(comments, function(c) { return c.id === comment.id ? comment : c; }); comments = _.map(comments, function(c) { return c.id === comment.id ? comment : c; });
render(); render();
} }
function addComment(comment) { function addComment(comment) {
comments.push(comment); comments.push(comment);
render(); render();
} }
function submitComment($form, commentToEdit) { function submitComment($form, commentToEdit) {
$form.find('.preview').slideUp(); $form.find('.preview').slideUp();
var $textarea = $form.find('textarea'); var $textarea = $form.find('textarea');
var data = {text: $textarea.val()}; var data = {text: $textarea.val()};
var p; var p;
if (commentToEdit) { if (commentToEdit) {
p = promise.wait(api.put('/comments/' + commentToEdit.id, data)); p = promise.wait(api.put('/comments/' + commentToEdit.id, data));
} else { } else {
p = promise.wait(api.post('/comments/' + post.id, data)); p = promise.wait(api.post('/comments/' + post.id, data));
} }
p.then(function(response) { p.then(function(response) {
$textarea.val(''); $textarea.val('');
var comment = response.json; var comment = response.json;
if (commentToEdit) { if (commentToEdit) {
$form.slideUp(function() { $form.slideUp(function() {
$form.remove(); $form.remove();
}); });
updateComment(comment); updateComment(comment);
} else { } else {
addComment(comment); addComment(comment);
} }
}).fail(showGenericError); }).fail(showGenericError);
} }
function editCommentStart($item, comment) { function editCommentStart($item, comment) {
if ($item.find('.comment-form').length > 0) { if ($item.find('.comment-form').length > 0) {
return; return;
} }
var $form = jQuery(templates.commentForm({title: 'Edit comment', text: comment.text})); var $form = jQuery(templates.commentForm({title: 'Edit comment', text: comment.text}));
$item.find('.body').append($form); $item.find('.body').append($form);
$item.find('form button[type=submit]').click(function(e) { commentFormSubmitted(e, comment); }); $item.find('form button[type=submit]').click(function(e) { commentFormSubmitted(e, comment); });
} }
function deleteComment(comment) { function deleteComment(comment) {
if (!window.confirm('Are you sure you want to delete this comment?')) { if (!window.confirm('Are you sure you want to delete this comment?')) {
return; return;
} }
promise.wait(api.delete('/comments/' + comment.id)) promise.wait(api.delete('/comments/' + comment.id))
.then(function(response) { .then(function(response) {
comments = _.filter(comments, function(c) { return c.id !== comment.id; }); comments = _.filter(comments, function(c) { return c.id !== comment.id; });
renderComments(comments); renderComments(comments);
}).fail(showGenericError); }).fail(showGenericError);
} }
function score(comment, scoreValue) { function score(comment, scoreValue) {
promise.wait(api.post('/comments/' + comment.id + '/score', {score: scoreValue})) promise.wait(api.post('/comments/' + comment.id + '/score', {score: scoreValue}))
.then(function(response) { .then(function(response) {
comment.score = parseInt(response.json.score); comment.score = parseInt(response.json.score);
comment.ownScore = parseInt(response.json.ownScore); comment.ownScore = parseInt(response.json.ownScore);
updateComment(comment); updateComment(comment);
}).fail(showGenericError); }).fail(showGenericError);
} }
function showGenericError(response) { function showGenericError(response) {
window.alert(response.json && response.json.error || response); window.alert(response.json && response.json.error || response);
} }
return { return {
init: init, init: init,
render: render, render: render,
}; };
}; };

View file

@ -2,103 +2,103 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.GlobalCommentListPresenter = function( App.Presenters.GlobalCommentListPresenter = function(
_, _,
jQuery, jQuery,
util, util,
auth, auth,
promise, promise,
pagerPresenter, pagerPresenter,
topNavigationPresenter) { topNavigationPresenter) {
var $el; var $el;
var privileges; var privileges;
var templates = {}; var templates = {};
function init(params, loaded) { function init(params, loaded) {
$el = jQuery('#content'); $el = jQuery('#content');
topNavigationPresenter.select('comments'); topNavigationPresenter.select('comments');
privileges = { privileges = {
canViewPosts: auth.hasPrivilege(auth.privileges.viewPosts), canViewPosts: auth.hasPrivilege(auth.privileges.viewPosts),
}; };
promise.wait( promise.wait(
util.promiseTemplate('global-comment-list'), util.promiseTemplate('global-comment-list'),
util.promiseTemplate('global-comment-list-item'), util.promiseTemplate('global-comment-list-item'),
util.promiseTemplate('post-list-item')) util.promiseTemplate('post-list-item'))
.then(function(listTemplate, listItemTemplate, postTemplate) { .then(function(listTemplate, listItemTemplate, postTemplate) {
templates.list = listTemplate; templates.list = listTemplate;
templates.listItem = listItemTemplate; templates.listItem = listItemTemplate;
templates.post = postTemplate; templates.post = postTemplate;
render(); render();
loaded(); loaded();
pagerPresenter.init({ pagerPresenter.init({
baseUri: '#/comments', baseUri: '#/comments',
backendUri: '/comments', backendUri: '/comments',
$target: $el.find('.pagination-target'), $target: $el.find('.pagination-target'),
updateCallback: function($page, data) { updateCallback: function($page, data) {
renderComments($page, data.entities); renderComments($page, data.entities);
}, },
}, },
function() { function() {
reinit(params, function() {}); reinit(params, function() {});
}); });
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function reinit(params, loaded) { function reinit(params, loaded) {
pagerPresenter.reinit({query: params.query || {}}); pagerPresenter.reinit({query: params.query || {}});
loaded(); loaded();
} }
function deinit() { function deinit() {
pagerPresenter.deinit(); pagerPresenter.deinit();
} }
function render() { function render() {
$el.html(templates.list()); $el.html(templates.list());
} }
function renderComments($page, data) { function renderComments($page, data) {
var $target = $page.find('.posts'); var $target = $page.find('.posts');
_.each(data, function(data) { _.each(data, function(data) {
var post = data.post; var post = data.post;
var comments = data.comments; var comments = data.comments;
var $post = jQuery('<li>' + templates.listItem({ var $post = jQuery('<li>' + templates.listItem({
util: util, util: util,
post: post, post: post,
postTemplate: templates.post, postTemplate: templates.post,
canViewPosts: privileges.canViewPosts, canViewPosts: privileges.canViewPosts,
}) + '</li>'); }) + '</li>');
util.loadImagesNicely($post.find('img')); util.loadImagesNicely($post.find('img'));
var presenter = App.DI.get('commentListPresenter'); var presenter = App.DI.get('commentListPresenter');
presenter.init({ presenter.init({
post: post, post: post,
comments: comments, comments: comments,
$target: $post.find('.post-comments-target'), $target: $post.find('.post-comments-target'),
}, function() { }, function() {
presenter.render(); presenter.render();
}); });
$target.append($post); $target.append($post);
}); });
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
deinit: deinit, deinit: deinit,
render: render, render: render,
}; };
}; };

View file

@ -2,48 +2,48 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.HelpPresenter = function( App.Presenters.HelpPresenter = function(
jQuery, jQuery,
promise, promise,
util, util,
topNavigationPresenter) { topNavigationPresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var templates = {}; var templates = {};
var activeTab; var activeTab;
function init(params, loaded) { function init(params, loaded) {
topNavigationPresenter.select('help'); topNavigationPresenter.select('help');
topNavigationPresenter.changeTitle('Help'); topNavigationPresenter.changeTitle('Help');
promise.wait(util.promiseTemplate('help')) promise.wait(util.promiseTemplate('help'))
.then(function(template) { .then(function(template) {
templates.help = template; templates.help = template;
reinit(params, loaded); reinit(params, loaded);
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function reinit(params, loaded) { function reinit(params, loaded) {
activeTab = params.tab || 'about'; activeTab = params.tab || 'about';
render(); render();
loaded(); loaded();
} }
function render() { function render() {
$el.html(templates.help({title: topNavigationPresenter.getBaseTitle() })); $el.html(templates.help({title: topNavigationPresenter.getBaseTitle() }));
$el.find('.big-button').removeClass('active'); $el.find('.big-button').removeClass('active');
$el.find('.big-button[href*="' + activeTab + '"]').addClass('active'); $el.find('.big-button[href*="' + activeTab + '"]').addClass('active');
$el.find('div[data-tab]').hide(); $el.find('div[data-tab]').hide();
$el.find('div[data-tab*="' + activeTab + '"]').show(); $el.find('div[data-tab*="' + activeTab + '"]').show();
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
render: render, render: render,
}; };
}; };

View file

@ -2,76 +2,76 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.HistoryPresenter = function( App.Presenters.HistoryPresenter = function(
_, _,
jQuery, jQuery,
util, util,
promise, promise,
auth, auth,
pagerPresenter, pagerPresenter,
topNavigationPresenter) { topNavigationPresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var templates = {}; var templates = {};
var params; var params;
function init(params, loaded) { function init(params, loaded) {
topNavigationPresenter.changeTitle('History'); topNavigationPresenter.changeTitle('History');
promise.wait( promise.wait(
util.promiseTemplate('global-history'), util.promiseTemplate('global-history'),
util.promiseTemplate('history')) util.promiseTemplate('history'))
.then(function(historyWrapperTemplate, historyTemplate) { .then(function(historyWrapperTemplate, historyTemplate) {
templates.historyWrapper = historyWrapperTemplate; templates.historyWrapper = historyWrapperTemplate;
templates.history = historyTemplate; templates.history = historyTemplate;
render(); render();
loaded(); loaded();
pagerPresenter.init({ pagerPresenter.init({
baseUri: '#/history', baseUri: '#/history',
backendUri: '/history', backendUri: '/history',
$target: $el.find('.pagination-target'), $target: $el.find('.pagination-target'),
updateCallback: function($page, data) { updateCallback: function($page, data) {
renderHistory($page, data.entities); renderHistory($page, data.entities);
}, },
}, },
function() { function() {
reinit(params, function() {}); reinit(params, function() {});
}); });
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function reinit(_params, loaded) { function reinit(_params, loaded) {
params = _params; params = _params;
params.query = params.query || {}; params.query = params.query || {};
pagerPresenter.reinit({query: params.query}); pagerPresenter.reinit({query: params.query});
loaded(); loaded();
} }
function deinit() { function deinit() {
pagerPresenter.deinit(); pagerPresenter.deinit();
} }
function render() { function render() {
$el.html(templates.historyWrapper()); $el.html(templates.historyWrapper());
} }
function renderHistory($page, historyItems) { function renderHistory($page, historyItems) {
$page.append(templates.history({ $page.append(templates.history({
util: util, util: util,
history: historyItems})); history: historyItems}));
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
deinit: deinit, deinit: deinit,
render: render, render: render,
}; };
}; };

View file

@ -2,90 +2,90 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.HomePresenter = function( App.Presenters.HomePresenter = function(
jQuery, jQuery,
util, util,
promise, promise,
api, api,
auth, auth,
presenterManager, presenterManager,
postContentPresenter, postContentPresenter,
topNavigationPresenter, topNavigationPresenter,
messagePresenter) { messagePresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var templates = {}; var templates = {};
var globals; var globals;
var post; var post;
var user; var user;
function init(params, loaded) { function init(params, loaded) {
topNavigationPresenter.select('home'); topNavigationPresenter.select('home');
topNavigationPresenter.changeTitle('Home'); topNavigationPresenter.changeTitle('Home');
promise.wait( promise.wait(
util.promiseTemplate('home'), util.promiseTemplate('home'),
api.get('/globals'), api.get('/globals'),
api.get('/posts/featured')) api.get('/posts/featured'))
.then(function( .then(function(
homeTemplate, homeTemplate,
globalsResponse, globalsResponse,
featuredPostResponse) { featuredPostResponse) {
templates.home = homeTemplate; templates.home = homeTemplate;
globals = globalsResponse.json; globals = globalsResponse.json;
post = featuredPostResponse.json.post; post = featuredPostResponse.json.post;
user = featuredPostResponse.json.user; user = featuredPostResponse.json.user;
render(); render();
loaded(); loaded();
if ($el.find('#post-content-target').length > 0) { if ($el.find('#post-content-target').length > 0) {
presenterManager.initPresenters([ presenterManager.initPresenters([
[postContentPresenter, {post: post, $target: $el.find('#post-content-target')}]], [postContentPresenter, {post: post, $target: $el.find('#post-content-target')}]],
function() { function() {
var $wrapper = $el.find('.object-wrapper'); var $wrapper = $el.find('.object-wrapper');
$wrapper.css({ $wrapper.css({
maxWidth: $wrapper.attr('data-width') + 'px', maxWidth: $wrapper.attr('data-width') + 'px',
width: 'auto', width: 'auto',
margin: '0 auto'}); margin: '0 auto'});
postContentPresenter.updatePostNotesSize(); postContentPresenter.updatePostNotesSize();
}); });
} }
}).fail(function(response) { }).fail(function(response) {
messagePresenter.showError($el, response.json && response.json.error || response); messagePresenter.showError($el, response.json && response.json.error || response);
loaded(); loaded();
}); });
} }
function render() { function render() {
$el.html(templates.home({ $el.html(templates.home({
post: post, post: post,
user: user, user: user,
globals: globals, globals: globals,
title: topNavigationPresenter.getBaseTitle(), title: topNavigationPresenter.getBaseTitle(),
canViewUsers: auth.hasPrivilege(auth.privileges.viewUsers), canViewUsers: auth.hasPrivilege(auth.privileges.viewUsers),
canViewPosts: auth.hasPrivilege(auth.privileges.viewPosts), canViewPosts: auth.hasPrivilege(auth.privileges.viewPosts),
util: util, util: util,
version: jQuery('head').attr('data-version'), version: jQuery('head').attr('data-version'),
buildTime: jQuery('head').attr('data-build-time'), buildTime: jQuery('head').attr('data-build-time'),
})); }));
} }
return { return {
init: init, init: init,
render: render, render: render,
}; };
}; };
App.DI.register('homePresenter', [ App.DI.register('homePresenter', [
'jQuery', 'jQuery',
'util', 'util',
'promise', 'promise',
'api', 'api',
'auth', 'auth',
'presenterManager', 'presenterManager',
'postContentPresenter', 'postContentPresenter',
'topNavigationPresenter', 'topNavigationPresenter',
'messagePresenter'], 'messagePresenter'],
App.Presenters.HomePresenter); App.Presenters.HomePresenter);

View file

@ -2,46 +2,46 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.HttpErrorPresenter = function( App.Presenters.HttpErrorPresenter = function(
jQuery, jQuery,
promise, promise,
util, util,
topNavigationPresenter) { topNavigationPresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var templates = {}; var templates = {};
function init(params, loaded) { function init(params, loaded) {
topNavigationPresenter.changeTitle('Error ' + params.error); topNavigationPresenter.changeTitle('Error ' + params.error);
if (params.error === 404) { if (params.error === 404) {
promise.wait(util.promiseTemplate('404')) promise.wait(util.promiseTemplate('404'))
.then(function(template) { .then(function(template) {
templates.errorPage = template; templates.errorPage = template;
reinit(params, loaded); reinit(params, loaded);
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} else { } else {
console.log('Not supported.'); console.log('Not supported.');
loaded(); loaded();
} }
} }
function reinit(params, loaded) { function reinit(params, loaded) {
render(); render();
loaded(); loaded();
} }
function render() { function render() {
$el.html(templates.errorPage()); $el.html(templates.errorPage());
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
render: render, render: render,
}; };
}; };

View file

@ -2,84 +2,84 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.LoginPresenter = function( App.Presenters.LoginPresenter = function(
jQuery, jQuery,
util, util,
promise, promise,
router, router,
auth, auth,
topNavigationPresenter, topNavigationPresenter,
messagePresenter) { messagePresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var $messages; var $messages;
var templates = {}; var templates = {};
var previousLocation; var previousLocation;
function init(params, loaded) { function init(params, loaded) {
topNavigationPresenter.select('login'); topNavigationPresenter.select('login');
topNavigationPresenter.changeTitle('Login'); topNavigationPresenter.changeTitle('Login');
previousLocation = params.previousLocation; previousLocation = params.previousLocation;
promise.wait(util.promiseTemplate('login-form')) promise.wait(util.promiseTemplate('login-form'))
.then(function(template) { .then(function(template) {
templates.login = template; templates.login = template;
if (auth.isLoggedIn()) { if (auth.isLoggedIn()) {
finishLogin(); finishLogin();
} else { } else {
render(); render();
$el.find('input:eq(0)').focus(); $el.find('input:eq(0)').focus();
} }
loaded(); loaded();
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function render() { function render() {
$el.html(templates.login()); $el.html(templates.login());
$el.find('form').submit(loginFormSubmitted); $el.find('form').submit(loginFormSubmitted);
$messages = $el.find('.messages'); $messages = $el.find('.messages');
$messages.width($el.find('form').width()); $messages.width($el.find('form').width());
} }
function loginFormSubmitted(e) { function loginFormSubmitted(e) {
e.preventDefault(); e.preventDefault();
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
var userNameOrEmail = $el.find('[name=user]').val(); var userNameOrEmail = $el.find('[name=user]').val();
var password = $el.find('[name=password]').val(); var password = $el.find('[name=password]').val();
var remember = $el.find('[name=remember]').is(':checked'); var remember = $el.find('[name=remember]').is(':checked');
if (userNameOrEmail.length === 0) { if (userNameOrEmail.length === 0) {
messagePresenter.showError($messages, 'User name cannot be empty.'); messagePresenter.showError($messages, 'User name cannot be empty.');
return false; return false;
} }
if (password.length === 0) { if (password.length === 0) {
messagePresenter.showError($messages, 'Password cannot be empty.'); messagePresenter.showError($messages, 'Password cannot be empty.');
return false; return false;
} }
promise.wait(auth.loginFromCredentials(userNameOrEmail, password, remember)) promise.wait(auth.loginFromCredentials(userNameOrEmail, password, remember))
.then(function(response) { .then(function(response) {
finishLogin(); finishLogin();
}).fail(function(response) { }).fail(function(response) {
messagePresenter.showError($messages, response.json && response.json.error || response); messagePresenter.showError($messages, response.json && response.json.error || response);
}); });
} }
function finishLogin() { function finishLogin() {
if (previousLocation && !previousLocation.match(/logout|password-reset|activate|register/)) { if (previousLocation && !previousLocation.match(/logout|password-reset|activate|register/)) {
router.navigate(previousLocation); router.navigate(previousLocation);
} else { } else {
router.navigateToMainPage(); router.navigateToMainPage();
} }
} }
return { return {
init: init, init: init,
render: render, render: render,
}; };
}; };

View file

@ -2,38 +2,38 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.LogoutPresenter = function( App.Presenters.LogoutPresenter = function(
jQuery, jQuery,
promise, promise,
router, router,
auth, auth,
topNavigationPresenter, topNavigationPresenter,
messagePresenter) { messagePresenter) {
var $messages = jQuery('#content'); var $messages = jQuery('#content');
function init(params, loaded) { function init(params, loaded) {
topNavigationPresenter.select('logout'); topNavigationPresenter.select('logout');
topNavigationPresenter.changeTitle('Logout'); topNavigationPresenter.changeTitle('Logout');
promise.wait(auth.logout()) promise.wait(auth.logout())
.then(function() { .then(function() {
loaded(); loaded();
$messages.empty(); $messages.empty();
var $messageDiv = messagePresenter.showInfo($messages, 'Logged out. <a href="">Back to main page</a>'); var $messageDiv = messagePresenter.showInfo($messages, 'Logged out. <a href="">Back to main page</a>');
$messageDiv.find('a').click(mainPageLinkClicked); $messageDiv.find('a').click(mainPageLinkClicked);
}).fail(function(response) { }).fail(function(response) {
messagePresenter.showError(($messages, response.json && response.json.error || response) + '<br/>Reload the page to continue.'); messagePresenter.showError(($messages, response.json && response.json.error || response) + '<br/>Reload the page to continue.');
loaded(); loaded();
}); });
} }
function mainPageLinkClicked(e) { function mainPageLinkClicked(e) {
e.preventDefault(); e.preventDefault();
router.navigateToMainPage(); router.navigateToMainPage();
} }
return { return {
init: init init: init
}; };
}; };

View file

@ -3,51 +3,51 @@ App.Presenters = App.Presenters || {};
App.Presenters.MessagePresenter = function(_, jQuery) { App.Presenters.MessagePresenter = function(_, jQuery) {
var options = { var options = {
instant: false instant: false
}; };
function showInfo($el, message) { function showInfo($el, message) {
return showMessage($el, 'info', message); return showMessage($el, 'info', message);
} }
function showError($el, message) { function showError($el, message) {
return showMessage($el, 'error', message); return showMessage($el, 'error', message);
} }
function hideMessages($el) { function hideMessages($el) {
var $messages = $el.children('.message'); var $messages = $el.children('.message');
if (options.instant) { if (options.instant) {
$messages.each(function() { $messages.each(function() {
jQuery(this).slideUp('fast', function() { jQuery(this).slideUp('fast', function() {
jQuery(this).remove(); jQuery(this).remove();
}); });
}); });
} else { } else {
$messages.remove(); $messages.remove();
} }
} }
function showMessage($el, className, message) { function showMessage($el, className, message) {
var $messageDiv = jQuery('<div>'); var $messageDiv = jQuery('<div>');
$messageDiv.addClass('message'); $messageDiv.addClass('message');
$messageDiv.addClass(className); $messageDiv.addClass(className);
$messageDiv.html(message); $messageDiv.html(message);
if (!options.instant) { if (!options.instant) {
$messageDiv.hide(); $messageDiv.hide();
} }
$el.append($messageDiv); $el.append($messageDiv);
if (!options.instant) { if (!options.instant) {
$messageDiv.slideDown('fast'); $messageDiv.slideDown('fast');
} }
return $messageDiv; return $messageDiv;
} }
return _.extend(options, { return _.extend(options, {
showInfo: showInfo, showInfo: showInfo,
showError: showError, showError: showError,
hideMessages: hideMessages, hideMessages: hideMessages,
}); });
}; };

View file

@ -2,267 +2,267 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.PagerPresenter = function( App.Presenters.PagerPresenter = function(
_, _,
jQuery, jQuery,
util, util,
promise, promise,
keyboard, keyboard,
router, router,
pager, pager,
messagePresenter, messagePresenter,
browsingSettings, browsingSettings,
progress) { progress) {
var $target; var $target;
var $pageList; var $pageList;
var $messages; var $messages;
var targetContent; var targetContent;
var endlessScroll = browsingSettings.getSettings().endlessScroll; var endlessScroll = browsingSettings.getSettings().endlessScroll;
var scrollInterval; var scrollInterval;
var templates = {}; var templates = {};
var forceClear = !endlessScroll; var forceClear = !endlessScroll;
var baseUri; var baseUri;
var updateCallback; var updateCallback;
function init(params, loaded) { function init(params, loaded) {
baseUri = params.baseUri; baseUri = params.baseUri;
updateCallback = params.updateCallback; updateCallback = params.updateCallback;
messagePresenter.instant = true; messagePresenter.instant = true;
$target = params.$target; $target = params.$target;
targetContent = jQuery(params.$target).html(); targetContent = jQuery(params.$target).html();
pager.init({url: params.backendUri}); pager.init({url: params.backendUri});
setQuery(params.query); setQuery(params.query);
if (forceClear) { if (forceClear) {
clearContent(); clearContent();
} }
promise.wait(util.promiseTemplate('pager')) promise.wait(util.promiseTemplate('pager'))
.then(function(template) { .then(function(template) {
templates.pager = template; templates.pager = template;
render(); render();
loaded(); loaded();
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function reinit(params, loaded) { function reinit(params, loaded) {
setQuery(params.query); setQuery(params.query);
if (forceClear) { if (forceClear) {
clearContent(); clearContent();
} }
promise.wait(retrieve()) promise.wait(retrieve())
.then(loaded) .then(loaded)
.fail(loaded); .fail(loaded);
if (!endlessScroll) { if (!endlessScroll) {
keyboard.keydown('a', navigateToPrevPage); keyboard.keydown('a', navigateToPrevPage);
keyboard.keydown('d', navigateToNextPage); keyboard.keydown('d', navigateToNextPage);
} }
} }
function deinit() { function deinit() {
detachNextPageLoader(); detachNextPageLoader();
} }
function getUrl(options) { function getUrl(options) {
return util.appendComplexRouteParam( return util.appendComplexRouteParam(
baseUri, baseUri,
util.simplifySearchQuery( util.simplifySearchQuery(
_.extend( _.extend(
{}, {},
pager.getSearchParams(), pager.getSearchParams(),
{page: pager.getPage()}, {page: pager.getPage()},
options))); options)));
} }
function syncUrl(options) { function syncUrl(options) {
router.navigate(getUrl(options)); router.navigate(getUrl(options));
} }
function syncUrlInplace(options) { function syncUrlInplace(options) {
router.navigateInplace(getUrl(options)); router.navigateInplace(getUrl(options));
} }
function retrieve() { function retrieve() {
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
progress.start(); progress.start();
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
hidePageList(); hidePageList();
promise.wait(pager.retrieve()) promise.wait(pager.retrieve())
.then(function(response) { .then(function(response) {
progress.done(); progress.done();
if (forceClear) { if (forceClear) {
clearContent(); clearContent();
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
var $page = jQuery('<div class="page">'); var $page = jQuery('<div class="page">');
if (endlessScroll && pager.getTotalPages() > 1) { if (endlessScroll && pager.getTotalPages() > 1) {
$page.append('<p>Page ' + pager.getPage() + ' of ' + pager.getTotalPages() + '</p>'); $page.append('<p>Page ' + pager.getPage() + ' of ' + pager.getTotalPages() + '</p>');
} }
$page.append(targetContent); $page.append(targetContent);
$target.find('.pagination-content').append($page); $target.find('.pagination-content').append($page);
updateCallback($page, response); updateCallback($page, response);
refreshPageList(); refreshPageList();
if (!response.entities.length) { if (!response.entities.length) {
messagePresenter.showInfo($messages, 'No data to show'); messagePresenter.showInfo($messages, 'No data to show');
if (pager.getVisiblePages().length === 1) { if (pager.getVisiblePages().length === 1) {
hidePageList(); hidePageList();
} else { } else {
showPageList(); showPageList();
} }
} else { } else {
showPageList(); showPageList();
} }
if (pager.getPage() < response.totalPages) { if (pager.getPage() < response.totalPages) {
attachNextPageLoader(); attachNextPageLoader();
} }
resolve(); resolve();
}).fail(function(response) { }).fail(function(response) {
progress.done(); progress.done();
clearContent(); clearContent();
hidePageList(); hidePageList();
messagePresenter.showError($messages, response.json && response.json.error || response); messagePresenter.showError($messages, response.json && response.json.error || response);
reject(); reject();
}); });
}); });
} }
function clearContent() { function clearContent() {
detachNextPageLoader(); detachNextPageLoader();
$target.find('.pagination-content').empty(); $target.find('.pagination-content').empty();
} }
function attachNextPageLoader() { function attachNextPageLoader() {
if (!endlessScroll) { if (!endlessScroll) {
return; return;
} }
detachNextPageLoader(); detachNextPageLoader();
scrollInterval = window.setInterval(function() { scrollInterval = window.setInterval(function() {
var myScrollInterval = scrollInterval; var myScrollInterval = scrollInterval;
var baseLine = $target.offset().top + $target.innerHeight(); var baseLine = $target.offset().top + $target.innerHeight();
var scrollY = jQuery(window).scrollTop() + jQuery(window).height(); var scrollY = jQuery(window).scrollTop() + jQuery(window).height();
if (scrollY > baseLine) { if (scrollY > baseLine) {
syncUrlInplace({page: pager.getPage() + 1}); syncUrlInplace({page: pager.getPage() + 1});
window.clearInterval(myScrollInterval); window.clearInterval(myScrollInterval);
} }
}, 100); }, 100);
} }
function detachNextPageLoader() { function detachNextPageLoader() {
window.clearInterval(scrollInterval); window.clearInterval(scrollInterval);
} }
function showPageList() { function showPageList() {
$pageList.show(); $pageList.show();
} }
function hidePageList() { function hidePageList() {
$pageList.hide(); $pageList.hide();
} }
function navigateToPrevPage() { function navigateToPrevPage() {
console.log('!'); console.log('!');
if (pager.prevPage()) { if (pager.prevPage()) {
syncUrl({page: pager.getPage()}); syncUrl({page: pager.getPage()});
} }
} }
function navigateToNextPage() { function navigateToNextPage() {
if (pager.nextPage()) { if (pager.nextPage()) {
syncUrl({page: pager.getPage()}); syncUrl({page: pager.getPage()});
} }
} }
function refreshPageList() { function refreshPageList() {
var $lastItem = $pageList.find('li:last-child'); var $lastItem = $pageList.find('li:last-child');
var currentPage = pager.getPage(); var currentPage = pager.getPage();
var pages = pager.getVisiblePages(); var pages = pager.getVisiblePages();
$pageList.find('li.page').remove(); $pageList.find('li.page').remove();
var lastPage = 0; var lastPage = 0;
_.each(pages, function(page) { _.each(pages, function(page) {
if (page - lastPage > 1) { if (page - lastPage > 1) {
jQuery('<li class="page ellipsis"><a>&hellip;</a></li>').insertBefore($lastItem); jQuery('<li class="page ellipsis"><a>&hellip;</a></li>').insertBefore($lastItem);
} }
lastPage = page; lastPage = page;
var $a = jQuery('<a href="#"/>'); var $a = jQuery('<a href="#"/>');
$a.click(function(e) { $a.click(function(e) {
e.preventDefault(); e.preventDefault();
syncUrl({page: page}); syncUrl({page: page});
}); });
$a.addClass('big-button'); $a.addClass('big-button');
$a.text(page); $a.text(page);
if (page === currentPage) { if (page === currentPage) {
$a.addClass('active'); $a.addClass('active');
} }
jQuery('<li class="page"/>').append($a).insertBefore($lastItem); jQuery('<li class="page"/>').append($a).insertBefore($lastItem);
}); });
$pageList.find('li.next a').unbind('click').bind('click', function(e) { $pageList.find('li.next a').unbind('click').bind('click', function(e) {
e.preventDefault(); e.preventDefault();
navigateToNextPage(); navigateToNextPage();
}); });
$pageList.find('li.prev a').unbind('click').bind('click', function(e) { $pageList.find('li.prev a').unbind('click').bind('click', function(e) {
e.preventDefault(); e.preventDefault();
navigateToPrevPage(); navigateToPrevPage();
}); });
} }
function render() { function render() {
$target.html(templates.pager()); $target.html(templates.pager());
$messages = $target.find('.pagination-content'); $messages = $target.find('.pagination-content');
$pageList = $target.find('.page-list'); $pageList = $target.find('.page-list');
if (endlessScroll) { if (endlessScroll) {
$pageList.remove(); $pageList.remove();
} else { } else {
refreshPageList(); refreshPageList();
} }
} }
function setQuery(query) { function setQuery(query) {
if (!query) { if (!query) {
return; return;
} }
query.page = parseInt(query.page) || 1; query.page = parseInt(query.page) || 1;
var page = query.page; var page = query.page;
query = _.extend({}, query); query = _.extend({}, query);
delete query.page; delete query.page;
forceClear = forceClear =
query.query !== pager.getSearchParams().query || query.query !== pager.getSearchParams().query ||
query.order !== pager.getSearchParams().order || query.order !== pager.getSearchParams().order ||
parseInt(page) !== pager.getPage() + 1 || parseInt(page) !== pager.getPage() + 1 ||
!endlessScroll; !endlessScroll;
pager.setSearchParams(query); pager.setSearchParams(query);
pager.setPage(page); pager.setPage(page);
} }
function setQueryAndSyncUrl(query) { function setQueryAndSyncUrl(query) {
setQuery(query); setQuery(query);
syncUrl(); syncUrl();
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
deinit: deinit, deinit: deinit,
syncUrl: syncUrl, syncUrl: syncUrl,
setQuery: setQueryAndSyncUrl, setQuery: setQueryAndSyncUrl,
}; };
}; };

View file

@ -2,76 +2,76 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.PostContentPresenter = function( App.Presenters.PostContentPresenter = function(
jQuery, jQuery,
util, util,
promise, promise,
presenterManager, presenterManager,
postNotesPresenter) { postNotesPresenter) {
var post; var post;
var templates = {}; var templates = {};
var $target; var $target;
function init(params, loaded) { function init(params, loaded) {
$target = params.$target; $target = params.$target;
post = params.post; post = params.post;
promise.wait(util.promiseTemplate('post-content')) promise.wait(util.promiseTemplate('post-content'))
.then(function(postContentTemplate) { .then(function(postContentTemplate) {
templates.postContent = postContentTemplate; templates.postContent = postContentTemplate;
render(); render();
loaded(); loaded();
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function render() { function render() {
$target.html(templates.postContent({post: post})); $target.html(templates.postContent({post: post}));
if (post.contentType === 'image') { if (post.contentType === 'image') {
loadPostNotes(); loadPostNotes();
updatePostNotesSize(); updatePostNotesSize();
} }
jQuery(window).resize(updatePostNotesSize); jQuery(window).resize(updatePostNotesSize);
} }
function loadPostNotes() { function loadPostNotes() {
presenterManager.initPresenters([ presenterManager.initPresenters([
[postNotesPresenter, {post: post, notes: post.notes, $target: $target.find('.post-notes-target')}]], [postNotesPresenter, {post: post, notes: post.notes, $target: $target.find('.post-notes-target')}]],
function() {}); function() {});
} }
function updatePostNotesSize() { function updatePostNotesSize() {
var $postNotes = $target.find('.post-notes-target'); var $postNotes = $target.find('.post-notes-target');
var $objectWrapper = $target.find('.object-wrapper'); var $objectWrapper = $target.find('.object-wrapper');
$postNotes.css({ $postNotes.css({
width: $objectWrapper.outerWidth() + 'px', width: $objectWrapper.outerWidth() + 'px',
height: $objectWrapper.outerHeight() + 'px', height: $objectWrapper.outerHeight() + 'px',
left: ($objectWrapper.offset().left - $objectWrapper.parent().offset().left) + 'px', left: ($objectWrapper.offset().left - $objectWrapper.parent().offset().left) + 'px',
top: ($objectWrapper.offset().top - $objectWrapper.parent().offset().top) + 'px', top: ($objectWrapper.offset().top - $objectWrapper.parent().offset().top) + 'px',
}); });
} }
function addNewPostNote() { function addNewPostNote() {
postNotesPresenter.addNewPostNote(); postNotesPresenter.addNewPostNote();
} }
return { return {
init: init, init: init,
render: render, render: render,
addNewPostNote: addNewPostNote, addNewPostNote: addNewPostNote,
updatePostNotesSize: updatePostNotesSize, updatePostNotesSize: updatePostNotesSize,
}; };
}; };
App.DI.register('postContentPresenter', [ App.DI.register('postContentPresenter', [
'jQuery', 'jQuery',
'util', 'util',
'promise', 'promise',
'presenterManager', 'presenterManager',
'postNotesPresenter'], 'postNotesPresenter'],
App.Presenters.PostContentPresenter); App.Presenters.PostContentPresenter);

View file

@ -2,154 +2,154 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.PostEditPresenter = function( App.Presenters.PostEditPresenter = function(
jQuery, jQuery,
util, util,
promise, promise,
api, api,
auth, auth,
tagList) { tagList) {
var $target; var $target;
var post; var post;
var updateCallback; var updateCallback;
var privileges = {}; var privileges = {};
var templates = {}; var templates = {};
var tagInput; var tagInput;
var postContentFileDropper; var postContentFileDropper;
var postThumbnailFileDropper; var postThumbnailFileDropper;
var postContent; var postContent;
var postThumbnail; var postThumbnail;
privileges.canChangeSafety = auth.hasPrivilege(auth.privileges.changePostSafety); privileges.canChangeSafety = auth.hasPrivilege(auth.privileges.changePostSafety);
privileges.canChangeSource = auth.hasPrivilege(auth.privileges.changePostSource); privileges.canChangeSource = auth.hasPrivilege(auth.privileges.changePostSource);
privileges.canChangeTags = auth.hasPrivilege(auth.privileges.changePostTags); privileges.canChangeTags = auth.hasPrivilege(auth.privileges.changePostTags);
privileges.canChangeContent = auth.hasPrivilege(auth.privileges.changePostContent); privileges.canChangeContent = auth.hasPrivilege(auth.privileges.changePostContent);
privileges.canChangeThumbnail = auth.hasPrivilege(auth.privileges.changePostThumbnail); privileges.canChangeThumbnail = auth.hasPrivilege(auth.privileges.changePostThumbnail);
privileges.canChangeRelations = auth.hasPrivilege(auth.privileges.changePostRelations); privileges.canChangeRelations = auth.hasPrivilege(auth.privileges.changePostRelations);
privileges.canChangeFlags = auth.hasPrivilege(auth.privileges.changePostFlags); privileges.canChangeFlags = auth.hasPrivilege(auth.privileges.changePostFlags);
function init(params, loaded) { function init(params, loaded) {
post = params.post; post = params.post;
updateCallback = params.updateCallback; updateCallback = params.updateCallback;
$target = params.$target; $target = params.$target;
promise.wait(util.promiseTemplate('post-edit')) promise.wait(util.promiseTemplate('post-edit'))
.then(function(postEditTemplate) { .then(function(postEditTemplate) {
templates.postEdit = postEditTemplate; templates.postEdit = postEditTemplate;
render(); render();
loaded(); loaded();
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function render() { function render() {
$target.html(templates.postEdit({post: post, privileges: privileges})); $target.html(templates.postEdit({post: post, privileges: privileges}));
postContentFileDropper = new App.Controls.FileDropper($target.find('form [name=content]')); postContentFileDropper = new App.Controls.FileDropper($target.find('form [name=content]'));
postContentFileDropper.onChange = postContentChanged; postContentFileDropper.onChange = postContentChanged;
postContentFileDropper.setNames = true; postContentFileDropper.setNames = true;
postThumbnailFileDropper = new App.Controls.FileDropper($target.find('form [name=thumbnail]')); postThumbnailFileDropper = new App.Controls.FileDropper($target.find('form [name=thumbnail]'));
postThumbnailFileDropper.onChange = postThumbnailChanged; postThumbnailFileDropper.onChange = postThumbnailChanged;
postThumbnailFileDropper.setNames = true; postThumbnailFileDropper.setNames = true;
if (privileges.canChangeTags) { if (privileges.canChangeTags) {
tagInput = new App.Controls.TagInput($target.find('form [name=tags]')); tagInput = new App.Controls.TagInput($target.find('form [name=tags]'));
tagInput.inputConfirmed = editPost; tagInput.inputConfirmed = editPost;
} }
$target.find('form').submit(editFormSubmitted); $target.find('form').submit(editFormSubmitted);
} }
function focus() { function focus() {
if (tagInput) { if (tagInput) {
tagInput.focus(); tagInput.focus();
} }
} }
function editFormSubmitted(e) { function editFormSubmitted(e) {
e.preventDefault(); e.preventDefault();
editPost(); editPost();
} }
function postContentChanged(files) { function postContentChanged(files) {
postContent = files[0]; postContent = files[0];
} }
function postThumbnailChanged(files) { function postThumbnailChanged(files) {
postThumbnail = files[0]; postThumbnail = files[0];
} }
function getPrivileges() { function getPrivileges() {
return privileges; return privileges;
} }
function editPost() { function editPost() {
var $form = $target.find('form'); var $form = $target.find('form');
var formData = new FormData(); var formData = new FormData();
formData.append('seenEditTime', post.lastEditTime); formData.append('seenEditTime', post.lastEditTime);
if (privileges.canChangeContent && postContent) { if (privileges.canChangeContent && postContent) {
formData.append('content', postContent); formData.append('content', postContent);
} }
if (privileges.canChangeThumbnail && postThumbnail) { if (privileges.canChangeThumbnail && postThumbnail) {
formData.append('thumbnail', postThumbnail); formData.append('thumbnail', postThumbnail);
} }
if (privileges.canChangeSource) { if (privileges.canChangeSource) {
formData.append('source', $form.find('[name=source]').val()); formData.append('source', $form.find('[name=source]').val());
} }
if (privileges.canChangeSafety) { if (privileges.canChangeSafety) {
formData.append('safety', $form.find('[name=safety]:checked').val()); formData.append('safety', $form.find('[name=safety]:checked').val());
} }
if (privileges.canChangeTags) { if (privileges.canChangeTags) {
formData.append('tags', tagInput.getTags().join(' ')); formData.append('tags', tagInput.getTags().join(' '));
} }
if (privileges.canChangeRelations) { if (privileges.canChangeRelations) {
formData.append('relations', $form.find('[name=relations]').val()); formData.append('relations', $form.find('[name=relations]').val());
} }
if (privileges.canChangeFlags) { if (privileges.canChangeFlags) {
if (post.contentType === 'video') { if (post.contentType === 'video') {
formData.append('loop', $form.find('[name=loop]').is(':checked') ? 1 : 0); formData.append('loop', $form.find('[name=loop]').is(':checked') ? 1 : 0);
} }
} }
if (post.tags.length === 0) { if (post.tags.length === 0) {
showEditError('No tags set.'); showEditError('No tags set.');
return; return;
} }
jQuery(document.activeElement).blur(); jQuery(document.activeElement).blur();
promise.wait(api.post('/posts/' + post.id, formData)) promise.wait(api.post('/posts/' + post.id, formData))
.then(function(response) { .then(function(response) {
tagList.refreshTags(); tagList.refreshTags();
if (typeof(updateCallback) !== 'undefined') { if (typeof(updateCallback) !== 'undefined') {
updateCallback(post = response.json); updateCallback(post = response.json);
} }
}).fail(function(response) { }).fail(function(response) {
showEditError(response); showEditError(response);
}); });
} }
function showEditError(response) { function showEditError(response) {
window.alert(response.json && response.json.error || response); window.alert(response.json && response.json.error || response);
} }
return { return {
init: init, init: init,
render: render, render: render,
getPrivileges: getPrivileges, getPrivileges: getPrivileges,
focus: focus, focus: focus,
}; };
}; };

View file

@ -2,264 +2,264 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.PostListPresenter = function( App.Presenters.PostListPresenter = function(
_, _,
jQuery, jQuery,
util, util,
promise, promise,
auth, auth,
api, api,
keyboard, keyboard,
pagerPresenter, pagerPresenter,
browsingSettings, browsingSettings,
topNavigationPresenter) { topNavigationPresenter) {
var KEY_RETURN = 13; var KEY_RETURN = 13;
var templates = {}; var templates = {};
var $el = jQuery('#content'); var $el = jQuery('#content');
var $searchInput; var $searchInput;
var privileges = {}; var privileges = {};
var params; var params;
function init(_params, loaded) { function init(_params, loaded) {
topNavigationPresenter.select('posts'); topNavigationPresenter.select('posts');
topNavigationPresenter.changeTitle('Posts'); topNavigationPresenter.changeTitle('Posts');
params = _params; params = _params;
params.query = params.query || {}; params.query = params.query || {};
privileges.canMassTag = auth.hasPrivilege(auth.privileges.massTag); privileges.canMassTag = auth.hasPrivilege(auth.privileges.massTag);
privileges.canViewPosts = auth.hasPrivilege(auth.privileges.viewPosts); privileges.canViewPosts = auth.hasPrivilege(auth.privileges.viewPosts);
promise.wait( promise.wait(
util.promiseTemplate('post-list'), util.promiseTemplate('post-list'),
util.promiseTemplate('post-list-item')) util.promiseTemplate('post-list-item'))
.then(function(listTemplate, listItemTemplate) { .then(function(listTemplate, listItemTemplate) {
templates.list = listTemplate; templates.list = listTemplate;
templates.listItem = listItemTemplate; templates.listItem = listItemTemplate;
render(); render();
loaded(); loaded();
pagerPresenter.init({ pagerPresenter.init({
baseUri: '#/posts', baseUri: '#/posts',
backendUri: '/posts', backendUri: '/posts',
$target: $el.find('.pagination-target'), $target: $el.find('.pagination-target'),
updateCallback: function($page, data) { updateCallback: function($page, data) {
renderPosts($page, data.entities); renderPosts($page, data.entities);
}, },
}, },
function() { function() {
reinit(params, function() {}); reinit(params, function() {});
}); });
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
jQuery(window).on('resize', windowResized); jQuery(window).on('resize', windowResized);
} }
function reinit(_params, loaded) { function reinit(_params, loaded) {
params = _params; params = _params;
params.query = params.query || {}; params.query = params.query || {};
pagerPresenter.reinit({query: params.query}); pagerPresenter.reinit({query: params.query});
loaded(); loaded();
softRender(); softRender();
} }
function deinit() { function deinit() {
pagerPresenter.deinit(); pagerPresenter.deinit();
jQuery(window).off('resize', windowResized); jQuery(window).off('resize', windowResized);
} }
function render() { function render() {
$el.html(templates.list({ $el.html(templates.list({
massTag: params.query.massTag, massTag: params.query.massTag,
privileges: privileges, privileges: privileges,
browsingSettings: browsingSettings.getSettings()})); browsingSettings: browsingSettings.getSettings()}));
$searchInput = $el.find('input[name=query]'); $searchInput = $el.find('input[name=query]');
App.Controls.AutoCompleteInput($searchInput); App.Controls.AutoCompleteInput($searchInput);
$searchInput.val(params.query.query); $searchInput.val(params.query.query);
$searchInput.keydown(searchInputKeyPressed); $searchInput.keydown(searchInputKeyPressed);
$el.find('form').submit(searchFormSubmitted); $el.find('form').submit(searchFormSubmitted);
$el.find('[name=mass-tag]').click(massTagButtonClicked); $el.find('[name=mass-tag]').click(massTagButtonClicked);
$el.find('.safety button').click(safetyButtonClicked); $el.find('.safety button').click(safetyButtonClicked);
keyboard.keyup('p', function() { keyboard.keyup('p', function() {
$el.find('.posts li a').eq(0).focus(); $el.find('.posts li a').eq(0).focus();
}); });
keyboard.keyup('q', function() { keyboard.keyup('q', function() {
$searchInput.eq(0).focus().select(); $searchInput.eq(0).focus().select();
}); });
windowResized(); windowResized();
} }
function safetyButtonClicked(e) { function safetyButtonClicked(e) {
e.preventDefault(); e.preventDefault();
var settings = browsingSettings.getSettings(); var settings = browsingSettings.getSettings();
var buttonClass = jQuery(e.currentTarget).attr('class').split(' ')[0]; var buttonClass = jQuery(e.currentTarget).attr('class').split(' ')[0];
var enabled = jQuery(e.currentTarget).hasClass('disabled'); var enabled = jQuery(e.currentTarget).hasClass('disabled');
jQuery(e.currentTarget).toggleClass('disabled'); jQuery(e.currentTarget).toggleClass('disabled');
if (buttonClass === 'safety-unsafe') { if (buttonClass === 'safety-unsafe') {
settings.listPosts.unsafe = enabled; settings.listPosts.unsafe = enabled;
} else if (buttonClass === 'safety-sketchy') { } else if (buttonClass === 'safety-sketchy') {
settings.listPosts.sketchy = enabled; settings.listPosts.sketchy = enabled;
} else if (buttonClass === 'safety-safe') { } else if (buttonClass === 'safety-safe') {
settings.listPosts.safe = enabled; settings.listPosts.safe = enabled;
} }
promise.wait(browsingSettings.setSettings(settings)) promise.wait(browsingSettings.setSettings(settings))
.then(function() { .then(function() {
reinit(params, function() {}); reinit(params, function() {});
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
}); });
} }
function softRender() { function softRender() {
$searchInput.val(params.query.query); $searchInput.val(params.query.query);
var $massTagInfo = $el.find('.mass-tag-info'); var $massTagInfo = $el.find('.mass-tag-info');
if (params.query.massTag) { if (params.query.massTag) {
$massTagInfo.show(); $massTagInfo.show();
$massTagInfo.find('span').text(params.query.massTag); $massTagInfo.find('span').text(params.query.massTag);
} else { } else {
$massTagInfo.hide(); $massTagInfo.hide();
} }
_.map($el.find('.posts .post-small'), function(postNode) { softRenderPost(jQuery(postNode).parents('li')); }); _.map($el.find('.posts .post-small'), function(postNode) { softRenderPost(jQuery(postNode).parents('li')); });
} }
function renderPosts($page, posts) { function renderPosts($page, posts) {
var $target = $page.find('.posts'); var $target = $page.find('.posts');
_.each(posts, function(post) { _.each(posts, function(post) {
if (!shouldSkipPost(post)) { if (!shouldSkipPost(post)) {
var $post = renderPost(post); var $post = renderPost(post);
softRenderPost($post); softRenderPost($post);
$target.append($post); $target.append($post);
} }
}); });
windowResized(); windowResized();
} }
function shouldSkipPost(post) { function shouldSkipPost(post) {
var settings = browsingSettings.getSettings(); var settings = browsingSettings.getSettings();
if (post.ownScore < 0 && settings.hideDownvoted) { if (post.ownScore < 0 && settings.hideDownvoted) {
return true; return true;
} }
if (settings.listPosts) { if (settings.listPosts) {
if (post.safety === 'safe' && !settings.listPosts.safe) { if (post.safety === 'safe' && !settings.listPosts.safe) {
return true; return true;
} else if (post.safety === 'sketchy' && !settings.listPosts.sketchy) { } else if (post.safety === 'sketchy' && !settings.listPosts.sketchy) {
return true; return true;
} else if (post.safety === 'unsafe' && !settings.listPosts.unsafe) { } else if (post.safety === 'unsafe' && !settings.listPosts.unsafe) {
return true; return true;
} }
} }
return false; return false;
} }
function renderPost(post) { function renderPost(post) {
var $post = jQuery('<li>' + templates.listItem({ var $post = jQuery('<li>' + templates.listItem({
util: util, util: util,
query: params.query, query: params.query,
post: post, post: post,
canViewPosts: privileges.canViewPosts, canViewPosts: privileges.canViewPosts,
}) + '</li>'); }) + '</li>');
$post.data('post', post); $post.data('post', post);
util.loadImagesNicely($post.find('img')); util.loadImagesNicely($post.find('img'));
return $post; return $post;
} }
function softRenderPost($post) { function softRenderPost($post) {
var classes = []; var classes = [];
if (params.query.massTag) { if (params.query.massTag) {
var post = $post.data('post'); var post = $post.data('post');
if (_.contains(_.map(post.tags, function(tag) { return tag.name.toLowerCase(); }), params.query.massTag.toLowerCase())) { if (_.contains(_.map(post.tags, function(tag) { return tag.name.toLowerCase(); }), params.query.massTag.toLowerCase())) {
classes.push('tagged'); classes.push('tagged');
} else { } else {
classes.push('untagged'); classes.push('untagged');
} }
} }
$post.toggleClass('tagged', _.contains(classes, 'tagged')); $post.toggleClass('tagged', _.contains(classes, 'tagged'));
$post.toggleClass('untagged', _.contains(classes, 'untagged')); $post.toggleClass('untagged', _.contains(classes, 'untagged'));
$post.find('.action').toggle(_.any(classes)); $post.find('.action').toggle(_.any(classes));
$post.find('.action button').text(_.contains(classes, 'tagged') ? 'Tagged' : 'Untagged').unbind('click').click(postTagButtonClicked); $post.find('.action button').text(_.contains(classes, 'tagged') ? 'Tagged' : 'Untagged').unbind('click').click(postTagButtonClicked);
} }
function windowResized() { function windowResized() {
var $list = $el.find('ul.posts'); var $list = $el.find('ul.posts');
var $posts = $list.find('.post-small'); var $posts = $list.find('.post-small');
var $firstPost = $posts.eq(0); var $firstPost = $posts.eq(0);
var $lastPost = $firstPost; var $lastPost = $firstPost;
for (var i = 1; i < $posts.length; i ++) { for (var i = 1; i < $posts.length; i ++) {
$lastPost = $posts.eq(i-1); $lastPost = $posts.eq(i-1);
if ($posts.eq(i).offset().left < $lastPost.offset().left) { if ($posts.eq(i).offset().left < $lastPost.offset().left) {
break; break;
} }
} }
if ($firstPost.length === 0) { if ($firstPost.length === 0) {
return; return;
} }
$el.find('.search').width($lastPost.offset().left + $lastPost.width() - $firstPost.offset().left); $el.find('.search').width($lastPost.offset().left + $lastPost.width() - $firstPost.offset().left);
} }
function postTagButtonClicked(e) { function postTagButtonClicked(e) {
e.preventDefault(); e.preventDefault();
var $post = jQuery(e.target).parents('li'); var $post = jQuery(e.target).parents('li');
var post = $post.data('post'); var post = $post.data('post');
var tags = _.pluck(post.tags, 'name'); var tags = _.pluck(post.tags, 'name');
if (_.contains(_.map(tags, function(tag) { return tag.toLowerCase(); }), params.query.massTag.toLowerCase())) { if (_.contains(_.map(tags, function(tag) { return tag.toLowerCase(); }), params.query.massTag.toLowerCase())) {
tags = _.filter(tags, function(tag) { return tag.toLowerCase() !== params.query.massTag.toLowerCase(); }); tags = _.filter(tags, function(tag) { return tag.toLowerCase() !== params.query.massTag.toLowerCase(); });
} else { } else {
tags.push(params.query.massTag); tags.push(params.query.massTag);
} }
var formData = {}; var formData = {};
formData.seenEditTime = post.lastEditTime; formData.seenEditTime = post.lastEditTime;
formData.tags = tags.join(' '); formData.tags = tags.join(' ');
promise.wait(api.post('/posts/' + post.id, formData)) promise.wait(api.post('/posts/' + post.id, formData))
.then(function(response) { .then(function(response) {
post = response.json; post = response.json;
$post.data('post', post); $post.data('post', post);
softRenderPost($post); softRenderPost($post);
}).fail(function(response) { }).fail(function(response) {
window.alert(response.json && response.json.error || response); window.alert(response.json && response.json.error || response);
}); });
} }
function searchInputKeyPressed(e) { function searchInputKeyPressed(e) {
if (e.which !== KEY_RETURN) { if (e.which !== KEY_RETURN) {
return; return;
} }
updateSearch(); updateSearch();
} }
function massTagButtonClicked(e) { function massTagButtonClicked(e) {
e.preventDefault(); e.preventDefault();
params.query.massTag = window.prompt('Enter tag to tag with:'); params.query.massTag = window.prompt('Enter tag to tag with:');
pagerPresenter.setQuery(params.query); pagerPresenter.setQuery(params.query);
} }
function searchFormSubmitted(e) { function searchFormSubmitted(e) {
e.preventDefault(); e.preventDefault();
updateSearch(); updateSearch();
} }
function updateSearch() { function updateSearch() {
$searchInput.blur(); $searchInput.blur();
params.query.query = $searchInput.val().trim(); params.query.query = $searchInput.val().trim();
params.query.page = 1; params.query.page = 1;
pagerPresenter.setQuery(params.query); pagerPresenter.setQuery(params.query);
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
deinit: deinit, deinit: deinit,
render: render, render: render,
}; };
}; };

View file

@ -2,192 +2,192 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.PostNotesPresenter = function( App.Presenters.PostNotesPresenter = function(
jQuery, jQuery,
util, util,
promise, promise,
api, api,
auth, auth,
draggable, draggable,
resizable) { resizable) {
var post; var post;
var notes; var notes;
var templates = {}; var templates = {};
var $target; var $target;
var $form; var $form;
var privileges = {}; var privileges = {};
function init(params, loaded) { function init(params, loaded) {
$target = params.$target; $target = params.$target;
post = params.post; post = params.post;
notes = params.notes || []; notes = params.notes || [];
privileges.canDeletePostNotes = auth.hasPrivilege(auth.privileges.deletePostNotes); privileges.canDeletePostNotes = auth.hasPrivilege(auth.privileges.deletePostNotes);
privileges.canEditPostNotes = auth.hasPrivilege(auth.privileges.editPostNotes); privileges.canEditPostNotes = auth.hasPrivilege(auth.privileges.editPostNotes);
promise.wait(util.promiseTemplate('post-notes')) promise.wait(util.promiseTemplate('post-notes'))
.then(function(postNotesTemplate) { .then(function(postNotesTemplate) {
templates.postNotes = postNotesTemplate; templates.postNotes = postNotesTemplate;
render(); render();
loaded(); loaded();
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function addNewPostNote() { function addNewPostNote() {
notes.push({left: 10.0, top: 10.0, width: 10.0, height: 10.0, text: '…'}); notes.push({left: 10.0, top: 10.0, width: 10.0, height: 10.0, text: '…'});
} }
function addNewPostNoteAndRender() { function addNewPostNoteAndRender() {
addNewPostNote(); addNewPostNote();
render(); render();
} }
function render() { function render() {
$target.html(templates.postNotes({ $target.html(templates.postNotes({
privileges: privileges, privileges: privileges,
post: post, post: post,
notes: notes, notes: notes,
util: util})); util: util}));
$form = $target.find('.post-note-edit'); $form = $target.find('.post-note-edit');
var $postNotes = $target.find('.post-note'); var $postNotes = $target.find('.post-note');
$postNotes.each(function(i) { $postNotes.each(function(i) {
var postNote = notes[i]; var postNote = notes[i];
var $postNote = jQuery(this); var $postNote = jQuery(this);
$postNote.data('postNote', postNote); $postNote.data('postNote', postNote);
$postNote.find('.text-wrapper').click(postNoteClicked); $postNote.find('.text-wrapper').click(postNoteClicked);
postNote.$element = $postNote; postNote.$element = $postNote;
draggable.makeDraggable($postNote, draggable.relativeDragStrategy, true); draggable.makeDraggable($postNote, draggable.relativeDragStrategy, true);
resizable.makeResizable($postNote, true); resizable.makeResizable($postNote, true);
}); });
$form.find('button').click(formSubmitted); $form.find('button').click(formSubmitted);
} }
function formSubmitted(e) { function formSubmitted(e) {
e.preventDefault(); e.preventDefault();
var $button = jQuery(e.target); var $button = jQuery(e.target);
var sender = $button.val(); var sender = $button.val();
var postNote = $form.data('postNote'); var postNote = $form.data('postNote');
postNote.left = (postNote.$element.offset().left - $target.offset().left) * 100.0 / $target.outerWidth(); postNote.left = (postNote.$element.offset().left - $target.offset().left) * 100.0 / $target.outerWidth();
postNote.top = (postNote.$element.offset().top - $target.offset().top) * 100.0 / $target.outerHeight(); postNote.top = (postNote.$element.offset().top - $target.offset().top) * 100.0 / $target.outerHeight();
postNote.width = postNote.$element.width() * 100.0 / $target.outerWidth(); postNote.width = postNote.$element.width() * 100.0 / $target.outerWidth();
postNote.height = postNote.$element.height() * 100.0 / $target.outerHeight(); postNote.height = postNote.$element.height() * 100.0 / $target.outerHeight();
postNote.text = $form.find('textarea').val(); postNote.text = $form.find('textarea').val();
if (sender === 'cancel') { if (sender === 'cancel') {
hideForm(); hideForm();
} else if (sender === 'remove') { } else if (sender === 'remove') {
removePostNote(postNote); removePostNote(postNote);
} else if (sender === 'save') { } else if (sender === 'save') {
savePostNote(postNote); savePostNote(postNote);
} else if (sender === 'preview') { } else if (sender === 'preview') {
previewPostNote(postNote); previewPostNote(postNote);
} }
} }
function removePostNote(postNote) { function removePostNote(postNote) {
if (postNote.id) { if (postNote.id) {
if (window.confirm('Are you sure you want to delete this note?')) { if (window.confirm('Are you sure you want to delete this note?')) {
promise.wait(api.delete('/notes/' + postNote.id)) promise.wait(api.delete('/notes/' + postNote.id))
.then(function() { .then(function() {
hideForm(); hideForm();
postNote.$element.remove(); postNote.$element.remove();
}).fail(function(response) { }).fail(function(response) {
window.alert(response.json && response.json.error || response); window.alert(response.json && response.json.error || response);
}); });
} }
} else { } else {
postNote.$element.remove(); postNote.$element.remove();
hideForm(); hideForm();
} }
} }
function savePostNote(postNote) { function savePostNote(postNote) {
if (window.confirm('Are you sure you want to save this note?')) { if (window.confirm('Are you sure you want to save this note?')) {
var formData = { var formData = {
left: postNote.left, left: postNote.left,
top: postNote.top, top: postNote.top,
width: postNote.width, width: postNote.width,
height: postNote.height, height: postNote.height,
text: postNote.text, text: postNote.text,
}; };
var p = postNote.id ? var p = postNote.id ?
api.put('/notes/' + postNote.id, formData) : api.put('/notes/' + postNote.id, formData) :
api.post('/notes/' + post.id, formData); api.post('/notes/' + post.id, formData);
promise.wait(p) promise.wait(p)
.then(function(response) { .then(function(response) {
hideForm(); hideForm();
postNote.id = response.json.id; postNote.id = response.json.id;
postNote.$element.data('postNote', postNote); postNote.$element.data('postNote', postNote);
render(); render();
}).fail(function(response) { }).fail(function(response) {
window.alert(response.json && response.json.error || response); window.alert(response.json && response.json.error || response);
}); });
} }
} }
function previewPostNote(postNote) { function previewPostNote(postNote) {
var previewText = $form.find('textarea').val(); var previewText = $form.find('textarea').val();
postNote.$element.find('.text').html(util.formatMarkdown(previewText)); postNote.$element.find('.text').html(util.formatMarkdown(previewText));
showPostNoteText(postNote); showPostNoteText(postNote);
} }
function showPostNoteText(postNote) { function showPostNoteText(postNote) {
postNote.$element.find('.text-wrapper').show(); postNote.$element.find('.text-wrapper').show();
} }
function hidePostNoteText(postNote) { function hidePostNoteText(postNote) {
postNote.$element.find('.text-wrapper').css('display', ''); postNote.$element.find('.text-wrapper').css('display', '');
} }
function postNoteClicked(e) { function postNoteClicked(e) {
e.preventDefault(); e.preventDefault();
var $postNote = jQuery(e.currentTarget).parents('.post-note'); var $postNote = jQuery(e.currentTarget).parents('.post-note');
if ($postNote.hasClass('resizing') || $postNote.hasClass('dragging')) { if ($postNote.hasClass('resizing') || $postNote.hasClass('dragging')) {
return; return;
} }
showFormForPostNote($postNote); showFormForPostNote($postNote);
} }
function showFormForPostNote($postNote) { function showFormForPostNote($postNote) {
hideForm(); hideForm();
var postNote = $postNote.data('postNote'); var postNote = $postNote.data('postNote');
$form.data('postNote', postNote); $form.data('postNote', postNote);
$form.find('textarea').val(postNote.text); $form.find('textarea').val(postNote.text);
$form.show(); $form.show();
draggable.makeDraggable($form, draggable.absoluteDragStrategy, false); draggable.makeDraggable($form, draggable.absoluteDragStrategy, false);
} }
function hideForm() { function hideForm() {
var previousPostNote = $form.data('post-note'); var previousPostNote = $form.data('post-note');
if (previousPostNote) { if (previousPostNote) {
hidePostNoteText(previousPostNote); hidePostNoteText(previousPostNote);
} }
$form.hide(); $form.hide();
} }
return { return {
init: init, init: init,
render: render, render: render,
addNewPostNote: addNewPostNoteAndRender, addNewPostNote: addNewPostNoteAndRender,
}; };
}; };
App.DI.register('postNotesPresenter', [ App.DI.register('postNotesPresenter', [
'jQuery', 'jQuery',
'util', 'util',
'promise', 'promise',
'api', 'api',
'auth', 'auth',
'draggable', 'draggable',
'resizable'], 'resizable'],
App.Presenters.PostNotesPresenter); App.Presenters.PostNotesPresenter);

View file

@ -2,349 +2,349 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.PostPresenter = function( App.Presenters.PostPresenter = function(
_, _,
jQuery, jQuery,
util, util,
promise, promise,
api, api,
auth, auth,
router, router,
keyboard, keyboard,
presenterManager, presenterManager,
postsAroundCalculator, postsAroundCalculator,
postEditPresenter, postEditPresenter,
postContentPresenter, postContentPresenter,
commentListPresenter, commentListPresenter,
topNavigationPresenter, topNavigationPresenter,
messagePresenter) { messagePresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var $messages = $el; var $messages = $el;
var templates = {}; var templates = {};
var params; var params;
var postNameOrId; var postNameOrId;
var post; var post;
var privileges = {}; var privileges = {};
function init(params, loaded) { function init(params, loaded) {
topNavigationPresenter.select('posts'); topNavigationPresenter.select('posts');
postsAroundCalculator.resetCache(); postsAroundCalculator.resetCache();
privileges.canDeletePosts = auth.hasPrivilege(auth.privileges.deletePosts); privileges.canDeletePosts = auth.hasPrivilege(auth.privileges.deletePosts);
privileges.canFeaturePosts = auth.hasPrivilege(auth.privileges.featurePosts); privileges.canFeaturePosts = auth.hasPrivilege(auth.privileges.featurePosts);
privileges.canViewHistory = auth.hasPrivilege(auth.privileges.viewHistory); privileges.canViewHistory = auth.hasPrivilege(auth.privileges.viewHistory);
privileges.canAddPostNotes = auth.hasPrivilege(auth.privileges.addPostNotes); privileges.canAddPostNotes = auth.hasPrivilege(auth.privileges.addPostNotes);
promise.wait( promise.wait(
util.promiseTemplate('post'), util.promiseTemplate('post'),
util.promiseTemplate('history')) util.promiseTemplate('history'))
.then(function( .then(function(
postTemplate, postTemplate,
historyTemplate) { historyTemplate) {
templates.post = postTemplate; templates.post = postTemplate;
templates.history = historyTemplate; templates.history = historyTemplate;
reinit(params, loaded); reinit(params, loaded);
}).fail(function(response) { }).fail(function(response) {
showGenericError(response); showGenericError(response);
loaded(); loaded();
}); });
} }
function reinit(_params, loaded) { function reinit(_params, loaded) {
params = _params; params = _params;
params.query = params.query || {}; params.query = params.query || {};
params.query.page = parseInt(params.query.page) || 1; params.query.page = parseInt(params.query.page) || 1;
postNameOrId = params.postNameOrId; postNameOrId = params.postNameOrId;
promise.wait(refreshPost()) promise.wait(refreshPost())
.then(function() { .then(function() {
topNavigationPresenter.changeTitle('@' + post.id); topNavigationPresenter.changeTitle('@' + post.id);
render(); render();
loaded(); loaded();
presenterManager.initPresenters([ presenterManager.initPresenters([
[postContentPresenter, {post: post, $target: $el.find('#post-content-target')}], [postContentPresenter, {post: post, $target: $el.find('#post-content-target')}],
[postEditPresenter, {post: post, $target: $el.find('#post-edit-target'), updateCallback: postEdited}], [postEditPresenter, {post: post, $target: $el.find('#post-edit-target'), updateCallback: postEdited}],
[commentListPresenter, {post: post, $target: $el.find('#post-comments-target')}]], [commentListPresenter, {post: post, $target: $el.find('#post-comments-target')}]],
function() { }); function() { });
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function attachLinksToPostsAround() { function attachLinksToPostsAround() {
promise.wait(postsAroundCalculator.getLinksToPostsAround(params.query, post.id)) promise.wait(postsAroundCalculator.getLinksToPostsAround(params.query, post.id))
.then(function(nextPostUrl, prevPostUrl) { .then(function(nextPostUrl, prevPostUrl) {
var $prevPost = $el.find('#post-current-search .right a'); var $prevPost = $el.find('#post-current-search .right a');
var $nextPost = $el.find('#post-current-search .left a'); var $nextPost = $el.find('#post-current-search .left a');
if (nextPostUrl) { if (nextPostUrl) {
$nextPost.addClass('enabled'); $nextPost.addClass('enabled');
$nextPost.attr('href', nextPostUrl); $nextPost.attr('href', nextPostUrl);
keyboard.keyup('a', function() { keyboard.keyup('a', function() {
router.navigate(nextPostUrl); router.navigate(nextPostUrl);
}); });
} else { } else {
$nextPost.removeClass('enabled'); $nextPost.removeClass('enabled');
$nextPost.removeAttr('href'); $nextPost.removeAttr('href');
keyboard.unbind('a'); keyboard.unbind('a');
} }
if (prevPostUrl) { if (prevPostUrl) {
$prevPost.addClass('enabled'); $prevPost.addClass('enabled');
$prevPost.attr('href', prevPostUrl); $prevPost.attr('href', prevPostUrl);
keyboard.keyup('d', function() { keyboard.keyup('d', function() {
router.navigate(prevPostUrl); router.navigate(prevPostUrl);
}); });
} else { } else {
$prevPost.removeClass('enabled'); $prevPost.removeClass('enabled');
$prevPost.removeAttr('href'); $prevPost.removeAttr('href');
keyboard.unbind('d'); keyboard.unbind('d');
} }
}).fail(function() { }).fail(function() {
}); });
} }
function refreshPost() { function refreshPost() {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
promise.wait(api.get('/posts/' + postNameOrId)) promise.wait(api.get('/posts/' + postNameOrId))
.then(function(postResponse) { .then(function(postResponse) {
post = postResponse.json; post = postResponse.json;
resolve(); resolve();
}).fail(function(response) { }).fail(function(response) {
showGenericError(response); showGenericError(response);
reject(); reject();
}); });
}); });
} }
function render() { function render() {
$el.html(renderPostTemplate()); $el.html(renderPostTemplate());
$messages = $el.find('.messages'); $messages = $el.find('.messages');
keyboard.keyup('e', function() { keyboard.keyup('e', function() {
editButtonClicked(null); editButtonClicked(null);
}); });
keyboard.keyup('f', function() { keyboard.keyup('f', function() {
var $wrapper = $el.find('.object-wrapper'); var $wrapper = $el.find('.object-wrapper');
if ($wrapper.data('full')) { if ($wrapper.data('full')) {
$wrapper.css({maxWidth: $wrapper.attr('data-width') + 'px', width: 'auto'}); $wrapper.css({maxWidth: $wrapper.attr('data-width') + 'px', width: 'auto'});
$wrapper.data('full', false); $wrapper.data('full', false);
} else { } else {
$wrapper.css({maxWidth: null, width: $wrapper.attr('data-width')}); $wrapper.css({maxWidth: null, width: $wrapper.attr('data-width')});
$wrapper.data('full', true); $wrapper.data('full', true);
} }
postContentPresenter.updatePostNotesSize(); postContentPresenter.updatePostNotesSize();
}); });
attachSidebarEvents(); attachSidebarEvents();
attachLinksToPostsAround(); attachLinksToPostsAround();
} }
function postEdited(newPost) { function postEdited(newPost) {
post = newPost; post = newPost;
hideEditForm(); hideEditForm();
softRender(); softRender();
} }
function softRender() { function softRender() {
renderSidebar(); renderSidebar();
$el.find('video').prop('loop', post.flags.loop); $el.find('video').prop('loop', post.flags.loop);
} }
function renderSidebar() { function renderSidebar() {
$el.find('#sidebar').html(jQuery(renderPostTemplate()).find('#sidebar').html()); $el.find('#sidebar').html(jQuery(renderPostTemplate()).find('#sidebar').html());
attachSidebarEvents(); attachSidebarEvents();
} }
function renderPostTemplate() { function renderPostTemplate() {
return templates.post({ return templates.post({
query: params.query, query: params.query,
post: post, post: post,
ownScore: post.ownScore, ownScore: post.ownScore,
postFavorites: post.favorites, postFavorites: post.favorites,
postHistory: post.history, postHistory: post.history,
util: util, util: util,
historyTemplate: templates.history, historyTemplate: templates.history,
hasFav: _.any(post.favorites, function(favUser) { return favUser.id === auth.getCurrentUser().id; }), hasFav: _.any(post.favorites, function(favUser) { return favUser.id === auth.getCurrentUser().id; }),
isLoggedIn: auth.isLoggedIn(), isLoggedIn: auth.isLoggedIn(),
privileges: privileges, privileges: privileges,
editPrivileges: postEditPresenter.getPrivileges(), editPrivileges: postEditPresenter.getPrivileges(),
}); });
} }
function attachSidebarEvents() { function attachSidebarEvents() {
$el.find('#sidebar .delete').click(deleteButtonClicked); $el.find('#sidebar .delete').click(deleteButtonClicked);
$el.find('#sidebar .feature').click(featureButtonClicked); $el.find('#sidebar .feature').click(featureButtonClicked);
$el.find('#sidebar .edit').click(editButtonClicked); $el.find('#sidebar .edit').click(editButtonClicked);
$el.find('#sidebar .history').click(historyButtonClicked); $el.find('#sidebar .history').click(historyButtonClicked);
$el.find('#sidebar .add-favorite').click(addFavoriteButtonClicked); $el.find('#sidebar .add-favorite').click(addFavoriteButtonClicked);
$el.find('#sidebar .delete-favorite').click(deleteFavoriteButtonClicked); $el.find('#sidebar .delete-favorite').click(deleteFavoriteButtonClicked);
$el.find('#sidebar .score-up').click(scoreUpButtonClicked); $el.find('#sidebar .score-up').click(scoreUpButtonClicked);
$el.find('#sidebar .score-down').click(scoreDownButtonClicked); $el.find('#sidebar .score-down').click(scoreDownButtonClicked);
$el.find('#sidebar .add-note').click(addNoteButtonClicked); $el.find('#sidebar .add-note').click(addNoteButtonClicked);
} }
function addNoteButtonClicked(e) { function addNoteButtonClicked(e) {
e.preventDefault(); e.preventDefault();
postContentPresenter.addNewPostNote(); postContentPresenter.addNewPostNote();
} }
function deleteButtonClicked(e) { function deleteButtonClicked(e) {
e.preventDefault(); e.preventDefault();
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
if (window.confirm('Do you really want to delete this post?')) { if (window.confirm('Do you really want to delete this post?')) {
deletePost(); deletePost();
} }
} }
function deletePost() { function deletePost() {
promise.wait(api.delete('/posts/' + post.id)) promise.wait(api.delete('/posts/' + post.id))
.then(function(response) { .then(function(response) {
router.navigate('#/posts'); router.navigate('#/posts');
}).fail(showGenericError); }).fail(showGenericError);
} }
function featureButtonClicked(e) { function featureButtonClicked(e) {
e.preventDefault(); e.preventDefault();
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
if (window.confirm('Do you want to feature this post on the front page?')) { if (window.confirm('Do you want to feature this post on the front page?')) {
featurePost(); featurePost();
} }
} }
function featurePost() { function featurePost() {
promise.wait(api.post('/posts/' + post.id + '/feature')) promise.wait(api.post('/posts/' + post.id + '/feature'))
.then(function(response) { .then(function(response) {
router.navigate('#/home'); router.navigate('#/home');
}).fail(showGenericError); }).fail(showGenericError);
} }
function editButtonClicked(e) { function editButtonClicked(e) {
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
} }
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
if ($el.find('#post-edit-target').is(':visible')) { if ($el.find('#post-edit-target').is(':visible')) {
hideEditForm(); hideEditForm();
} else { } else {
showEditForm(); showEditForm();
} }
} }
function showEditForm() { function showEditForm() {
$el.find('#post-edit-target').slideDown('fast'); $el.find('#post-edit-target').slideDown('fast');
util.enableExitConfirmation(); util.enableExitConfirmation();
postEditPresenter.focus(); postEditPresenter.focus();
} }
function hideEditForm() { function hideEditForm() {
$el.find('#post-edit-target').slideUp('fast'); $el.find('#post-edit-target').slideUp('fast');
util.disableExitConfirmation(); util.disableExitConfirmation();
} }
function historyButtonClicked(e) { function historyButtonClicked(e) {
e.preventDefault(); e.preventDefault();
if ($el.find('.post-history-wrapper').is(':visible')) { if ($el.find('.post-history-wrapper').is(':visible')) {
hideHistory(); hideHistory();
} else { } else {
showHistory(); showHistory();
} }
} }
function hideHistory() { function hideHistory() {
$el.find('.post-history-wrapper').slideUp('slow'); $el.find('.post-history-wrapper').slideUp('slow');
} }
function showHistory() { function showHistory() {
$el.find('.post-history-wrapper').slideDown('slow'); $el.find('.post-history-wrapper').slideDown('slow');
} }
function addFavoriteButtonClicked(e) { function addFavoriteButtonClicked(e) {
e.preventDefault(); e.preventDefault();
addFavorite(); addFavorite();
} }
function deleteFavoriteButtonClicked(e) { function deleteFavoriteButtonClicked(e) {
e.preventDefault(); e.preventDefault();
deleteFavorite(); deleteFavorite();
} }
function addFavorite() { function addFavorite() {
promise.wait(api.post('/posts/' + post.id + '/favorites')) promise.wait(api.post('/posts/' + post.id + '/favorites'))
.then(function(response) { .then(function(response) {
promise.wait(refreshPost()).then(softRender); promise.wait(refreshPost()).then(softRender);
}).fail(showGenericError); }).fail(showGenericError);
} }
function deleteFavorite() { function deleteFavorite() {
promise.wait(api.delete('/posts/' + post.id + '/favorites')) promise.wait(api.delete('/posts/' + post.id + '/favorites'))
.then(function(response) { .then(function(response) {
promise.wait(refreshPost()).then(softRender); promise.wait(refreshPost()).then(softRender);
}).fail(showGenericError); }).fail(showGenericError);
} }
function scoreUpButtonClicked(e) { function scoreUpButtonClicked(e) {
e.preventDefault(); e.preventDefault();
var $target = jQuery(this); var $target = jQuery(this);
score($target.hasClass('active') ? 0 : 1); score($target.hasClass('active') ? 0 : 1);
} }
function scoreDownButtonClicked(e) { function scoreDownButtonClicked(e) {
e.preventDefault(); e.preventDefault();
var $target = jQuery(this); var $target = jQuery(this);
score($target.hasClass('active') ? 0 : -1); score($target.hasClass('active') ? 0 : -1);
} }
function score(scoreValue) { function score(scoreValue) {
promise.wait(api.post('/posts/' + post.id + '/score', {score: scoreValue})) promise.wait(api.post('/posts/' + post.id + '/score', {score: scoreValue}))
.then(function() { .then(function() {
promise.wait(refreshPost()).then(softRender); promise.wait(refreshPost()).then(softRender);
}).fail(showGenericError); }).fail(showGenericError);
} }
function showGenericError(response) { function showGenericError(response) {
if ($messages === $el) { if ($messages === $el) {
$el.empty(); $el.empty();
} }
messagePresenter.showError($messages, response.json && response.json.error || response); messagePresenter.showError($messages, response.json && response.json.error || response);
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
render: render render: render
}; };
}; };
App.DI.register('postPresenter', [ App.DI.register('postPresenter', [
'_', '_',
'jQuery', 'jQuery',
'util', 'util',
'promise', 'promise',
'api', 'api',
'auth', 'auth',
'router', 'router',
'keyboard', 'keyboard',
'presenterManager', 'presenterManager',
'postsAroundCalculator', 'postsAroundCalculator',
'postEditPresenter', 'postEditPresenter',
'postContentPresenter', 'postContentPresenter',
'commentListPresenter', 'commentListPresenter',
'topNavigationPresenter', 'topNavigationPresenter',
'messagePresenter'], 'messagePresenter'],
App.Presenters.PostPresenter); App.Presenters.PostPresenter);

File diff suppressed because it is too large Load diff

View file

@ -2,43 +2,43 @@ var App = App || {};
App.Controls = App.Controls || {}; App.Controls = App.Controls || {};
App.Presenters.ProgressPresenter = function(nprogress) { App.Presenters.ProgressPresenter = function(nprogress) {
var nesting = 0; var nesting = 0;
function start() { function start() {
nesting ++; nesting ++;
if (nesting === 1) { if (nesting === 1) {
nprogress.start(); nprogress.start();
} }
} }
function reset() { function reset() {
nesting = 0; nesting = 0;
} }
function done() { function done() {
if (nesting) { if (nesting) {
nesting --; nesting --;
} }
if (nesting <= 0) { if (nesting <= 0) {
nprogress.done(); nprogress.done();
} else { } else {
nprogress.inc(); nprogress.inc();
} }
} }
window.setInterval(function() { window.setInterval(function() {
if (nesting <= 0) { if (nesting <= 0) {
nprogress.done(); nprogress.done();
} }
}, 1000); }, 1000);
return { return {
start: start, start: start,
done: done, done: done,
reset: reset, reset: reset,
}; };
}; };

View file

@ -2,100 +2,100 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.RegistrationPresenter = function( App.Presenters.RegistrationPresenter = function(
jQuery, jQuery,
util, util,
promise, promise,
api, api,
topNavigationPresenter, topNavigationPresenter,
messagePresenter) { messagePresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var templates = {}; var templates = {};
var $messages; var $messages;
function init(params, loaded) { function init(params, loaded) {
topNavigationPresenter.select('register'); topNavigationPresenter.select('register');
topNavigationPresenter.changeTitle('Registration'); topNavigationPresenter.changeTitle('Registration');
promise.wait(util.promiseTemplate('registration-form')) promise.wait(util.promiseTemplate('registration-form'))
.then(function(template) { .then(function(template) {
templates.registration = template; templates.registration = template;
render(); render();
loaded(); loaded();
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function render() { function render() {
$el.html(templates.registration()); $el.html(templates.registration());
$el.find('form').submit(registrationFormSubmitted); $el.find('form').submit(registrationFormSubmitted);
$messages = $el.find('.messages'); $messages = $el.find('.messages');
$messages.width($el.find('form').width()); $messages.width($el.find('form').width());
} }
function registrationFormSubmitted(e) { function registrationFormSubmitted(e) {
e.preventDefault(); e.preventDefault();
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
var formData = { var formData = {
userName: $el.find('[name=userName]').val(), userName: $el.find('[name=userName]').val(),
password: $el.find('[name=password]').val(), password: $el.find('[name=password]').val(),
passwordConfirmation: $el.find('[name=passwordConfirmation]').val(), passwordConfirmation: $el.find('[name=passwordConfirmation]').val(),
email: $el.find('[name=email]').val(), email: $el.find('[name=email]').val(),
}; };
if (!validateRegistrationFormData(formData)) { if (!validateRegistrationFormData(formData)) {
return; return;
} }
promise.wait(api.post('/users', formData)) promise.wait(api.post('/users', formData))
.then(function(response) { .then(function(response) {
registrationSuccess(response); registrationSuccess(response);
}).fail(function(response) { }).fail(function(response) {
registrationFailure(response); registrationFailure(response);
}); });
} }
function registrationSuccess(apiResponse) { function registrationSuccess(apiResponse) {
$el.find('form').slideUp(function() { $el.find('form').slideUp(function() {
var message = 'Registration complete! '; var message = 'Registration complete! ';
if (!apiResponse.json.confirmed) { if (!apiResponse.json.confirmed) {
message += '<br/>Check your inbox for activation e-mail.<br/>If e-mail doesn\'t show up, check your spam folder.'; message += '<br/>Check your inbox for activation e-mail.<br/>If e-mail doesn\'t show up, check your spam folder.';
} else { } else {
message += '<a href="#/login">Click here</a> to login.'; message += '<a href="#/login">Click here</a> to login.';
} }
messagePresenter.showInfo($messages, message); messagePresenter.showInfo($messages, message);
}); });
} }
function registrationFailure(apiResponse) { function registrationFailure(apiResponse) {
messagePresenter.showError($messages, apiResponse.json && apiResponse.json.error || apiResponse); messagePresenter.showError($messages, apiResponse.json && apiResponse.json.error || apiResponse);
} }
function validateRegistrationFormData(formData) { function validateRegistrationFormData(formData) {
if (formData.userName.length === 0) { if (formData.userName.length === 0) {
messagePresenter.showError($messages, 'User name cannot be empty.'); messagePresenter.showError($messages, 'User name cannot be empty.');
return false; return false;
} }
if (formData.password.length === 0) { if (formData.password.length === 0) {
messagePresenter.showError($messages, 'Password cannot be empty.'); messagePresenter.showError($messages, 'Password cannot be empty.');
return false; return false;
} }
if (formData.password !== formData.passwordConfirmation) { if (formData.password !== formData.passwordConfirmation) {
messagePresenter.showError($messages, 'Passwords must be the same.'); messagePresenter.showError($messages, 'Passwords must be the same.');
return false; return false;
} }
return true; return true;
} }
return { return {
init: init, init: init,
render: render, render: render,
}; };
}; };

View file

@ -2,124 +2,124 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.TagListPresenter = function( App.Presenters.TagListPresenter = function(
_, _,
jQuery, jQuery,
util, util,
promise, promise,
keyboard, keyboard,
pagerPresenter, pagerPresenter,
topNavigationPresenter) { topNavigationPresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var $searchInput; var $searchInput;
var templates = {}; var templates = {};
var params; var params;
function init(_params, loaded) { function init(_params, loaded) {
topNavigationPresenter.select('tags'); topNavigationPresenter.select('tags');
topNavigationPresenter.changeTitle('Tags'); topNavigationPresenter.changeTitle('Tags');
params = _params; params = _params;
params.query = params.query || {}; params.query = params.query || {};
promise.wait( promise.wait(
util.promiseTemplate('tag-list'), util.promiseTemplate('tag-list'),
util.promiseTemplate('tag-list-item')) util.promiseTemplate('tag-list-item'))
.then(function(listTemplate, listItemTemplate) { .then(function(listTemplate, listItemTemplate) {
templates.list = listTemplate; templates.list = listTemplate;
templates.listItem = listItemTemplate; templates.listItem = listItemTemplate;
render(); render();
loaded(); loaded();
pagerPresenter.init({ pagerPresenter.init({
baseUri: '#/tags', baseUri: '#/tags',
backendUri: '/tags', backendUri: '/tags',
$target: $el.find('.pagination-target'), $target: $el.find('.pagination-target'),
updateCallback: function($page, data) { updateCallback: function($page, data) {
renderTags($page, data.entities); renderTags($page, data.entities);
}, },
}, },
function() { function() {
reinit(params, function() {}); reinit(params, function() {});
}); });
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function reinit(_params, loaded) { function reinit(_params, loaded) {
params = _params; params = _params;
params.query = params.query || {}; params.query = params.query || {};
params.query.order = params.query.order || 'name,asc'; params.query.order = params.query.order || 'name,asc';
updateActiveOrder(params.query.order); updateActiveOrder(params.query.order);
pagerPresenter.reinit({query: params.query}); pagerPresenter.reinit({query: params.query});
keyboard.keyup('p', function() { keyboard.keyup('p', function() {
$el.find('table a').eq(0).focus(); $el.find('table a').eq(0).focus();
}); });
keyboard.keyup('q', function() { keyboard.keyup('q', function() {
$searchInput.eq(0).focus().select(); $searchInput.eq(0).focus().select();
}); });
loaded(); loaded();
softRender(); softRender();
} }
function deinit() { function deinit() {
pagerPresenter.deinit(); pagerPresenter.deinit();
} }
function render() { function render() {
$el.html(templates.list()); $el.html(templates.list());
$searchInput = $el.find('input[name=query]'); $searchInput = $el.find('input[name=query]');
$el.find('form').submit(searchFormSubmitted); $el.find('form').submit(searchFormSubmitted);
App.Controls.AutoCompleteInput($searchInput); App.Controls.AutoCompleteInput($searchInput);
softRender(); softRender();
} }
function softRender() { function softRender() {
$searchInput.val(params.query.query); $searchInput.val(params.query.query);
} }
function searchFormSubmitted(e) { function searchFormSubmitted(e) {
e.preventDefault(); e.preventDefault();
updateSearch(); updateSearch();
} }
function updateSearch() { function updateSearch() {
$searchInput.blur(); $searchInput.blur();
params.query.query = $searchInput.val().trim(); params.query.query = $searchInput.val().trim();
params.query.page = 1; params.query.page = 1;
pagerPresenter.setQuery(params.query); pagerPresenter.setQuery(params.query);
} }
function updateActiveOrder(activeOrder) { function updateActiveOrder(activeOrder) {
$el.find('.order li a.active').removeClass('active'); $el.find('.order li a.active').removeClass('active');
$el.find('.order [href*="' + activeOrder + '"]').addClass('active'); $el.find('.order [href*="' + activeOrder + '"]').addClass('active');
} }
function renderTags($page, tags) { function renderTags($page, tags) {
var $target = $page.find('tbody'); var $target = $page.find('tbody');
_.each(tags, function(tag) { _.each(tags, function(tag) {
var $item = jQuery(templates.listItem({ var $item = jQuery(templates.listItem({
tag: tag, tag: tag,
util: util, util: util,
})); }));
$target.append($item); $target.append($item);
}); });
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
deinit: deinit, deinit: deinit,
render: render, render: render,
}; };
}; };

View file

@ -2,199 +2,199 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.TagPresenter = function( App.Presenters.TagPresenter = function(
_, _,
jQuery, jQuery,
util, util,
promise, promise,
auth, auth,
api, api,
tagList, tagList,
router, router,
keyboard, keyboard,
topNavigationPresenter, topNavigationPresenter,
messagePresenter) { messagePresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var $messages = $el; var $messages = $el;
var templates = {}; var templates = {};
var implicationsTagInput; var implicationsTagInput;
var suggestionsTagInput; var suggestionsTagInput;
var tag; var tag;
var posts; var posts;
var siblings; var siblings;
var privileges = {}; var privileges = {};
function init(params, loaded) { function init(params, loaded) {
topNavigationPresenter.select('tags'); topNavigationPresenter.select('tags');
topNavigationPresenter.changeTitle('Tags'); topNavigationPresenter.changeTitle('Tags');
privileges.canChangeName = auth.hasPrivilege(auth.privileges.changeTagName); privileges.canChangeName = auth.hasPrivilege(auth.privileges.changeTagName);
privileges.canChangeCategory = auth.hasPrivilege(auth.privileges.changeTagCategory); privileges.canChangeCategory = auth.hasPrivilege(auth.privileges.changeTagCategory);
privileges.canChangeImplications = auth.hasPrivilege(auth.privileges.changeTagImplications); privileges.canChangeImplications = auth.hasPrivilege(auth.privileges.changeTagImplications);
privileges.canChangeSuggestions = auth.hasPrivilege(auth.privileges.changeTagSuggestions); privileges.canChangeSuggestions = auth.hasPrivilege(auth.privileges.changeTagSuggestions);
privileges.canBan = auth.hasPrivilege(auth.privileges.banTags); privileges.canBan = auth.hasPrivilege(auth.privileges.banTags);
privileges.canViewHistory = auth.hasPrivilege(auth.privileges.viewHistory); privileges.canViewHistory = auth.hasPrivilege(auth.privileges.viewHistory);
privileges.canDelete = auth.hasPrivilege(auth.privileges.deleteTags); privileges.canDelete = auth.hasPrivilege(auth.privileges.deleteTags);
privileges.canMerge = auth.hasPrivilege(auth.privileges.mergeTags); privileges.canMerge = auth.hasPrivilege(auth.privileges.mergeTags);
privileges.canViewPosts = auth.hasPrivilege(auth.privileges.viewPosts); privileges.canViewPosts = auth.hasPrivilege(auth.privileges.viewPosts);
promise.wait( promise.wait(
util.promiseTemplate('tag'), util.promiseTemplate('tag'),
util.promiseTemplate('post-list-item'), util.promiseTemplate('post-list-item'),
util.promiseTemplate('history')) util.promiseTemplate('history'))
.then(function(tagTemplate, postListItemTemplate, historyTemplate) { .then(function(tagTemplate, postListItemTemplate, historyTemplate) {
templates.tag = tagTemplate; templates.tag = tagTemplate;
templates.postListItem = postListItemTemplate; templates.postListItem = postListItemTemplate;
templates.history = historyTemplate; templates.history = historyTemplate;
reinit(params, loaded); reinit(params, loaded);
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function reinit(params, loaded) { function reinit(params, loaded) {
var tagName = params.tagName; var tagName = params.tagName;
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
promise.wait( promise.wait(
api.get('tags/' + tagName), api.get('tags/' + tagName),
api.get('tags/' + tagName + '/siblings'), api.get('tags/' + tagName + '/siblings'),
api.get('posts', {query: tagName})) api.get('posts', {query: tagName}))
.then(function(tagResponse, siblingsResponse, postsResponse) { .then(function(tagResponse, siblingsResponse, postsResponse) {
tag = tagResponse.json; tag = tagResponse.json;
siblings = siblingsResponse.json.data; siblings = siblingsResponse.json.data;
posts = postsResponse.json.data; posts = postsResponse.json.data;
posts = posts.slice(0, 8); posts = posts.slice(0, 8);
render(); render();
loaded(); loaded();
renderPosts(posts); renderPosts(posts);
}).fail(function(tagResponse, siblingsResponse, postsResponse) { }).fail(function(tagResponse, siblingsResponse, postsResponse) {
messagePresenter.showError($messages, tagResponse.json.error || siblingsResponse.json.error || postsResponse.json.error); messagePresenter.showError($messages, tagResponse.json.error || siblingsResponse.json.error || postsResponse.json.error);
loaded(); loaded();
}); });
} }
function render() { function render() {
$el.html(templates.tag({ $el.html(templates.tag({
privileges: privileges, privileges: privileges,
tag: tag, tag: tag,
siblings: siblings, siblings: siblings,
tagCategories: JSON.parse(jQuery('head').attr('data-tag-categories')), tagCategories: JSON.parse(jQuery('head').attr('data-tag-categories')),
util: util, util: util,
historyTemplate: templates.history, historyTemplate: templates.history,
})); }));
$el.find('.post-list').hide(); $el.find('.post-list').hide();
$el.find('form').submit(function(e) { e.preventDefault(); }); $el.find('form').submit(function(e) { e.preventDefault(); });
$el.find('form button[name=update]').click(updateButtonClicked); $el.find('form button[name=update]').click(updateButtonClicked);
$el.find('form button[name=delete]').click(deleteButtonClicked); $el.find('form button[name=delete]').click(deleteButtonClicked);
$el.find('form button[name=merge]').click(mergeButtonClicked); $el.find('form button[name=merge]').click(mergeButtonClicked);
implicationsTagInput = App.Controls.TagInput($el.find('[name=implications]')); implicationsTagInput = App.Controls.TagInput($el.find('[name=implications]'));
suggestionsTagInput = App.Controls.TagInput($el.find('[name=suggestions]')); suggestionsTagInput = App.Controls.TagInput($el.find('[name=suggestions]'));
} }
function updateButtonClicked(e) { function updateButtonClicked(e) {
e.preventDefault(); e.preventDefault();
var $form = $el.find('form'); var $form = $el.find('form');
var formData = {}; var formData = {};
if (privileges.canChangeName) { if (privileges.canChangeName) {
formData.name = $form.find('[name=name]').val(); formData.name = $form.find('[name=name]').val();
} }
if (privileges.canChangeCategory) { if (privileges.canChangeCategory) {
formData.category = $form.find('[name=category]:checked').val(); formData.category = $form.find('[name=category]:checked').val();
} }
if (privileges.canBan) { if (privileges.canBan) {
formData.banned = $form.find('[name=ban]').is(':checked') ? 1 : 0; formData.banned = $form.find('[name=ban]').is(':checked') ? 1 : 0;
} }
if (privileges.canChangeImplications) { if (privileges.canChangeImplications) {
formData.implications = implicationsTagInput.getTags().join(' '); formData.implications = implicationsTagInput.getTags().join(' ');
} }
if (privileges.canChangeSuggestions) { if (privileges.canChangeSuggestions) {
formData.suggestions = suggestionsTagInput.getTags().join(' '); formData.suggestions = suggestionsTagInput.getTags().join(' ');
} }
promise.wait(api.put('/tags/' + tag.name, formData)) promise.wait(api.put('/tags/' + tag.name, formData))
.then(function(response) { .then(function(response) {
router.navigateInplace('#/tag/' + response.json.name); router.navigateInplace('#/tag/' + response.json.name);
tagList.refreshTags(); tagList.refreshTags();
}).fail(function(response) { }).fail(function(response) {
window.alert(response.json && response.json.error || 'An error occured.'); window.alert(response.json && response.json.error || 'An error occured.');
}); });
} }
function deleteButtonClicked(e) { function deleteButtonClicked(e) {
if (!window.confirm('Are you sure you want to delete this tag?')) { if (!window.confirm('Are you sure you want to delete this tag?')) {
return; return;
} }
promise.wait(api.delete('/tags/' + tag.name)) promise.wait(api.delete('/tags/' + tag.name))
.then(function(response) { .then(function(response) {
router.navigate('#/tags'); router.navigate('#/tags');
tagList.refreshTags(); tagList.refreshTags();
}).fail(function(response) { }).fail(function(response) {
window.alert(response.json && response.json.error || 'An error occured.'); window.alert(response.json && response.json.error || 'An error occured.');
}); });
} }
function mergeButtonClicked(e) { function mergeButtonClicked(e) {
var targetTag = window.prompt('What tag should this be merged to?'); var targetTag = window.prompt('What tag should this be merged to?');
if (targetTag) { if (targetTag) {
promise.wait(api.put('/tags/' + tag.name + '/merge', {targetTag: targetTag})) promise.wait(api.put('/tags/' + tag.name + '/merge', {targetTag: targetTag}))
.then(function(response) { .then(function(response) {
router.navigate('#/tags'); router.navigate('#/tags');
tagList.refreshTags(); tagList.refreshTags();
}).fail(function(response) { }).fail(function(response) {
window.alert(response.json && response.json.error || 'An error occured.'); window.alert(response.json && response.json.error || 'An error occured.');
}); });
} }
} }
function renderPosts(posts) { function renderPosts(posts) {
var $target = $el.find('.post-list ul'); var $target = $el.find('.post-list ul');
_.each(posts, function(post) { _.each(posts, function(post) {
var $post = jQuery('<li>' + templates.postListItem({ var $post = jQuery('<li>' + templates.postListItem({
util: util, util: util,
post: post, post: post,
query: {query: tag.name}, query: {query: tag.name},
canViewPosts: privileges.canViewPosts, canViewPosts: privileges.canViewPosts,
}) + '</li>'); }) + '</li>');
$target.append($post); $target.append($post);
}); });
if (posts.length > 0) { if (posts.length > 0) {
$el.find('.post-list').fadeIn(); $el.find('.post-list').fadeIn();
keyboard.keyup('p', function() { keyboard.keyup('p', function() {
$el.find('.post-list a').eq(0).focus(); $el.find('.post-list a').eq(0).focus();
}); });
} }
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
}; };
}; };
App.DI.register('tagPresenter', [ App.DI.register('tagPresenter', [
'_', '_',
'jQuery', 'jQuery',
'util', 'util',
'promise', 'promise',
'auth', 'auth',
'api', 'api',
'tagList', 'tagList',
'router', 'router',
'keyboard', 'keyboard',
'topNavigationPresenter', 'topNavigationPresenter',
'messagePresenter'], 'messagePresenter'],
App.Presenters.TagPresenter); App.Presenters.TagPresenter);

View file

@ -2,78 +2,78 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.TopNavigationPresenter = function( App.Presenters.TopNavigationPresenter = function(
jQuery, jQuery,
util, util,
promise, promise,
auth) { auth) {
var selectedElement = null; var selectedElement = null;
var $el = jQuery('#top-navigation'); var $el = jQuery('#top-navigation');
var templates = {}; var templates = {};
var baseTitle = document.title; var baseTitle = document.title;
function init(params, loaded) { function init(params, loaded) {
promise.wait(util.promiseTemplate('top-navigation')) promise.wait(util.promiseTemplate('top-navigation'))
.then(function(template) { .then(function(template) {
templates.topNavigation = template; templates.topNavigation = template;
render(); render();
loaded(); loaded();
auth.startObservingLoginChanges('top-navigation', loginStateChanged); auth.startObservingLoginChanges('top-navigation', loginStateChanged);
}).fail(function() { }).fail(function() {
loaded(); loaded();
}); });
} }
function select(newSelectedElement) { function select(newSelectedElement) {
selectedElement = newSelectedElement; selectedElement = newSelectedElement;
$el.find('li a').removeClass('active'); $el.find('li a').removeClass('active');
$el.find('li.' + selectedElement).find('a').addClass('active'); $el.find('li.' + selectedElement).find('a').addClass('active');
} }
function loginStateChanged() { function loginStateChanged() {
render(); render();
} }
function render() { function render() {
$el.html(templates.topNavigation({ $el.html(templates.topNavigation({
loggedIn: auth.isLoggedIn(), loggedIn: auth.isLoggedIn(),
user: auth.getCurrentUser(), user: auth.getCurrentUser(),
canListUsers: auth.hasPrivilege(auth.privileges.listUsers), canListUsers: auth.hasPrivilege(auth.privileges.listUsers),
canListPosts: auth.hasPrivilege(auth.privileges.listPosts), canListPosts: auth.hasPrivilege(auth.privileges.listPosts),
canListComments: auth.hasPrivilege(auth.privileges.listComments), canListComments: auth.hasPrivilege(auth.privileges.listComments),
canListTags: auth.hasPrivilege(auth.privileges.listTags), canListTags: auth.hasPrivilege(auth.privileges.listTags),
canUploadPosts: auth.hasPrivilege(auth.privileges.uploadPosts), canUploadPosts: auth.hasPrivilege(auth.privileges.uploadPosts),
})); }));
$el.find('li.' + selectedElement).find('a').addClass('active'); $el.find('li.' + selectedElement).find('a').addClass('active');
} }
function focus() { function focus() {
var $tmp = jQuery('<a href="#"> </a>'); var $tmp = jQuery('<a href="#"> </a>');
$el.prepend($tmp); $el.prepend($tmp);
$tmp.focus(); $tmp.focus();
$tmp.remove(); $tmp.remove();
} }
function getBaseTitle() { function getBaseTitle() {
return baseTitle; return baseTitle;
} }
function changeTitle(subTitle) { function changeTitle(subTitle) {
var newTitle = baseTitle; var newTitle = baseTitle;
if (subTitle) { if (subTitle) {
newTitle += ' - ' + subTitle; newTitle += ' - ' + subTitle;
} }
document.title = newTitle; document.title = newTitle;
} }
return { return {
init: init, init: init,
render: render, render: render,
select: select, select: select,
focus: focus, focus: focus,
getBaseTitle: getBaseTitle, getBaseTitle: getBaseTitle,
changeTitle: changeTitle, changeTitle: changeTitle,
}; };
}; };

View file

@ -2,80 +2,80 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.UserAccountRemovalPresenter = function( App.Presenters.UserAccountRemovalPresenter = function(
jQuery, jQuery,
util, util,
promise, promise,
api, api,
auth, auth,
router, router,
messagePresenter) { messagePresenter) {
var target; var target;
var templates = {}; var templates = {};
var user; var user;
var privileges = {}; var privileges = {};
function init(params, loaded) { function init(params, loaded) {
user = params.user; user = params.user;
target = params.target; target = params.target;
privileges.canDeleteAccount = privileges.canDeleteAccount =
auth.hasPrivilege(auth.privileges.deleteAllAccounts) || auth.hasPrivilege(auth.privileges.deleteAllAccounts) ||
(auth.hasPrivilege(auth.privileges.deleteOwnAccount) && auth.isLoggedIn(user.name)); (auth.hasPrivilege(auth.privileges.deleteOwnAccount) && auth.isLoggedIn(user.name));
promise.wait(util.promiseTemplate('account-removal')) promise.wait(util.promiseTemplate('account-removal'))
.then(function(template) { .then(function(template) {
templates.accountRemoval = template; templates.accountRemoval = template;
render(); render();
loaded(); loaded();
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function render() { function render() {
var $el = jQuery(target); var $el = jQuery(target);
$el.html(templates.accountRemoval({ $el.html(templates.accountRemoval({
user: user, user: user,
canDeleteAccount: privileges.canDeleteAccount})); canDeleteAccount: privileges.canDeleteAccount}));
$el.find('form').submit(accountRemovalFormSubmitted); $el.find('form').submit(accountRemovalFormSubmitted);
} }
function getPrivileges() { function getPrivileges() {
return privileges; return privileges;
} }
function accountRemovalFormSubmitted(e) { function accountRemovalFormSubmitted(e) {
e.preventDefault(); e.preventDefault();
var $el = jQuery(target); var $el = jQuery(target);
var $messages = $el.find('.messages'); var $messages = $el.find('.messages');
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
if (!$el.find('input[name=confirmation]:visible').prop('checked')) { if (!$el.find('input[name=confirmation]:visible').prop('checked')) {
messagePresenter.showError($messages, 'Must confirm to proceed.'); messagePresenter.showError($messages, 'Must confirm to proceed.');
return; return;
} }
promise.wait(api.delete('/users/' + user.name)) promise.wait(api.delete('/users/' + user.name))
.then(function() { .then(function() {
auth.logout(); auth.logout();
var $messageDiv = messagePresenter.showInfo($messages, 'Account deleted. <a href="">Back to main page</a>'); var $messageDiv = messagePresenter.showInfo($messages, 'Account deleted. <a href="">Back to main page</a>');
$messageDiv.find('a').click(mainPageLinkClicked); $messageDiv.find('a').click(mainPageLinkClicked);
}).fail(function(response) { }).fail(function(response) {
messagePresenter.showError($messages, response.json && response.json.error || response); messagePresenter.showError($messages, response.json && response.json.error || response);
}); });
} }
function mainPageLinkClicked(e) { function mainPageLinkClicked(e) {
e.preventDefault(); e.preventDefault();
router.navigateToMainPage(); router.navigateToMainPage();
} }
return { return {
init: init, init: init,
render: render, render: render,
getPrivileges: getPrivileges getPrivileges: getPrivileges
}; };
}; };

View file

@ -2,162 +2,162 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.UserAccountSettingsPresenter = function( App.Presenters.UserAccountSettingsPresenter = function(
_, _,
jQuery, jQuery,
util, util,
promise, promise,
api, api,
auth, auth,
messagePresenter) { messagePresenter) {
var target; var target;
var templates = {}; var templates = {};
var user; var user;
var privileges; var privileges;
var avatarContent; var avatarContent;
var fileDropper; var fileDropper;
function init(params, loaded) { function init(params, loaded) {
user = params.user; user = params.user;
target = params.target; target = params.target;
privileges = { privileges = {
canBan: canBan:
auth.hasPrivilege(auth.privileges.banUsers), auth.hasPrivilege(auth.privileges.banUsers),
canChangeAccessRank: canChangeAccessRank:
auth.hasPrivilege(auth.privileges.changeAccessRank), auth.hasPrivilege(auth.privileges.changeAccessRank),
canChangeAvatarStyle: canChangeAvatarStyle:
auth.hasPrivilege(auth.privileges.changeAllAvatarStyles) || auth.hasPrivilege(auth.privileges.changeAllAvatarStyles) ||
(auth.hasPrivilege(auth.privileges.changeOwnAvatarStyle) && auth.isLoggedIn(user.name)), (auth.hasPrivilege(auth.privileges.changeOwnAvatarStyle) && auth.isLoggedIn(user.name)),
canChangeName: canChangeName:
auth.hasPrivilege(auth.privileges.changeAllNames) || auth.hasPrivilege(auth.privileges.changeAllNames) ||
(auth.hasPrivilege(auth.privileges.changeOwnName) && auth.isLoggedIn(user.name)), (auth.hasPrivilege(auth.privileges.changeOwnName) && auth.isLoggedIn(user.name)),
canChangeEmailAddress: canChangeEmailAddress:
auth.hasPrivilege(auth.privileges.changeAllEmailAddresses) || auth.hasPrivilege(auth.privileges.changeAllEmailAddresses) ||
(auth.hasPrivilege(auth.privileges.changeOwnEmailAddress) && auth.isLoggedIn(user.name)), (auth.hasPrivilege(auth.privileges.changeOwnEmailAddress) && auth.isLoggedIn(user.name)),
canChangePassword: canChangePassword:
auth.hasPrivilege(auth.privileges.changeAllPasswords) || auth.hasPrivilege(auth.privileges.changeAllPasswords) ||
(auth.hasPrivilege(auth.privileges.changeOwnPassword) && auth.isLoggedIn(user.name)), (auth.hasPrivilege(auth.privileges.changeOwnPassword) && auth.isLoggedIn(user.name)),
}; };
promise.wait(util.promiseTemplate('account-settings')) promise.wait(util.promiseTemplate('account-settings'))
.then(function(template) { .then(function(template) {
templates.accountRemoval = template; templates.accountRemoval = template;
render(); render();
loaded(); loaded();
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function render() { function render() {
var $el = jQuery(target); var $el = jQuery(target);
$el.html(templates.accountRemoval(_.extend({user: user}, privileges))); $el.html(templates.accountRemoval(_.extend({user: user}, privileges)));
$el.find('form').submit(accountSettingsFormSubmitted); $el.find('form').submit(accountSettingsFormSubmitted);
$el.find('form [name=avatar-style]').change(avatarStyleChanged); $el.find('form [name=avatar-style]').change(avatarStyleChanged);
avatarStyleChanged(); avatarStyleChanged();
fileDropper = new App.Controls.FileDropper($el.find('[name=avatar-content]')); fileDropper = new App.Controls.FileDropper($el.find('[name=avatar-content]'));
fileDropper.onChange = avatarContentChanged; fileDropper.onChange = avatarContentChanged;
fileDropper.setNames = true; fileDropper.setNames = true;
} }
function getPrivileges() { function getPrivileges() {
return privileges; return privileges;
} }
function avatarStyleChanged(e) { function avatarStyleChanged(e) {
var $el = jQuery(target); var $el = jQuery(target);
var $target = $el.find('.avatar-content .file-handler'); var $target = $el.find('.avatar-content .file-handler');
if ($el.find('[name=avatar-style]:checked').val() === 'manual') { if ($el.find('[name=avatar-style]:checked').val() === 'manual') {
$target.show(); $target.show();
} else { } else {
$target.hide(); $target.hide();
} }
} }
function avatarContentChanged(files) { function avatarContentChanged(files) {
avatarContent = files[0]; avatarContent = files[0];
} }
function accountSettingsFormSubmitted(e) { function accountSettingsFormSubmitted(e) {
e.preventDefault(); e.preventDefault();
var $el = jQuery(target); var $el = jQuery(target);
var $messages = jQuery(target).find('.messages'); var $messages = jQuery(target).find('.messages');
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
var formData = new FormData(); var formData = new FormData();
if (privileges.canChangeAvatarStyle) { if (privileges.canChangeAvatarStyle) {
formData.append('avatarStyle', $el.find('[name=avatar-style]:checked').val()); formData.append('avatarStyle', $el.find('[name=avatar-style]:checked').val());
if (avatarContent) { if (avatarContent) {
formData.append('avatarContent', avatarContent); formData.append('avatarContent', avatarContent);
} }
} }
if (privileges.canChangeName) { if (privileges.canChangeName) {
formData.append('userName', $el.find('[name=userName]').val()); formData.append('userName', $el.find('[name=userName]').val());
} }
if (privileges.canChangeEmailAddress) { if (privileges.canChangeEmailAddress) {
formData.append('email', $el.find('[name=email]').val()); formData.append('email', $el.find('[name=email]').val());
} }
if (privileges.canChangePassword) { if (privileges.canChangePassword) {
var password = $el.find('[name=password]').val(); var password = $el.find('[name=password]').val();
var passwordConfirmation = $el.find('[name=passwordConfirmation]').val(); var passwordConfirmation = $el.find('[name=passwordConfirmation]').val();
if (password) { if (password) {
if (password !== passwordConfirmation) { if (password !== passwordConfirmation) {
messagePresenter.showError($messages, 'Passwords must be the same.'); messagePresenter.showError($messages, 'Passwords must be the same.');
return; return;
} }
formData.append('password', password); formData.append('password', password);
} }
} }
if (privileges.canChangeAccessRank) { if (privileges.canChangeAccessRank) {
formData.append('accessRank', $el.find('[name=access-rank]:checked').val()); formData.append('accessRank', $el.find('[name=access-rank]:checked').val());
} }
if (privileges.canBan) { if (privileges.canBan) {
formData.append('banned', $el.find('[name=ban]').is(':checked') ? 1 : 0); formData.append('banned', $el.find('[name=ban]').is(':checked') ? 1 : 0);
} }
promise.wait(api.post('/users/' + user.name, formData)) promise.wait(api.post('/users/' + user.name, formData))
.then(function(response) { .then(function(response) {
editSuccess(response); editSuccess(response);
}).fail(function(response) { }).fail(function(response) {
editFailure(response); editFailure(response);
}); });
} }
function editSuccess(apiResponse) { function editSuccess(apiResponse) {
var wasLoggedIn = auth.isLoggedIn(user.name); var wasLoggedIn = auth.isLoggedIn(user.name);
user = apiResponse.json; user = apiResponse.json;
if (wasLoggedIn) { if (wasLoggedIn) {
auth.updateCurrentUser(user); auth.updateCurrentUser(user);
} }
render(); render();
var $messages = jQuery(target).find('.messages'); var $messages = jQuery(target).find('.messages');
var message = 'Account settings updated!'; var message = 'Account settings updated!';
if (!apiResponse.json.confirmed) { if (!apiResponse.json.confirmed) {
message += '<br/>Check your inbox for activation e-mail.<br/>If e-mail doesn\'t show up, check your spam folder.'; message += '<br/>Check your inbox for activation e-mail.<br/>If e-mail doesn\'t show up, check your spam folder.';
} }
messagePresenter.showInfo($messages, message); messagePresenter.showInfo($messages, message);
} }
function editFailure(apiResponse) { function editFailure(apiResponse) {
var $messages = jQuery(target).find('.messages'); var $messages = jQuery(target).find('.messages');
messagePresenter.showError($messages, apiResponse.json && apiResponse.json.error || apiResponse); messagePresenter.showError($messages, apiResponse.json && apiResponse.json.error || apiResponse);
} }
return { return {
init: init, init: init,
render: render, render: render,
getPrivileges: getPrivileges, getPrivileges: getPrivileges,
}; };
}; };

View file

@ -2,117 +2,117 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.UserActivationPresenter = function( App.Presenters.UserActivationPresenter = function(
jQuery, jQuery,
promise, promise,
util, util,
auth, auth,
api, api,
router, router,
topNavigationPresenter, topNavigationPresenter,
messagePresenter) { messagePresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var $messages = $el; var $messages = $el;
var templates = {}; var templates = {};
var formHidden = false; var formHidden = false;
var operation; var operation;
function init(params, loaded) { function init(params, loaded) {
topNavigationPresenter.select('login'); topNavigationPresenter.select('login');
topNavigationPresenter.changeTitle('Account recovery'); topNavigationPresenter.changeTitle('Account recovery');
reinit(params, loaded); reinit(params, loaded);
} }
function reinit(params, loaded) { function reinit(params, loaded) {
operation = params.operation; operation = params.operation;
promise.wait(util.promiseTemplate('user-query-form')) promise.wait(util.promiseTemplate('user-query-form'))
.then(function(template) { .then(function(template) {
templates.userQuery = template; templates.userQuery = template;
if (params.token) { if (params.token) {
hideForm(); hideForm();
confirmToken(params.token); confirmToken(params.token);
} else { } else {
showForm(); showForm();
} }
render(); render();
loaded(); loaded();
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function render() { function render() {
$el.html(templates.userQuery()); $el.html(templates.userQuery());
$messages = $el.find('.messages'); $messages = $el.find('.messages');
if (formHidden) { if (formHidden) {
$el.find('form').hide(); $el.find('form').hide();
} }
$el.find('form').submit(userQueryFormSubmitted); $el.find('form').submit(userQueryFormSubmitted);
} }
function hideForm() { function hideForm() {
formHidden = true; formHidden = true;
} }
function showForm() { function showForm() {
formHidden = false; formHidden = false;
} }
function userQueryFormSubmitted(e) { function userQueryFormSubmitted(e) {
e.preventDefault(); e.preventDefault();
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
var userNameOrEmail = $el.find('form input[name=user]').val(); var userNameOrEmail = $el.find('form input[name=user]').val();
if (userNameOrEmail.length === 0) { if (userNameOrEmail.length === 0) {
messagePresenter.showError($messages, 'Field cannot be blank.'); messagePresenter.showError($messages, 'Field cannot be blank.');
return; return;
} }
var url = operation === 'passwordReset' ? var url = operation === 'passwordReset' ?
'/password-reset/' + userNameOrEmail : '/password-reset/' + userNameOrEmail :
'/activation/' + userNameOrEmail; '/activation/' + userNameOrEmail;
promise.wait(api.post(url)) promise.wait(api.post(url))
.then(function(response) { .then(function(response) {
var message = operation === 'passwordReset' ? var message = operation === 'passwordReset' ?
'Password reset request sent.' : 'Password reset request sent.' :
'Activation e-mail resent.'; 'Activation e-mail resent.';
message += ' Check your inbox.<br/>If e-mail doesn\'t show up, check your spam folder.'; message += ' Check your inbox.<br/>If e-mail doesn\'t show up, check your spam folder.';
$el.find('#user-query-form').slideUp(function() { $el.find('#user-query-form').slideUp(function() {
messagePresenter.showInfo($messages, message); messagePresenter.showInfo($messages, message);
}); });
}).fail(function(response) { }).fail(function(response) {
messagePresenter.showError($messages, response.json && response.json.error || response); messagePresenter.showError($messages, response.json && response.json.error || response);
}); });
} }
function confirmToken(token) { function confirmToken(token) {
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
var url = operation === 'passwordReset' ? var url = operation === 'passwordReset' ?
'/finish-password-reset/' + token : '/finish-password-reset/' + token :
'/finish-activation/' + token; '/finish-activation/' + token;
promise.wait(api.post(url)) promise.wait(api.post(url))
.then(function(response) { .then(function(response) {
var message = operation === 'passwordReset' ? var message = operation === 'passwordReset' ?
'Your new password is <strong>' + response.json.newPassword + '</strong>.' : 'Your new password is <strong>' + response.json.newPassword + '</strong>.' :
'E-mail activation successful.'; 'E-mail activation successful.';
$el.find('#user-query-form').slideUp(function() { $el.find('#user-query-form').slideUp(function() {
messagePresenter.showInfo($messages, message); messagePresenter.showInfo($messages, message);
}); });
}).fail(function(response) { }).fail(function(response) {
messagePresenter.showError($messages, response.json && response.json.error || response); messagePresenter.showError($messages, response.json && response.json.error || response);
}); });
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
render: render, render: render,
}; };
}; };

View file

@ -2,75 +2,75 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.UserBrowsingSettingsPresenter = function( App.Presenters.UserBrowsingSettingsPresenter = function(
jQuery, jQuery,
util, util,
promise, promise,
auth, auth,
browsingSettings, browsingSettings,
messagePresenter) { messagePresenter) {
var target; var target;
var templates = {}; var templates = {};
var user; var user;
var privileges = {}; var privileges = {};
function init(params, loaded) { function init(params, loaded) {
user = params.user; user = params.user;
target = params.target; target = params.target;
privileges.canChangeBrowsingSettings = auth.isLoggedIn(user.name) && user.name === auth.getCurrentUser().name; privileges.canChangeBrowsingSettings = auth.isLoggedIn(user.name) && user.name === auth.getCurrentUser().name;
promise.wait(util.promiseTemplate('browsing-settings')) promise.wait(util.promiseTemplate('browsing-settings'))
.then(function(template) { .then(function(template) {
templates.browsingSettings = template; templates.browsingSettings = template;
render(); render();
loaded(); loaded();
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function render() { function render() {
var $el = jQuery(target); var $el = jQuery(target);
$el.html(templates.browsingSettings({user: user, settings: browsingSettings.getSettings()})); $el.html(templates.browsingSettings({user: user, settings: browsingSettings.getSettings()}));
$el.find('form').submit(browsingSettingsFormSubmitted); $el.find('form').submit(browsingSettingsFormSubmitted);
} }
function browsingSettingsFormSubmitted(e) { function browsingSettingsFormSubmitted(e) {
e.preventDefault(); e.preventDefault();
var $el = jQuery(target); var $el = jQuery(target);
var $messages = $el.find('.messages'); var $messages = $el.find('.messages');
messagePresenter.hideMessages($messages); messagePresenter.hideMessages($messages);
var newSettings = { var newSettings = {
endlessScroll: $el.find('[name=endlessScroll]').is(':checked'), endlessScroll: $el.find('[name=endlessScroll]').is(':checked'),
hideDownvoted: $el.find('[name=hideDownvoted]').is(':checked'), hideDownvoted: $el.find('[name=hideDownvoted]').is(':checked'),
listPosts: { listPosts: {
safe: $el.find('[name=listSafePosts]').is(':checked'), safe: $el.find('[name=listSafePosts]').is(':checked'),
sketchy: $el.find('[name=listSketchyPosts]').is(':checked'), sketchy: $el.find('[name=listSketchyPosts]').is(':checked'),
unsafe: $el.find('[name=listUnsafePosts]').is(':checked'), unsafe: $el.find('[name=listUnsafePosts]').is(':checked'),
}, },
keyboardShortcuts: $el.find('[name=keyboardShortcuts]').is(':checked'), keyboardShortcuts: $el.find('[name=keyboardShortcuts]').is(':checked'),
}; };
promise.wait(browsingSettings.setSettings(newSettings)) promise.wait(browsingSettings.setSettings(newSettings))
.then(function() { .then(function() {
messagePresenter.showInfo($messages, 'Browsing settings updated!'); messagePresenter.showInfo($messages, 'Browsing settings updated!');
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
}); });
} }
function getPrivileges() { function getPrivileges() {
return privileges; return privileges;
} }
return { return {
init: init, init: init,
render: render, render: render,
getPrivileges: getPrivileges, getPrivileges: getPrivileges,
}; };
}; };

View file

@ -2,93 +2,93 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.UserListPresenter = function( App.Presenters.UserListPresenter = function(
_, _,
jQuery, jQuery,
util, util,
promise, promise,
auth, auth,
pagerPresenter, pagerPresenter,
topNavigationPresenter) { topNavigationPresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var templates = {}; var templates = {};
var params; var params;
var privileges = {}; var privileges = {};
function init(params, loaded) { function init(params, loaded) {
topNavigationPresenter.select('users'); topNavigationPresenter.select('users');
topNavigationPresenter.changeTitle('Users'); topNavigationPresenter.changeTitle('Users');
privileges.canViewUsers = auth.hasPrivilege(auth.privileges.viewUsers); privileges.canViewUsers = auth.hasPrivilege(auth.privileges.viewUsers);
promise.wait( promise.wait(
util.promiseTemplate('user-list'), util.promiseTemplate('user-list'),
util.promiseTemplate('user-list-item')) util.promiseTemplate('user-list-item'))
.then(function(listTemplate, listItemTemplate) { .then(function(listTemplate, listItemTemplate) {
templates.list = listTemplate; templates.list = listTemplate;
templates.listItem = listItemTemplate; templates.listItem = listItemTemplate;
render(); render();
loaded(); loaded();
pagerPresenter.init({ pagerPresenter.init({
baseUri: '#/users', baseUri: '#/users',
backendUri: '/users', backendUri: '/users',
$target: $el.find('.pagination-target'), $target: $el.find('.pagination-target'),
updateCallback: function($page, data) { updateCallback: function($page, data) {
renderUsers($page, data.entities); renderUsers($page, data.entities);
}, },
}, },
function() { function() {
reinit(params, function() {}); reinit(params, function() {});
}); });
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function reinit(_params, loaded) { function reinit(_params, loaded) {
params = _params; params = _params;
params.query = params.query || {}; params.query = params.query || {};
params.query.order = params.query.order || 'name,asc'; params.query.order = params.query.order || 'name,asc';
updateActiveOrder(params.query.order); updateActiveOrder(params.query.order);
pagerPresenter.reinit({query: params.query}); pagerPresenter.reinit({query: params.query});
loaded(); loaded();
} }
function deinit() { function deinit() {
pagerPresenter.deinit(); pagerPresenter.deinit();
} }
function render() { function render() {
$el.html(templates.list(privileges)); $el.html(templates.list(privileges));
} }
function updateActiveOrder(activeOrder) { function updateActiveOrder(activeOrder) {
$el.find('.order li a.active').removeClass('active'); $el.find('.order li a.active').removeClass('active');
$el.find('.order [href*="' + activeOrder + '"]').addClass('active'); $el.find('.order [href*="' + activeOrder + '"]').addClass('active');
} }
function renderUsers($page, users) { function renderUsers($page, users) {
var $target = $page.find('.users'); var $target = $page.find('.users');
_.each(users, function(user) { _.each(users, function(user) {
var $item = jQuery('<li>' + templates.listItem(_.extend({ var $item = jQuery('<li>' + templates.listItem(_.extend({
user: user, user: user,
util: util, util: util,
}, privileges)) + '</li>'); }, privileges)) + '</li>');
$target.append($item); $target.append($item);
}); });
_.map(_.map($target.find('img'), jQuery), util.loadImagesNicely); _.map(_.map($target.find('img'), jQuery), util.loadImagesNicely);
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
deinit: deinit, deinit: deinit,
render: render, render: render,
}; };
}; };

View file

@ -2,105 +2,105 @@ var App = App || {};
App.Presenters = App.Presenters || {}; App.Presenters = App.Presenters || {};
App.Presenters.UserPresenter = function( App.Presenters.UserPresenter = function(
_, _,
jQuery, jQuery,
util, util,
promise, promise,
api, api,
auth, auth,
topNavigationPresenter, topNavigationPresenter,
presenterManager, presenterManager,
userBrowsingSettingsPresenter, userBrowsingSettingsPresenter,
userAccountSettingsPresenter, userAccountSettingsPresenter,
userAccountRemovalPresenter, userAccountRemovalPresenter,
messagePresenter) { messagePresenter) {
var $el = jQuery('#content'); var $el = jQuery('#content');
var $messages = $el; var $messages = $el;
var templates = {}; var templates = {};
var user; var user;
var userName = null; var userName = null;
var activeTab; var activeTab;
function init(params, loaded) { function init(params, loaded) {
promise.wait(util.promiseTemplate('user')) promise.wait(util.promiseTemplate('user'))
.then(function(template) { .then(function(template) {
templates.user = template; templates.user = template;
reinit(params, loaded); reinit(params, loaded);
}).fail(function() { }).fail(function() {
console.log(arguments); console.log(arguments);
loaded(); loaded();
}); });
} }
function reinit(params, loaded) { function reinit(params, loaded) {
if (params.userName !== userName) { if (params.userName !== userName) {
userName = params.userName; userName = params.userName;
topNavigationPresenter.select(auth.isLoggedIn(userName) ? 'my-account' : 'users'); topNavigationPresenter.select(auth.isLoggedIn(userName) ? 'my-account' : 'users');
topNavigationPresenter.changeTitle(userName); topNavigationPresenter.changeTitle(userName);
promise.wait(api.get('/users/' + userName)) promise.wait(api.get('/users/' + userName))
.then(function(response) { .then(function(response) {
user = response.json; user = response.json;
var extendedContext = _.extend(params, {user: user}); var extendedContext = _.extend(params, {user: user});
presenterManager.initPresenters([ presenterManager.initPresenters([
[userBrowsingSettingsPresenter, _.extend({}, extendedContext, {target: '#browsing-settings-target'})], [userBrowsingSettingsPresenter, _.extend({}, extendedContext, {target: '#browsing-settings-target'})],
[userAccountSettingsPresenter, _.extend({}, extendedContext, {target: '#account-settings-target'})], [userAccountSettingsPresenter, _.extend({}, extendedContext, {target: '#account-settings-target'})],
[userAccountRemovalPresenter, _.extend({}, extendedContext, {target: '#account-removal-target'})]], [userAccountRemovalPresenter, _.extend({}, extendedContext, {target: '#account-removal-target'})]],
function() { function() {
initTabs(params); initTabs(params);
loaded(); loaded();
}); });
}).fail(function(response) { }).fail(function(response) {
$el.empty(); $el.empty();
messagePresenter.showError($messages, response.json && response.json.error || response); messagePresenter.showError($messages, response.json && response.json.error || response);
loaded(); loaded();
}); });
} else { } else {
initTabs(params); initTabs(params);
loaded(); loaded();
} }
} }
function initTabs(params) { function initTabs(params) {
activeTab = params.tab || 'basic-info'; activeTab = params.tab || 'basic-info';
render(); render();
} }
function render() { function render() {
$el.html(templates.user({ $el.html(templates.user({
user: user, user: user,
isLoggedIn: auth.isLoggedIn(user.name), isLoggedIn: auth.isLoggedIn(user.name),
util: util, util: util,
canChangeBrowsingSettings: userBrowsingSettingsPresenter.getPrivileges().canChangeBrowsingSettings, canChangeBrowsingSettings: userBrowsingSettingsPresenter.getPrivileges().canChangeBrowsingSettings,
canChangeAccountSettings: _.any(userAccountSettingsPresenter.getPrivileges()), canChangeAccountSettings: _.any(userAccountSettingsPresenter.getPrivileges()),
canDeleteAccount: userAccountRemovalPresenter.getPrivileges().canDeleteAccount})); canDeleteAccount: userAccountRemovalPresenter.getPrivileges().canDeleteAccount}));
$messages = $el.find('.messages'); $messages = $el.find('.messages');
util.loadImagesNicely($el.find('img')); util.loadImagesNicely($el.find('img'));
userBrowsingSettingsPresenter.render(); userBrowsingSettingsPresenter.render();
userAccountSettingsPresenter.render(); userAccountSettingsPresenter.render();
userAccountRemovalPresenter.render(); userAccountRemovalPresenter.render();
changeTab(activeTab); changeTab(activeTab);
} }
function changeTab(targetTab) { function changeTab(targetTab) {
var $link = $el.find('a[data-tab=' + targetTab + ']'); var $link = $el.find('a[data-tab=' + targetTab + ']');
var $links = $link.closest('ul').find('a[data-tab]'); var $links = $link.closest('ul').find('a[data-tab]');
var $tabs = $el.find('.tab-wrapper').find('.tab'); var $tabs = $el.find('.tab-wrapper').find('.tab');
$links.removeClass('active'); $links.removeClass('active');
$link.addClass('active'); $link.addClass('active');
$tabs.removeClass('active'); $tabs.removeClass('active');
$tabs.filter('[data-tab=' + targetTab + ']').addClass('active'); $tabs.filter('[data-tab=' + targetTab + ']').addClass('active');
} }
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
render: render render: render
}; };
}; };

View file

@ -2,84 +2,84 @@ var App = App || {};
App.Promise = function(_, jQuery, progress) { App.Promise = function(_, jQuery, progress) {
function BrokenPromiseError(promiseId) { function BrokenPromiseError(promiseId) {
this.name = 'BrokenPromiseError'; this.name = 'BrokenPromiseError';
this.message = 'Broken promise (promise ID: ' + promiseId + ')'; this.message = 'Broken promise (promise ID: ' + promiseId + ')';
} }
BrokenPromiseError.prototype = new Error(); BrokenPromiseError.prototype = new Error();
var active = []; var active = [];
var promiseId = 0; var promiseId = 0;
function make(callback, useProgress) { function make(callback, useProgress) {
var deferred = jQuery.Deferred(); var deferred = jQuery.Deferred();
var promise = deferred.promise(); var promise = deferred.promise();
promise.promiseId = ++ promiseId; promise.promiseId = ++ promiseId;
if (useProgress === true) { if (useProgress === true) {
progress.start(); progress.start();
} }
callback(function() { callback(function() {
try { try {
deferred.resolve.apply(deferred, arguments); deferred.resolve.apply(deferred, arguments);
active = _.without(active, promise.promiseId); active = _.without(active, promise.promiseId);
progress.done(); progress.done();
} catch (e) { } catch (e) {
if (!(e instanceof BrokenPromiseError)) { if (!(e instanceof BrokenPromiseError)) {
console.log(e); console.log(e);
} }
progress.reset(); progress.reset();
} }
}, function() { }, function() {
try { try {
deferred.reject.apply(deferred, arguments); deferred.reject.apply(deferred, arguments);
active = _.without(active, promise.promiseId); active = _.without(active, promise.promiseId);
progress.done(); progress.done();
} catch (e) { } catch (e) {
if (!(e instanceof BrokenPromiseError)) { if (!(e instanceof BrokenPromiseError)) {
console.log(e); console.log(e);
} }
progress.reset(); progress.reset();
} }
}); });
active.push(promise.promiseId); active.push(promise.promiseId);
promise.always(function() { promise.always(function() {
if (!_.contains(active, promise.promiseId)) { if (!_.contains(active, promise.promiseId)) {
throw new BrokenPromiseError(promise.promiseId); throw new BrokenPromiseError(promise.promiseId);
} }
}); });
return promise; return promise;
} }
function wait() { function wait() {
var promises = arguments; var promises = arguments;
var deferred = jQuery.Deferred(); var deferred = jQuery.Deferred();
return jQuery.when.apply(jQuery, promises) return jQuery.when.apply(jQuery, promises)
.then(function() { .then(function() {
return deferred.resolve.apply(deferred, arguments); return deferred.resolve.apply(deferred, arguments);
}).fail(function() { }).fail(function() {
return deferred.reject.apply(deferred, arguments); return deferred.reject.apply(deferred, arguments);
}); });
} }
function abortAll() { function abortAll() {
active = []; active = [];
} }
function getActive() { function getActive() {
return active.length; return active.length;
} }
return { return {
make: function(callback) { return make(callback, true); }, make: function(callback) { return make(callback, true); },
makeSilent: function(callback) { return make(callback, false); }, makeSilent: function(callback) { return make(callback, false); },
wait: wait, wait: wait,
getActive: getActive, getActive: getActive,
abortAll: abortAll, abortAll: abortAll,
}; };
}; };

View file

@ -2,184 +2,184 @@ var App = App || {};
App.Router = function(_, jQuery, promise, util, appState, presenterManager) { App.Router = function(_, jQuery, promise, util, appState, presenterManager) {
var root = '#/'; var root = '#/';
var previousLocation = window.location.href; var previousLocation = window.location.href;
var routes = []; var routes = [];
injectRoutes(); injectRoutes();
function injectRoutes() { function injectRoutes() {
inject('', 'homePresenter'); inject('', 'homePresenter');
inject('#/', 'homePresenter'); inject('#/', 'homePresenter');
inject('#/404', 'httpErrorPresenter', {error: 404}); inject('#/404', 'httpErrorPresenter', {error: 404});
inject('#/home', 'homePresenter'); inject('#/home', 'homePresenter');
inject('#/login', 'loginPresenter'); inject('#/login', 'loginPresenter');
inject('#/logout', 'logoutPresenter'); inject('#/logout', 'logoutPresenter');
inject('#/register', 'registrationPresenter'); inject('#/register', 'registrationPresenter');
inject('#/upload', 'postUploadPresenter'); inject('#/upload', 'postUploadPresenter');
inject('#/password-reset(/:token)', 'userActivationPresenter', {operation: 'passwordReset'}); inject('#/password-reset(/:token)', 'userActivationPresenter', {operation: 'passwordReset'});
inject('#/activate(/:token)', 'userActivationPresenter', {operation: 'activation'}); inject('#/activate(/:token)', 'userActivationPresenter', {operation: 'activation'});
inject('#/users(/:!query)', 'userListPresenter'); inject('#/users(/:!query)', 'userListPresenter');
inject('#/user/:userName(/:tab)', 'userPresenter'); inject('#/user/:userName(/:tab)', 'userPresenter');
inject('#/posts(/:!query)', 'postListPresenter'); inject('#/posts(/:!query)', 'postListPresenter');
inject('#/post/:postNameOrId(/:!query)', 'postPresenter'); inject('#/post/:postNameOrId(/:!query)', 'postPresenter');
inject('#/comments(/:!query)', 'globalCommentListPresenter'); inject('#/comments(/:!query)', 'globalCommentListPresenter');
inject('#/tags(/:!query)', 'tagListPresenter'); inject('#/tags(/:!query)', 'tagListPresenter');
inject('#/tag/:tagName', 'tagPresenter'); inject('#/tag/:tagName', 'tagPresenter');
inject('#/help(/:tab)', 'helpPresenter'); inject('#/help(/:tab)', 'helpPresenter');
inject('#/history(/:!query)', 'historyPresenter'); inject('#/history(/:!query)', 'historyPresenter');
} }
function navigate(url, useBrowserDispatcher) { function navigate(url, useBrowserDispatcher) {
if (('pushState' in history) && !useBrowserDispatcher) { if (('pushState' in history) && !useBrowserDispatcher) {
history.pushState('', '', url); history.pushState('', '', url);
dispatch(); dispatch();
} else { } else {
window.location.href = url; window.location.href = url;
} }
} }
function navigateToMainPage() { function navigateToMainPage() {
navigate(root); navigate(root);
} }
function navigateInplace(url, useBrowserDispatcher) { function navigateInplace(url, useBrowserDispatcher) {
if ('replaceState' in history) { if ('replaceState' in history) {
history.replaceState('', '', url); history.replaceState('', '', url);
if (!useBrowserDispatcher) { if (!useBrowserDispatcher) {
dispatch(); dispatch();
} else { } else {
location.reload(); location.reload();
} }
} else { } else {
navigate(url, useBrowserDispatcher); navigate(url, useBrowserDispatcher);
} }
} }
function start() { function start() {
if ('onhashchange' in window) { if ('onhashchange' in window) {
window.onhashchange = dispatch; window.onhashchange = dispatch;
} else { } else {
window.onpopstate = dispatch; window.onpopstate = dispatch;
} }
dispatch(); dispatch();
} }
function inject(definition, presenterName, additionalParams) { function inject(definition, presenterName, additionalParams) {
routes.push(new Route(definition, function(params) { routes.push(new Route(definition, function(params) {
if (util.isExitConfirmationEnabled()) { if (util.isExitConfirmationEnabled()) {
if (window.location.href === previousLocation) { if (window.location.href === previousLocation) {
return; return;
} else { } else {
if (window.confirm('Are you sure you want to leave this page? Data will be lost.')) { if (window.confirm('Are you sure you want to leave this page? Data will be lost.')) {
util.disableExitConfirmation(); util.disableExitConfirmation();
} else { } else {
window.location.href = previousLocation; window.location.href = previousLocation;
return; return;
} }
} }
} }
params = _.extend({}, params, additionalParams, {previousLocation: previousLocation}); params = _.extend({}, params, additionalParams, {previousLocation: previousLocation});
//abort every operation that can be executed //abort every operation that can be executed
promise.abortAll(); promise.abortAll();
previousLocation = window.location.href; previousLocation = window.location.href;
var presenter = App.DI.get(presenterName); var presenter = App.DI.get(presenterName);
presenter.name = presenterName; presenter.name = presenterName;
presenterManager.switchContentPresenter(presenter, params); presenterManager.switchContentPresenter(presenter, params);
})); }));
} }
function dispatch() { function dispatch() {
var url = document.location.hash; var url = document.location.hash;
for (var i = 0; i < routes.length; i ++) { for (var i = 0; i < routes.length; i ++) {
var route = routes[i]; var route = routes[i];
if (route.match(url)) { if (route.match(url)) {
route.callback(route.params); route.callback(route.params);
return true; return true;
} }
} }
navigateInplace('#/404', true); navigateInplace('#/404', true);
return false; return false;
} }
function parseComplexParamValue(value) { function parseComplexParamValue(value) {
var result = {}; var result = {};
var params = (value || '').split(/;/); var params = (value || '').split(/;/);
for (var i = 0; i < params.length; i ++) { for (var i = 0; i < params.length; i ++) {
var param = params[i]; var param = params[i];
if (!param) { if (!param) {
continue; continue;
} }
var kv = param.split(/=/); var kv = param.split(/=/);
result[kv[0]] = kv[1]; result[kv[0]] = kv[1];
} }
return result; return result;
} }
function Route(definition, callback) { function Route(definition, callback) {
var possibleRoutes = getPossibleRoutes(definition); var possibleRoutes = getPossibleRoutes(definition);
function getPossibleRoutes(routeDefinition) { function getPossibleRoutes(routeDefinition) {
var parts = []; var parts = [];
var re = new RegExp('\\(([^}]+?)\\)', 'g'); var re = new RegExp('\\(([^}]+?)\\)', 'g');
while (true) { while (true) {
var text = re.exec(routeDefinition); var text = re.exec(routeDefinition);
if (!text) { if (!text) {
break; break;
} }
parts.push(text[1]); parts.push(text[1]);
} }
var possibleRoutes = [routeDefinition.split('(')[0]]; var possibleRoutes = [routeDefinition.split('(')[0]];
for (var i = 0; i < parts.length; i ++) { for (var i = 0; i < parts.length; i ++) {
possibleRoutes.push(possibleRoutes[possibleRoutes.length - 1] + parts[i]); possibleRoutes.push(possibleRoutes[possibleRoutes.length - 1] + parts[i]);
} }
return possibleRoutes; return possibleRoutes;
} }
function match(url) { function match(url) {
var params = {}; var params = {};
for (var i = 0; i < possibleRoutes.length; i ++) { for (var i = 0; i < possibleRoutes.length; i ++) {
var possibleRoute = possibleRoutes[i]; var possibleRoute = possibleRoutes[i];
var compare = url; var compare = url;
var possibleRouteParts = possibleRoute.split('/'); var possibleRouteParts = possibleRoute.split('/');
var compareParts = compare.split('/'); var compareParts = compare.split('/');
if (possibleRoute.search(':') > 0) { if (possibleRoute.search(':') > 0) {
for (var j = 0; j < possibleRouteParts.length; j ++) { for (var j = 0; j < possibleRouteParts.length; j ++) {
if ((j < compareParts.length) && (possibleRouteParts[j].charAt(0) === ':')) { if ((j < compareParts.length) && (possibleRouteParts[j].charAt(0) === ':')) {
var key = possibleRouteParts[j].substring(1); var key = possibleRouteParts[j].substring(1);
var value = compareParts[j]; var value = compareParts[j];
if (key.charAt(0) === '!') { if (key.charAt(0) === '!') {
key = key.substring(1); key = key.substring(1);
value = parseComplexParamValue(value); value = parseComplexParamValue(value);
} }
params[key] = value; params[key] = value;
compareParts[j] = possibleRouteParts[j]; compareParts[j] = possibleRouteParts[j];
compare = compareParts.join('/'); compare = compareParts.join('/');
} }
} }
} }
if (possibleRoute === compare) { if (possibleRoute === compare) {
this.params = params; this.params = params;
return true; return true;
} }
} }
return false; return false;
} }
this.match = match; this.match = match;
this.callback = callback; this.callback = callback;
} }
return { return {
start: start, start: start,
navigate: navigate, navigate: navigate,
navigateInplace: navigateInplace, navigateInplace: navigateInplace,
navigateToMainPage: navigateToMainPage, navigateToMainPage: navigateToMainPage,
}; };
}; };
App.DI.registerSingleton('router', ['_', 'jQuery', 'promise', 'util', 'appState', 'presenterManager'], App.Router); App.DI.registerSingleton('router', ['_', 'jQuery', 'promise', 'util', 'appState', 'presenterManager'], App.Router);

View file

@ -3,83 +3,83 @@ App.Services = App.Services || {};
App.Services.PostsAroundCalculator = function(_, promise, util, pager) { App.Services.PostsAroundCalculator = function(_, promise, util, pager) {
pager.init({url: '/posts'}); pager.init({url: '/posts'});
function resetCache() { function resetCache() {
pager.resetCache(); pager.resetCache();
} }
function getLinksToPostsAround(query, postId) { function getLinksToPostsAround(query, postId) {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
pager.setSearchParams(query); pager.setSearchParams(query);
pager.setPage(query.page); pager.setPage(query.page);
promise.wait(pager.retrieveCached()) promise.wait(pager.retrieveCached())
.then(function(response) { .then(function(response) {
var postIds = _.pluck(response.entities, 'id'); var postIds = _.pluck(response.entities, 'id');
var position = _.indexOf(postIds, postId); var position = _.indexOf(postIds, postId);
if (position === -1) { if (position === -1) {
resolve(null, null); resolve(null, null);
} }
promise.wait( promise.wait(
getLinkToPostAround(postIds, position, query.page, -1), getLinkToPostAround(postIds, position, query.page, -1),
getLinkToPostAround(postIds, position, query.page, 1)) getLinkToPostAround(postIds, position, query.page, 1))
.then(function(nextPostUrl, prevPostUrl) { .then(function(nextPostUrl, prevPostUrl) {
resolve(nextPostUrl, prevPostUrl); resolve(nextPostUrl, prevPostUrl);
}).fail(function() { }).fail(function() {
reject(); reject();
}); });
}).fail(function() { }).fail(function() {
reject(); reject();
}); });
}); });
} }
function getLinkToPostAround(postIds, position, page, direction) { function getLinkToPostAround(postIds, position, page, direction) {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
if (position + direction >= 0 && position + direction < postIds.length) { if (position + direction >= 0 && position + direction < postIds.length) {
var url = util.appendComplexRouteParam( var url = util.appendComplexRouteParam(
'#/post/' + postIds[position + direction], '#/post/' + postIds[position + direction],
util.simplifySearchQuery( util.simplifySearchQuery(
_.extend( _.extend(
{page: page}, {page: page},
pager.getSearchParams()))); pager.getSearchParams())));
resolve(url); resolve(url);
} else if (page + direction >= 1) { } else if (page + direction >= 1) {
pager.setPage(page + direction); pager.setPage(page + direction);
promise.wait(pager.retrieveCached()) promise.wait(pager.retrieveCached())
.then(function(response) { .then(function(response) {
if (response.entities.length) { if (response.entities.length) {
var post = direction === - 1 ? var post = direction === - 1 ?
_.last(response.entities) : _.last(response.entities) :
_.first(response.entities); _.first(response.entities);
var url = util.appendComplexRouteParam( var url = util.appendComplexRouteParam(
'#/post/' + post.id, '#/post/' + post.id,
util.simplifySearchQuery( util.simplifySearchQuery(
_.extend( _.extend(
{page: page + direction}, {page: page + direction},
pager.getSearchParams()))); pager.getSearchParams())));
resolve(url); resolve(url);
} else { } else {
resolve(null); resolve(null);
} }
}).fail(function() { }).fail(function() {
reject(); reject();
}); });
} else { } else {
resolve(null); resolve(null);
} }
}); });
} }
return { return {
resetCache: resetCache, resetCache: resetCache,
getLinksToPostsAround: getLinksToPostsAround, getLinksToPostsAround: getLinksToPostsAround,
}; };
}; };
App.DI.register('postsAroundCalculator', ['_', 'promise', 'util', 'pager'], App.Services.PostsAroundCalculator); App.DI.register('postsAroundCalculator', ['_', 'promise', 'util', 'pager'], App.Services.PostsAroundCalculator);

View file

@ -2,31 +2,31 @@ var App = App || {};
App.Services = App.Services || {}; App.Services = App.Services || {};
App.Services.TagList = function(jQuery) { App.Services.TagList = function(jQuery) {
var tags = []; var tags = [];
function refreshTags() { function refreshTags() {
jQuery.ajax({ jQuery.ajax({
success: function(data, textStatus, xhr) { success: function(data, textStatus, xhr) {
tags = data; tags = data;
}, },
error: function(xhr, textStatus, errorThrown) { error: function(xhr, textStatus, errorThrown) {
console.log(new Error(errorThrown)); console.log(new Error(errorThrown));
}, },
type: 'GET', type: 'GET',
url: '/data/tags.json', url: '/data/tags.json',
}); });
} }
function getTags() { function getTags() {
return tags; return tags;
} }
refreshTags(); refreshTags();
return { return {
refreshTags: refreshTags, refreshTags: refreshTags,
getTags: getTags, getTags: getTags,
}; };
}; };
App.DI.registerSingleton('tagList', ['jQuery'], App.Services.TagList); App.DI.registerSingleton('tagList', ['jQuery'], App.Services.TagList);

View file

@ -2,50 +2,50 @@ var App = App || {};
App.State = function() { App.State = function() {
var properties = {}; var properties = {};
var observers = {}; var observers = {};
function get(key) { function get(key) {
return properties[key]; return properties[key];
} }
function set(key, value) { function set(key, value) {
properties[key] = value; properties[key] = value;
if (key in observers) { if (key in observers) {
for (var observerName in observers[key]) { for (var observerName in observers[key]) {
if (observers[key].hasOwnProperty(observerName)) { if (observers[key].hasOwnProperty(observerName)) {
observers[key][observerName](key, value); observers[key][observerName](key, value);
} }
} }
} }
} }
function startObserving(key, observerName, callback) { function startObserving(key, observerName, callback) {
if (!(key in observers)) { if (!(key in observers)) {
observers[key] = {}; observers[key] = {};
} }
if (!(observerName in observers[key])) { if (!(observerName in observers[key])) {
observers[key][observerName] = {}; observers[key][observerName] = {};
} }
observers[key][observerName] = callback; observers[key][observerName] = callback;
} }
function stopObserving(key, observerName) { function stopObserving(key, observerName) {
if (!(key in observers)) { if (!(key in observers)) {
return; return;
} }
if (!(observerName in observers[key])) { if (!(observerName in observers[key])) {
return; return;
} }
delete observers[key][observerName]; delete observers[key][observerName];
} }
return { return {
get: get, get: get,
set: set, set: set,
startObserving: startObserving, startObserving: startObserving,
stopObserving: stopObserving, stopObserving: stopObserving,
}; };
}; };

View file

@ -2,146 +2,146 @@ var App = App || {};
App.Util = App.Util || {}; App.Util = App.Util || {};
App.Util.Draggable = function(jQuery) { App.Util.Draggable = function(jQuery) {
var KEY_LEFT = 37; var KEY_LEFT = 37;
var KEY_UP = 38; var KEY_UP = 38;
var KEY_RIGHT = 39; var KEY_RIGHT = 39;
var KEY_DOWN = 40; var KEY_DOWN = 40;
function relativeDragStrategy($element) { function relativeDragStrategy($element) {
var $parent = $element.parent(); var $parent = $element.parent();
var delta; var delta;
var x = $element.offset().left - $parent.offset().left; var x = $element.offset().left - $parent.offset().left;
var y = $element.offset().top - $parent.offset().top; var y = $element.offset().top - $parent.offset().top;
var getPosition = function() { var getPosition = function() {
return {x: x, y: y}; return {x: x, y: y};
}; };
var setPosition = function(newX, newY) { var setPosition = function(newX, newY) {
x = newX; x = newX;
y = newY; y = newY;
var screenX = Math.min(Math.max(newX, 0), $parent.outerWidth() - $element.outerWidth()); var screenX = Math.min(Math.max(newX, 0), $parent.outerWidth() - $element.outerWidth());
var screenY = Math.min(Math.max(newY, 0), $parent.outerHeight() - $element.outerHeight()); var screenY = Math.min(Math.max(newY, 0), $parent.outerHeight() - $element.outerHeight());
screenX *= 100.0 / $parent.outerWidth(); screenX *= 100.0 / $parent.outerWidth();
screenY *= 100.0 / $parent.outerHeight(); screenY *= 100.0 / $parent.outerHeight();
$element.css({ $element.css({
left: screenX + '%', left: screenX + '%',
top: screenY + '%'}); top: screenY + '%'});
}; };
return { return {
mouseClicked: function(e) { mouseClicked: function(e) {
delta = { delta = {
x: $element.offset().left - e.clientX, x: $element.offset().left - e.clientX,
y: $element.offset().top - e.clientY, y: $element.offset().top - e.clientY,
}; };
}, },
mouseMoved: function(e) { mouseMoved: function(e) {
setPosition( setPosition(
e.clientX + delta.x - $parent.offset().left, e.clientX + delta.x - $parent.offset().left,
e.clientY + delta.y - $parent.offset().top); e.clientY + delta.y - $parent.offset().top);
}, },
getPosition: getPosition, getPosition: getPosition,
setPosition: setPosition, setPosition: setPosition,
}; };
} }
function absoluteDragStrategy($element) { function absoluteDragStrategy($element) {
var delta; var delta;
var x = $element.offset().left; var x = $element.offset().left;
var y = $element.offset().top; var y = $element.offset().top;
var getPosition = function() { var getPosition = function() {
return {x: x, y: y}; return {x: x, y: y};
}; };
var setPosition = function(newX, newY) { var setPosition = function(newX, newY) {
x = newX; x = newX;
y = newY; y = newY;
$element.css({ $element.css({
left: x + 'px', left: x + 'px',
top: y + 'px'}); top: y + 'px'});
}; };
return { return {
mouseClicked: function(e) { mouseClicked: function(e) {
delta = { delta = {
x: $element.position().left - e.clientX, x: $element.position().left - e.clientX,
y: $element.position().top - e.clientY, y: $element.position().top - e.clientY,
}; };
}, },
mouseMoved: function(e) { mouseMoved: function(e) {
setPosition(e.clientX + delta.x, e.clientY + delta.y); setPosition(e.clientX + delta.x, e.clientY + delta.y);
}, },
getPosition: getPosition, getPosition: getPosition,
setPosition: setPosition, setPosition: setPosition,
}; };
} }
function makeDraggable($element, dragStrategy, enableHotkeys) { function makeDraggable($element, dragStrategy, enableHotkeys) {
var strategy = dragStrategy($element); var strategy = dragStrategy($element);
$element.data('drag-strategy', strategy); $element.data('drag-strategy', strategy);
$element.addClass('draggable'); $element.addClass('draggable');
$element.mousedown(function(e) { $element.mousedown(function(e) {
if (e.target !== $element.get(0)) { if (e.target !== $element.get(0)) {
return; return;
} }
e.preventDefault(); e.preventDefault();
$element.focus(); $element.focus();
$element.addClass('dragging'); $element.addClass('dragging');
strategy.mouseClicked(e); strategy.mouseClicked(e);
jQuery(window).bind('mousemove.elemmove', function(e) { jQuery(window).bind('mousemove.elemmove', function(e) {
strategy.mouseMoved(e); strategy.mouseMoved(e);
}).bind('mouseup.elemmove', function(e) { }).bind('mouseup.elemmove', function(e) {
e.preventDefault(); e.preventDefault();
strategy.mouseMoved(e); strategy.mouseMoved(e);
$element.removeClass('dragging'); $element.removeClass('dragging');
jQuery(window).unbind('mousemove.elemmove'); jQuery(window).unbind('mousemove.elemmove');
jQuery(window).unbind('mouseup.elemmove'); jQuery(window).unbind('mouseup.elemmove');
}); });
}); });
if (enableHotkeys) { if (enableHotkeys) {
$element.keydown(function(e) { $element.keydown(function(e) {
var position = strategy.getPosition(); var position = strategy.getPosition();
var oldPosition = {x: position.x, y: position.y}; var oldPosition = {x: position.x, y: position.y};
if (e.shiftKey) { if (e.shiftKey) {
return; return;
} }
var delta = e.ctrlKey ? 10 : 1; var delta = e.ctrlKey ? 10 : 1;
if (e.which === KEY_LEFT) { if (e.which === KEY_LEFT) {
position.x -= delta; position.x -= delta;
} else if (e.which === KEY_RIGHT) { } else if (e.which === KEY_RIGHT) {
position.x += delta; position.x += delta;
} else if (e.which === KEY_UP) { } else if (e.which === KEY_UP) {
position.y -= delta; position.y -= delta;
} else if (e.which === KEY_DOWN) { } else if (e.which === KEY_DOWN) {
position.y += delta; position.y += delta;
} }
if (position.x !== oldPosition.x || position.y !== oldPosition.y) { if (position.x !== oldPosition.x || position.y !== oldPosition.y) {
e.stopPropagation(); e.stopPropagation();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
e.preventDefault(); e.preventDefault();
strategy.setPosition(position.x, position.y); strategy.setPosition(position.x, position.y);
} }
}); });
} }
} }
return { return {
makeDraggable: makeDraggable, makeDraggable: makeDraggable,
absoluteDragStrategy: absoluteDragStrategy, absoluteDragStrategy: absoluteDragStrategy,
relativeDragStrategy: relativeDragStrategy, relativeDragStrategy: relativeDragStrategy,
}; };
}; };

View file

@ -3,265 +3,265 @@ App.Util = App.Util || {};
App.Util.Misc = function(_, jQuery, marked, promise) { App.Util.Misc = function(_, jQuery, marked, promise) {
var exitConfirmationEnabled = false; var exitConfirmationEnabled = false;
function transparentPixel() { function transparentPixel() {
return 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; return 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
} }
function enableExitConfirmation() { function enableExitConfirmation() {
exitConfirmationEnabled = true; exitConfirmationEnabled = true;
jQuery(window).bind('beforeunload', function(e) { jQuery(window).bind('beforeunload', function(e) {
return 'There are unsaved changes.'; return 'There are unsaved changes.';
}); });
} }
function disableExitConfirmation() { function disableExitConfirmation() {
exitConfirmationEnabled = false; exitConfirmationEnabled = false;
jQuery(window).unbind('beforeunload'); jQuery(window).unbind('beforeunload');
} }
function isExitConfirmationEnabled() { function isExitConfirmationEnabled() {
return exitConfirmationEnabled; return exitConfirmationEnabled;
} }
function loadImagesNicely($img) { function loadImagesNicely($img) {
if (!$img.get(0).complete) { if (!$img.get(0).complete) {
$img.addClass('loading'); $img.addClass('loading');
$img.css({opacity: 0}); $img.css({opacity: 0});
var $div = jQuery('<div>Loading ' + $img.attr('alt') + '&hellip;</div>'); var $div = jQuery('<div>Loading ' + $img.attr('alt') + '&hellip;</div>');
var width = $img.width(); var width = $img.width();
var height = $img.height(); var height = $img.height();
if (width > 50 && height > 50) { if (width > 50 && height > 50) {
$div.css({ $div.css({
position: 'absolute', position: 'absolute',
width: width + 'px', width: width + 'px',
height: height + 'px', height: height + 'px',
color: 'rgba(0, 0, 0, 0.15)', color: 'rgba(0, 0, 0, 0.15)',
zIndex: -1, zIndex: -1,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
textAlign: 'center'}); textAlign: 'center'});
$div.insertBefore($img); $div.insertBefore($img);
$div.offset($img.offset()); $div.offset($img.offset());
} }
$img.bind('load', function() { $img.bind('load', function() {
$img.animate({opacity: 1}, 'fast'); $img.animate({opacity: 1}, 'fast');
$img.removeClass('loading'); $img.removeClass('loading');
$div.fadeOut($div.remove); $div.fadeOut($div.remove);
}); });
} }
} }
function promiseTemplate(templateName) { function promiseTemplate(templateName) {
return promiseTemplateFromDOM(templateName) || return promiseTemplateFromDOM(templateName) ||
promiseTemplateWithAJAX(templateName); promiseTemplateWithAJAX(templateName);
} }
function promiseTemplateFromDOM(templateName) { function promiseTemplateFromDOM(templateName) {
var $template = jQuery('#' + templateName + '-template'); var $template = jQuery('#' + templateName + '-template');
if ($template.length) { if ($template.length) {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
resolve(_.template($template.html())); resolve(_.template($template.html()));
}); });
} }
return null; return null;
} }
function promiseTemplateWithAJAX(templateName) { function promiseTemplateWithAJAX(templateName) {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
var templatesDir = '/templates'; var templatesDir = '/templates';
var templateUrl = templatesDir + '/' + templateName + '.tpl'; var templateUrl = templatesDir + '/' + templateName + '.tpl';
jQuery.ajax({ jQuery.ajax({
url: templateUrl, url: templateUrl,
method: 'GET', method: 'GET',
success: function(data, textStatus, xhr) { success: function(data, textStatus, xhr) {
resolve(_.template(data)); resolve(_.template(data));
}, },
error: function(xhr, textStatus, errorThrown) { error: function(xhr, textStatus, errorThrown) {
console.log(new Error('Error while loading template ' + templateName + ': ' + errorThrown)); console.log(new Error('Error while loading template ' + templateName + ': ' + errorThrown));
reject(); reject();
}, },
}); });
}); });
} }
function formatRelativeTime(timeString) { function formatRelativeTime(timeString) {
if (!timeString) { if (!timeString) {
return 'never'; return 'never';
} }
var then = Date.parse(timeString); var then = Date.parse(timeString);
var now = Date.now(); var now = Date.now();
var difference = Math.abs(now - then); var difference = Math.abs(now - then);
var future = now < then; var future = now < then;
var text = (function(difference) { var text = (function(difference) {
var mul = 1000; var mul = 1000;
var prevMul; var prevMul;
mul *= 60; mul *= 60;
if (difference < mul) { if (difference < mul) {
return 'a few seconds'; return 'a few seconds';
} else if (difference < mul * 2) { } else if (difference < mul * 2) {
return 'a minute'; return 'a minute';
} }
prevMul = mul; mul *= 60; prevMul = mul; mul *= 60;
if (difference < mul) { if (difference < mul) {
return Math.round(difference / prevMul) + ' minutes'; return Math.round(difference / prevMul) + ' minutes';
} else if (difference < mul * 2) { } else if (difference < mul * 2) {
return 'an hour'; return 'an hour';
} }
prevMul = mul; mul *= 24; prevMul = mul; mul *= 24;
if (difference < mul) { if (difference < mul) {
return Math.round(difference / prevMul) + ' hours'; return Math.round(difference / prevMul) + ' hours';
} else if (difference < mul * 2) { } else if (difference < mul * 2) {
return 'a day'; return 'a day';
} }
prevMul = mul; mul *= 30.42; prevMul = mul; mul *= 30.42;
if (difference < mul) { if (difference < mul) {
return Math.round(difference / prevMul) + ' days'; return Math.round(difference / prevMul) + ' days';
} else if (difference < mul * 2) { } else if (difference < mul * 2) {
return 'a month'; return 'a month';
} }
prevMul = mul; mul *= 12; prevMul = mul; mul *= 12;
if (difference < mul) { if (difference < mul) {
return Math.round(difference / prevMul) + ' months'; return Math.round(difference / prevMul) + ' months';
} else if (difference < mul * 2) { } else if (difference < mul * 2) {
return 'a year'; return 'a year';
} }
return Math.round(difference / mul) + ' years'; return Math.round(difference / mul) + ' years';
})(difference); })(difference);
if (text === 'a day') { if (text === 'a day') {
return future ? 'tomorrow' : 'yesterday'; return future ? 'tomorrow' : 'yesterday';
} }
return future ? 'in ' + text : text + ' ago'; return future ? 'in ' + text : text + ' ago';
} }
function formatAbsoluteTime(timeString) { function formatAbsoluteTime(timeString) {
var time = new Date(Date.parse(timeString)); var time = new Date(Date.parse(timeString));
return time.toString(); return time.toString();
} }
function formatUnits(number, base, suffixes, callback) { function formatUnits(number, base, suffixes, callback) {
if (!number && number !== 0) { if (!number && number !== 0) {
return NaN; return NaN;
} }
number *= 1.0; number *= 1.0;
var suffix = suffixes.shift(); var suffix = suffixes.shift();
while (number >= base && suffixes.length > 0) { while (number >= base && suffixes.length > 0) {
suffix = suffixes.shift(); suffix = suffixes.shift();
number /= base; number /= base;
} }
if (typeof(callback) === 'undefined') { if (typeof(callback) === 'undefined') {
callback = function(number, suffix) { callback = function(number, suffix) {
return suffix ? number.toFixed(1) + suffix : number; return suffix ? number.toFixed(1) + suffix : number;
}; };
} }
return callback(number, suffix); return callback(number, suffix);
} }
function formatFileSize(fileSize) { function formatFileSize(fileSize) {
return formatUnits( return formatUnits(
fileSize, fileSize,
1024, 1024,
['B', 'K', 'M', 'G'], ['B', 'K', 'M', 'G'],
function(number, suffix) { function(number, suffix) {
var decimalPlaces = number < 20 && suffix !== 'B' ? 1 : 0; var decimalPlaces = number < 20 && suffix !== 'B' ? 1 : 0;
return number.toFixed(decimalPlaces) + suffix; return number.toFixed(decimalPlaces) + suffix;
}); });
} }
function formatMarkdown(text) { function formatMarkdown(text) {
var renderer = new marked.Renderer(); var renderer = new marked.Renderer();
var options = { var options = {
renderer: renderer, renderer: renderer,
breaks: true, breaks: true,
sanitize: true, sanitize: true,
smartypants: true, smartypants: true,
}; };
var preDecorator = function(text) { var preDecorator = function(text) {
//prevent ^#... from being treated as headers, due to tag permalinks //prevent ^#... from being treated as headers, due to tag permalinks
text = text.replace(/^#/g, '%%%#'); text = text.replace(/^#/g, '%%%#');
//fix \ before ~ being stripped away //fix \ before ~ being stripped away
text = text.replace(/\\~/g, '%%%T'); text = text.replace(/\\~/g, '%%%T');
return text; return text;
}; };
var postDecorator = function(text) { var postDecorator = function(text) {
//restore fixes //restore fixes
text = text.replace(/%%%T/g, '\\~'); text = text.replace(/%%%T/g, '\\~');
text = text.replace(/%%%#/g, '#'); text = text.replace(/%%%#/g, '#');
//search permalinks //search permalinks
text = text.replace(/\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/ig, '<a href="#/posts/query=$1"><code>$1</code></a>'); text = text.replace(/\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/ig, '<a href="#/posts/query=$1"><code>$1</code></a>');
//spoilers //spoilers
text = text.replace(/\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/ig, '<span class="spoiler">$1</span>'); text = text.replace(/\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/ig, '<span class="spoiler">$1</span>');
//[small] //[small]
text = text.replace(/\[small\]((?:[^\[]|\[(?!\/?small\]))+)\[\/small\]/ig, '<small>$1</small>'); text = text.replace(/\[small\]((?:[^\[]|\[(?!\/?small\]))+)\[\/small\]/ig, '<small>$1</small>');
//strike-through //strike-through
text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, '$1<del>$3</del>'); text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, '$1<del>$3</del>');
text = text.replace(/\\~/g, '~'); text = text.replace(/\\~/g, '~');
//post premalinks //post premalinks
text = text.replace(/(^|[\s<>\(\)\[\]])@(\d+)/g, '$1<a href="#/post/$2"><code>@$2</code></a>'); text = text.replace(/(^|[\s<>\(\)\[\]])@(\d+)/g, '$1<a href="#/post/$2"><code>@$2</code></a>');
//user permalinks //user permalinks
text = text.replace(/(^|[\s<>\(\)\[\]])\+([a-zA-Z0-9_-]+)/g, '$1<a href="#/user/$2"><code>+$2</code></a>'); text = text.replace(/(^|[\s<>\(\)\[\]])\+([a-zA-Z0-9_-]+)/g, '$1<a href="#/user/$2"><code>+$2</code></a>');
//tag permalinks //tag permalinks
text = text.replace(/(^|[\s<>\(\)\[\]])\#([^\s<>/\\]+)/g, '$1<a href="#/posts/query=$2"><code>#$2</code></a>'); text = text.replace(/(^|[\s<>\(\)\[\]])\#([^\s<>/\\]+)/g, '$1<a href="#/posts/query=$2"><code>#$2</code></a>');
return text; return text;
}; };
return postDecorator(marked(preDecorator(text), options)); return postDecorator(marked(preDecorator(text), options));
} }
function appendComplexRouteParam(baseUri, params) { function appendComplexRouteParam(baseUri, params) {
var result = baseUri + '/'; var result = baseUri + '/';
_.each(params, function(v, k) { _.each(params, function(v, k) {
if (typeof(v) !== 'undefined') { if (typeof(v) !== 'undefined') {
result += k + '=' + v + ';'; result += k + '=' + v + ';';
} }
}); });
return result.slice(0, -1); return result.slice(0, -1);
} }
function simplifySearchQuery(query) { function simplifySearchQuery(query) {
if (typeof(query) === 'undefined') { if (typeof(query) === 'undefined') {
return {}; return {};
} }
if (query.page === 1) { if (query.page === 1) {
delete query.page; delete query.page;
} }
query = _.pick(query, _.identity); //remove falsy values query = _.pick(query, _.identity); //remove falsy values
return query; return query;
} }
return { return {
promiseTemplate: promiseTemplate, promiseTemplate: promiseTemplate,
formatRelativeTime: formatRelativeTime, formatRelativeTime: formatRelativeTime,
formatAbsoluteTime: formatAbsoluteTime, formatAbsoluteTime: formatAbsoluteTime,
formatFileSize: formatFileSize, formatFileSize: formatFileSize,
formatMarkdown: formatMarkdown, formatMarkdown: formatMarkdown,
enableExitConfirmation: enableExitConfirmation, enableExitConfirmation: enableExitConfirmation,
disableExitConfirmation: disableExitConfirmation, disableExitConfirmation: disableExitConfirmation,
isExitConfirmationEnabled: isExitConfirmationEnabled, isExitConfirmationEnabled: isExitConfirmationEnabled,
transparentPixel: transparentPixel, transparentPixel: transparentPixel,
loadImagesNicely: loadImagesNicely, loadImagesNicely: loadImagesNicely,
appendComplexRouteParam: appendComplexRouteParam, appendComplexRouteParam: appendComplexRouteParam,
simplifySearchQuery: simplifySearchQuery, simplifySearchQuery: simplifySearchQuery,
}; };
}; };

View file

@ -2,108 +2,108 @@ var App = App || {};
App.Util = App.Util || {}; App.Util = App.Util || {};
App.Util.Resizable = function(jQuery) { App.Util.Resizable = function(jQuery) {
var KEY_LEFT = 37; var KEY_LEFT = 37;
var KEY_UP = 38; var KEY_UP = 38;
var KEY_RIGHT = 39; var KEY_RIGHT = 39;
var KEY_DOWN = 40; var KEY_DOWN = 40;
function relativeResizeStrategy($element) { function relativeResizeStrategy($element) {
var $parent = $element.parent(); var $parent = $element.parent();
var delta; var delta;
var width = $element.width(); var width = $element.width();
var height = $element.height(); var height = $element.height();
var getSize = function() { var getSize = function() {
return {width: width, height: height}; return {width: width, height: height};
}; };
var setSize = function(newWidth, newHeight) { var setSize = function(newWidth, newHeight) {
width = newWidth; width = newWidth;
height = newHeight; height = newHeight;
var screenWidth = Math.min(Math.max(width, 20), $parent.outerWidth() + $parent.offset().left - $element.offset().left); var screenWidth = Math.min(Math.max(width, 20), $parent.outerWidth() + $parent.offset().left - $element.offset().left);
var screenHeight = Math.min(Math.max(height, 20), $parent.outerHeight() + $parent.offset().top - $element.offset().top); var screenHeight = Math.min(Math.max(height, 20), $parent.outerHeight() + $parent.offset().top - $element.offset().top);
screenWidth *= 100.0 / $parent.outerWidth(); screenWidth *= 100.0 / $parent.outerWidth();
screenHeight *= 100.0 / $parent.outerHeight(); screenHeight *= 100.0 / $parent.outerHeight();
$element.css({ $element.css({
width: screenWidth + '%', width: screenWidth + '%',
height: screenHeight + '%'}); height: screenHeight + '%'});
}; };
return { return {
mouseClicked: function(e) { mouseClicked: function(e) {
delta = { delta = {
x: $element.width() - e.clientX, x: $element.width() - e.clientX,
y: $element.height() - e.clientY, y: $element.height() - e.clientY,
}; };
}, },
mouseMoved: function(e) { mouseMoved: function(e) {
setSize( setSize(
e.clientX + delta.x, e.clientX + delta.x,
e.clientY + delta.y); e.clientY + delta.y);
}, },
getSize: getSize, getSize: getSize,
setSize: setSize, setSize: setSize,
}; };
} }
function makeResizable($element, enableHotkeys) { function makeResizable($element, enableHotkeys) {
var $resizer = jQuery('<div class="resizer"></div>'); var $resizer = jQuery('<div class="resizer"></div>');
var strategy = relativeResizeStrategy($element); var strategy = relativeResizeStrategy($element);
$element.append($resizer); $element.append($resizer);
$resizer.mousedown(function(e) { $resizer.mousedown(function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
$element.focus(); $element.focus();
$element.addClass('resizing'); $element.addClass('resizing');
strategy.mouseClicked(e); strategy.mouseClicked(e);
jQuery(window).bind('mousemove.elemsize', function(e) { jQuery(window).bind('mousemove.elemsize', function(e) {
strategy.mouseMoved(e); strategy.mouseMoved(e);
}).bind('mouseup.elemsize', function(e) { }).bind('mouseup.elemsize', function(e) {
e.preventDefault(); e.preventDefault();
strategy.mouseMoved(e); strategy.mouseMoved(e);
$element.removeClass('resizing'); $element.removeClass('resizing');
jQuery(window).unbind('mousemove.elemsize'); jQuery(window).unbind('mousemove.elemsize');
jQuery(window).unbind('mouseup.elemsize'); jQuery(window).unbind('mouseup.elemsize');
}); });
}); });
if (enableHotkeys) { if (enableHotkeys) {
$element.keydown(function(e) { $element.keydown(function(e) {
var size = strategy.getSize(); var size = strategy.getSize();
var oldSize = {width: size.width, height: size.height}; var oldSize = {width: size.width, height: size.height};
if (!e.shiftKey) { if (!e.shiftKey) {
return; return;
} }
var delta = e.ctrlKey ? 10 : 1; var delta = e.ctrlKey ? 10 : 1;
if (e.which === KEY_LEFT) { if (e.which === KEY_LEFT) {
size.width -= delta; size.width -= delta;
} else if (e.which === KEY_RIGHT) { } else if (e.which === KEY_RIGHT) {
size.width += delta; size.width += delta;
} else if (e.which === KEY_UP) { } else if (e.which === KEY_UP) {
size.height -= delta; size.height -= delta;
} else if (e.which === KEY_DOWN) { } else if (e.which === KEY_DOWN) {
size.height += delta; size.height += delta;
} }
if (size.width !== oldSize.width || size.height !== oldSize.height) { if (size.width !== oldSize.width || size.height !== oldSize.height) {
e.stopPropagation(); e.stopPropagation();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
e.preventDefault(); e.preventDefault();
strategy.setSize(size.width, size.height); strategy.setSize(size.width, size.height);
} }
}); });
} }
} }
return { return {
makeResizable: makeResizable, makeResizable: makeResizable,
}; };
}; };

View file

@ -5,42 +5,42 @@ $startTime = microtime(true);
final class Bootstrap final class Bootstrap
{ {
private static $startTime; private static $startTime;
public static function init($startTime) public static function init($startTime)
{ {
self::$startTime = $startTime; self::$startTime = $startTime;
self::setTimezone(); self::setTimezone();
self::turnErrorsIntoExceptions(); self::turnErrorsIntoExceptions();
self::initAutoloader(); self::initAutoloader();
} }
public static function getStartTime() public static function getStartTime()
{ {
return self::$startTime; return self::$startTime;
} }
private static function setTimezone() private static function setTimezone()
{ {
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
} }
private static function initAutoloader() private static function initAutoloader()
{ {
require(__DIR__ require(__DIR__
. DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..'
. DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'vendor'
. DIRECTORY_SEPARATOR . 'autoload.php'); . DIRECTORY_SEPARATOR . 'autoload.php');
} }
private static function turnErrorsIntoExceptions() private static function turnErrorsIntoExceptions()
{ {
set_error_handler( set_error_handler(
function($errno, $errstr, $errfile, $errline, array $errcontext) function($errno, $errstr, $errfile, $errline, array $errcontext)
{ {
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
}); });
} }
} }
Bootstrap::init($startTime); Bootstrap::init($startTime);

View file

@ -3,79 +3,79 @@ namespace Szurubooru;
class Config extends \ArrayObject class Config extends \ArrayObject
{ {
private $dataDirectory; private $dataDirectory;
private $publicDataDirectory; private $publicDataDirectory;
public function __construct($dataDirectory, $publicDataDirectory) public function __construct($dataDirectory, $publicDataDirectory)
{ {
$this->setFlags($this->getArrayObjectFlags()); $this->setFlags($this->getArrayObjectFlags());
$this->dataDirectory = $dataDirectory; $this->dataDirectory = $dataDirectory;
$this->publicDataDirectory = $publicDataDirectory; $this->publicDataDirectory = $publicDataDirectory;
$this->tryLoadFromIni([ $this->tryLoadFromIni([
$dataDirectory . DIRECTORY_SEPARATOR . 'config.ini', $dataDirectory . DIRECTORY_SEPARATOR . 'config.ini',
$dataDirectory . DIRECTORY_SEPARATOR . 'local.ini']); $dataDirectory . DIRECTORY_SEPARATOR . 'local.ini']);
} }
public function tryLoadFromIni($configPaths) public function tryLoadFromIni($configPaths)
{ {
if (!is_array($configPaths)) if (!is_array($configPaths))
$configPaths = [$configPaths]; $configPaths = [$configPaths];
foreach ($configPaths as $configPath) foreach ($configPaths as $configPath)
{ {
if (file_exists($configPath)) if (file_exists($configPath))
$this->loadFromIni($configPath); $this->loadFromIni($configPath);
} }
} }
public function getDataDirectory() public function getDataDirectory()
{ {
return $this->dataDirectory; return $this->dataDirectory;
} }
public function getPublicDataDirectory() public function getPublicDataDirectory()
{ {
return $this->publicDataDirectory; return $this->publicDataDirectory;
} }
public function offsetGet($index) public function offsetGet($index)
{ {
if (!parent::offsetExists($index)) if (!parent::offsetExists($index))
return null; return null;
return parent::offsetGet($index); return parent::offsetGet($index);
} }
public function loadFromIni($configPath) public function loadFromIni($configPath)
{ {
$array = parse_ini_file($configPath, true, INI_SCANNER_RAW); $array = parse_ini_file($configPath, true, INI_SCANNER_RAW);
foreach ($array as $key => $value) foreach ($array as $key => $value)
{ {
if (!is_array($value)) if (!is_array($value))
{ {
$this->offsetSet($key, $value); $this->offsetSet($key, $value);
} }
else else
{ {
$section = $key; $section = $key;
$ptr = $this; $ptr = $this;
foreach (explode('.', $section) as $subSection) foreach (explode('.', $section) as $subSection)
{ {
if (!$ptr->offsetExists($subSection)) if (!$ptr->offsetExists($subSection))
$ptr->offsetSet($subSection, new \ArrayObject([], $this->getArrayObjectFlags())); $ptr->offsetSet($subSection, new \ArrayObject([], $this->getArrayObjectFlags()));
$ptr = $ptr->$subSection; $ptr = $ptr->$subSection;
} }
foreach ($value as $sectionKey => $sectionValue) foreach ($value as $sectionKey => $sectionValue)
$ptr->offsetSet($sectionKey, $sectionValue); $ptr->offsetSet($sectionKey, $sectionValue);
} }
} }
} }
private function getArrayObjectFlags() private function getArrayObjectFlags()
{ {
return parent::ARRAY_AS_PROPS | parent::STD_PROP_LIST; return parent::ARRAY_AS_PROPS | parent::STD_PROP_LIST;
} }
} }

View file

@ -12,300 +12,300 @@ use Szurubooru\Search\Result;
abstract class AbstractDao implements ICrudDao, IBatchDao abstract class AbstractDao implements ICrudDao, IBatchDao
{ {
protected $pdo; protected $pdo;
protected $tableName; protected $tableName;
protected $entityConverter; protected $entityConverter;
protected $driver; protected $driver;
public function __construct( public function __construct(
DatabaseConnection $databaseConnection, DatabaseConnection $databaseConnection,
$tableName, $tableName,
IEntityConverter $entityConverter) IEntityConverter $entityConverter)
{ {
$this->setDatabaseConnection($databaseConnection); $this->setDatabaseConnection($databaseConnection);
$this->tableName = $tableName; $this->tableName = $tableName;
$this->entityConverter = $entityConverter; $this->entityConverter = $entityConverter;
$this->entityConverter->setEntityDecorator(function($entity) $this->entityConverter->setEntityDecorator(function($entity)
{ {
$this->afterLoad($entity); $this->afterLoad($entity);
}); });
} }
public function getTableName() public function getTableName()
{ {
return $this->tableName; return $this->tableName;
} }
public function getEntityConverter() public function getEntityConverter()
{ {
return $this->entityConverter; return $this->entityConverter;
} }
public function save(&$entity) public function save(&$entity)
{ {
$entity = $this->upsert($entity); $entity = $this->upsert($entity);
$this->afterSave($entity); $this->afterSave($entity);
$this->afterBatchSave([$entity]); $this->afterBatchSave([$entity]);
return $entity; return $entity;
} }
public function batchSave(array $entities) public function batchSave(array $entities)
{ {
foreach ($entities as $key => $entity) foreach ($entities as $key => $entity)
{ {
$entities[$key] = $this->upsert($entity); $entities[$key] = $this->upsert($entity);
$this->afterSave($entity); $this->afterSave($entity);
} }
if (count($entities) > 0) if (count($entities) > 0)
$this->afterBatchSave([$entity]); $this->afterBatchSave([$entity]);
return $entities; return $entities;
} }
public function findAll() public function findAll()
{ {
$query = $this->pdo->from($this->tableName); $query = $this->pdo->from($this->tableName);
return $this->arrayToEntities($query); return $this->arrayToEntities($query);
} }
public function findById($entityId) public function findById($entityId)
{ {
return $this->findOneBy($this->getIdColumn(), $entityId); return $this->findOneBy($this->getIdColumn(), $entityId);
} }
public function findByIds($entityIds) public function findByIds($entityIds)
{ {
return $this->findBy($this->getIdColumn(), $entityIds); return $this->findBy($this->getIdColumn(), $entityIds);
} }
public function findFiltered(IFilter $searchFilter) public function findFiltered(IFilter $searchFilter)
{ {
$query = $this->pdo->from($this->tableName); $query = $this->pdo->from($this->tableName);
$orderByString = self::compileOrderBy($searchFilter->getOrder()); $orderByString = self::compileOrderBy($searchFilter->getOrder());
if ($orderByString) if ($orderByString)
$query->orderBy($orderByString); $query->orderBy($orderByString);
$this->decorateQueryFromFilter($query, $searchFilter); $this->decorateQueryFromFilter($query, $searchFilter);
if ($searchFilter->getPageSize() > 0) if ($searchFilter->getPageSize() > 0)
{ {
$query->limit($searchFilter->getPageSize()); $query->limit($searchFilter->getPageSize());
$query->offset($searchFilter->getPageSize() * max(0, $searchFilter->getPageNumber() - 1)); $query->offset($searchFilter->getPageSize() * max(0, $searchFilter->getPageNumber() - 1));
} }
$entities = $this->arrayToEntities(iterator_to_array($query)); $entities = $this->arrayToEntities(iterator_to_array($query));
$query = $this->pdo->from($this->tableName); $query = $this->pdo->from($this->tableName);
$this->decorateQueryFromFilter($query, $searchFilter); $this->decorateQueryFromFilter($query, $searchFilter);
$totalRecords = count($query); $totalRecords = count($query);
$searchResult = new Result(); $searchResult = new Result();
$searchResult->setSearchFilter($searchFilter); $searchResult->setSearchFilter($searchFilter);
$searchResult->setEntities($entities); $searchResult->setEntities($entities);
$searchResult->setTotalRecords($totalRecords); $searchResult->setTotalRecords($totalRecords);
$searchResult->setPageNumber($searchFilter->getPageNumber()); $searchResult->setPageNumber($searchFilter->getPageNumber());
$searchResult->setPageSize($searchFilter->getPageSize()); $searchResult->setPageSize($searchFilter->getPageSize());
return $searchResult; return $searchResult;
} }
public function deleteAll() public function deleteAll()
{ {
foreach ($this->findAll() as $entity) foreach ($this->findAll() as $entity)
{ {
$this->beforeDelete($entity); $this->beforeDelete($entity);
} }
$this->pdo->deleteFrom($this->tableName)->execute(); $this->pdo->deleteFrom($this->tableName)->execute();
} }
public function deleteById($entityId) public function deleteById($entityId)
{ {
return $this->deleteBy($this->getIdColumn(), $entityId); return $this->deleteBy($this->getIdColumn(), $entityId);
} }
public function update(Entity $entity) public function update(Entity $entity)
{ {
$arrayEntity = $this->entityConverter->toArray($entity); $arrayEntity = $this->entityConverter->toArray($entity);
unset($arrayEntity['id']); unset($arrayEntity['id']);
$this->pdo->update($this->tableName)->set($arrayEntity)->where($this->getIdColumn(), $entity->getId())->execute(); $this->pdo->update($this->tableName)->set($arrayEntity)->where($this->getIdColumn(), $entity->getId())->execute();
return $entity; return $entity;
} }
public function create(Entity $entity) public function create(Entity $entity)
{ {
$sql = 'UPDATE sequencer SET lastUsedId = (@lastUsedId := (lastUsedId + 1)) WHERE tableName = :tableName'; $sql = 'UPDATE sequencer SET lastUsedId = (@lastUsedId := (lastUsedId + 1)) WHERE tableName = :tableName';
$query = $this->pdo->prepare($sql); $query = $this->pdo->prepare($sql);
$query->bindValue(':tableName', $this->tableName); $query->bindValue(':tableName', $this->tableName);
$query->execute(); $query->execute();
$lastUsedId = $this->pdo->query('SELECT @lastUsedId')->fetchColumn(); $lastUsedId = $this->pdo->query('SELECT @lastUsedId')->fetchColumn();
$entity->setId(intval($lastUsedId)); $entity->setId(intval($lastUsedId));
$arrayEntity = $this->entityConverter->toArray($entity); $arrayEntity = $this->entityConverter->toArray($entity);
$this->pdo->insertInto($this->tableName)->values($arrayEntity)->execute(); $this->pdo->insertInto($this->tableName)->values($arrayEntity)->execute();
return $entity; return $entity;
} }
protected function getIdColumn() protected function getIdColumn()
{ {
return 'id'; return 'id';
} }
protected function hasAnyRecords() protected function hasAnyRecords()
{ {
return count(iterator_to_array($this->pdo->from($this->tableName)->limit(1))) > 0; return count(iterator_to_array($this->pdo->from($this->tableName)->limit(1))) > 0;
} }
protected function findBy($columnName, $value) protected function findBy($columnName, $value)
{ {
if (is_array($value) && empty($value)) if (is_array($value) && empty($value))
return []; return [];
$query = $this->pdo->from($this->tableName)->where($columnName, $value); $query = $this->pdo->from($this->tableName)->where($columnName, $value);
$arrayEntities = iterator_to_array($query); $arrayEntities = iterator_to_array($query);
return $this->arrayToEntities($arrayEntities); return $this->arrayToEntities($arrayEntities);
} }
protected function findOneBy($columnName, $value) protected function findOneBy($columnName, $value)
{ {
$entities = $this->findBy($columnName, $value); $entities = $this->findBy($columnName, $value);
if (!$entities) if (!$entities)
return null; return null;
return array_shift($entities); return array_shift($entities);
} }
protected function deleteBy($columnName, $value) protected function deleteBy($columnName, $value)
{ {
foreach ($this->findBy($columnName, $value) as $entity) foreach ($this->findBy($columnName, $value) as $entity)
{ {
$this->beforeDelete($entity); $this->beforeDelete($entity);
} }
$this->pdo->deleteFrom($this->tableName)->where($columnName, $value)->execute(); $this->pdo->deleteFrom($this->tableName)->where($columnName, $value)->execute();
} }
protected function afterLoad(Entity $entity) protected function afterLoad(Entity $entity)
{ {
} }
protected function afterSave(Entity $entity) protected function afterSave(Entity $entity)
{ {
} }
protected function afterBatchSave(array $entities) protected function afterBatchSave(array $entities)
{ {
} }
protected function beforeDelete(Entity $entity) protected function beforeDelete(Entity $entity)
{ {
} }
protected function decorateQueryFromRequirement($query, Requirement $requirement) protected function decorateQueryFromRequirement($query, Requirement $requirement)
{ {
$value = $requirement->getValue(); $value = $requirement->getValue();
$sqlColumn = $requirement->getType(); $sqlColumn = $requirement->getType();
if ($value instanceof RequirementCompositeValue) if ($value instanceof RequirementCompositeValue)
{ {
$sql = $sqlColumn; $sql = $sqlColumn;
$bindings = $value->getValues(); $bindings = $value->getValues();
if ($requirement->isNegated()) if ($requirement->isNegated())
$sql = 'NOT ' . $sql; $sql = 'NOT ' . $sql;
} }
else if ($value instanceof RequirementRangedValue) else if ($value instanceof RequirementRangedValue)
{ {
if ($value->getMinValue() && $value->getMaxValue()) if ($value->getMinValue() && $value->getMaxValue())
{ {
$sql = $sqlColumn . ' >= ? AND ' . $sqlColumn . ' <= ?'; $sql = $sqlColumn . ' >= ? AND ' . $sqlColumn . ' <= ?';
$bindings = [$value->getMinValue(), $value->getMaxValue()]; $bindings = [$value->getMinValue(), $value->getMaxValue()];
} }
elseif ($value->getMinValue()) elseif ($value->getMinValue())
{ {
$sql = $sqlColumn . ' >= ?'; $sql = $sqlColumn . ' >= ?';
$bindings = [$value->getMinValue()]; $bindings = [$value->getMinValue()];
} }
elseif ($value->getMaxValue()) elseif ($value->getMaxValue())
{ {
$sql = $sqlColumn . ' <= ?'; $sql = $sqlColumn . ' <= ?';
$bindings = [$value->getMaxValue()]; $bindings = [$value->getMaxValue()];
} }
else else
throw new \RuntimeException('Neither min or max value was supplied'); throw new \RuntimeException('Neither min or max value was supplied');
if ($requirement->isNegated()) if ($requirement->isNegated())
$sql = 'NOT (' . $sql . ')'; $sql = 'NOT (' . $sql . ')';
} }
else if ($value instanceof RequirementSingleValue) else if ($value instanceof RequirementSingleValue)
{ {
$sql = $sqlColumn; $sql = $sqlColumn;
$bindings = [$value->getValue()]; $bindings = [$value->getValue()];
if ($requirement->isNegated()) if ($requirement->isNegated())
$sql = 'NOT ' . $sql; $sql = 'NOT ' . $sql;
} }
else else
throw new \Exception('Bad value: ' . get_class($value)); throw new \Exception('Bad value: ' . get_class($value));
$query->where($sql, $bindings); $query->where($sql, $bindings);
} }
protected function arrayToEntities($arrayEntities, $entityConverter = null) protected function arrayToEntities($arrayEntities, $entityConverter = null)
{ {
if ($entityConverter === null) if ($entityConverter === null)
$entityConverter = $this->entityConverter; $entityConverter = $this->entityConverter;
$entities = []; $entities = [];
foreach ($arrayEntities as $arrayEntity) foreach ($arrayEntities as $arrayEntity)
{ {
$entity = $entityConverter->toEntity($arrayEntity); $entity = $entityConverter->toEntity($arrayEntity);
$entities[$entity->getId()] = $entity; $entities[$entity->getId()] = $entity;
} }
return $entities; return $entities;
} }
private function setDatabaseConnection(DatabaseConnection $databaseConnection) private function setDatabaseConnection(DatabaseConnection $databaseConnection)
{ {
$this->pdo = $databaseConnection->getPDO(); $this->pdo = $databaseConnection->getPDO();
$this->driver = $databaseConnection->getDriver(); $this->driver = $databaseConnection->getDriver();
} }
private function decorateQueryFromFilter($query, IFilter $filter) private function decorateQueryFromFilter($query, IFilter $filter)
{ {
foreach ($filter->getRequirements() as $requirement) foreach ($filter->getRequirements() as $requirement)
{ {
$this->decorateQueryFromRequirement($query, $requirement); $this->decorateQueryFromRequirement($query, $requirement);
} }
} }
private function compileOrderBy($order) private function compileOrderBy($order)
{ {
$orderByString = ''; $orderByString = '';
foreach ($order as $orderColumn => $orderDir) foreach ($order as $orderColumn => $orderDir)
{ {
if ($orderColumn === IFilter::ORDER_RANDOM) if ($orderColumn === IFilter::ORDER_RANDOM)
{ {
$driver = $this->driver; $driver = $this->driver;
if ($driver === 'sqlite') if ($driver === 'sqlite')
{ {
$orderColumn = 'RANDOM()'; $orderColumn = 'RANDOM()';
} }
else else
{ {
$orderColumn = 'RAND()'; $orderColumn = 'RAND()';
} }
} }
$orderByString .= $orderColumn . ' ' . ($orderDir === IFilter::ORDER_DESC ? 'DESC' : 'ASC') . ', '; $orderByString .= $orderColumn . ' ' . ($orderDir === IFilter::ORDER_DESC ? 'DESC' : 'ASC') . ', ';
} }
return substr($orderByString, 0, -2); return substr($orderByString, 0, -2);
} }
private function upsert(Entity $entity) private function upsert(Entity $entity)
{ {
if ($entity->getId()) if ($entity->getId())
{ {
return $this->update($entity); return $this->update($entity);
} }
else else
{ {
return $this->create($entity); return $this->create($entity);
} }
} }
} }

View file

@ -10,42 +10,42 @@ use Szurubooru\Entities\Post;
class CommentDao extends AbstractDao implements ICrudDao class CommentDao extends AbstractDao implements ICrudDao
{ {
private $userDao; private $userDao;
private $postDao; private $postDao;
public function __construct( public function __construct(
DatabaseConnection $databaseConnection, DatabaseConnection $databaseConnection,
UserDao $userDao, UserDao $userDao,
PostDao $postDao) PostDao $postDao)
{ {
parent::__construct( parent::__construct(
$databaseConnection, $databaseConnection,
'comments', 'comments',
new CommentEntityConverter()); new CommentEntityConverter());
$this->userDao = $userDao; $this->userDao = $userDao;
$this->postDao = $postDao; $this->postDao = $postDao;
} }
public function findByPost(Post $post) public function findByPost(Post $post)
{ {
return $this->findBy('postId', $post->getId()); return $this->findBy('postId', $post->getId());
} }
protected function afterLoad(Entity $comment) protected function afterLoad(Entity $comment)
{ {
$comment->setLazyLoader( $comment->setLazyLoader(
Comment::LAZY_LOADER_USER, Comment::LAZY_LOADER_USER,
function (Comment $comment) function (Comment $comment)
{ {
return $this->userDao->findById($comment->getUserId()); return $this->userDao->findById($comment->getUserId());
}); });
$comment->setLazyLoader( $comment->setLazyLoader(
Comment::LAZY_LOADER_POST, Comment::LAZY_LOADER_POST,
function (Comment $comment) function (Comment $comment)
{ {
return $this->postDao->findById($comment->getPostId()); return $this->postDao->findById($comment->getPostId());
}); });
} }
} }

View file

@ -5,43 +5,43 @@ use Szurubooru\Entities\Entity;
abstract class AbstractEntityConverter implements IEntityConverter abstract class AbstractEntityConverter implements IEntityConverter
{ {
private $entityDecorator = null; private $entityDecorator = null;
public function setEntityDecorator(callable $entityDecorator) public function setEntityDecorator(callable $entityDecorator)
{ {
$this->entityDecorator = $entityDecorator; $this->entityDecorator = $entityDecorator;
} }
public function toEntity(array $array) public function toEntity(array $array)
{ {
$entity = $this->toBasicEntity($array); $entity = $this->toBasicEntity($array);
$func = $this->entityDecorator; $func = $this->entityDecorator;
if ($func !== null) if ($func !== null)
$func($entity); $func($entity);
return $entity; return $entity;
} }
public function toArray(Entity $entity) public function toArray(Entity $entity)
{ {
$array = $this->toBasicArray($entity); $array = $this->toBasicArray($entity);
if ($entity->getId() !== null) if ($entity->getId() !== null)
$array['id'] = $entity->getId(); $array['id'] = $entity->getId();
return $array; return $array;
} }
protected abstract function toBasicEntity(array $array); protected abstract function toBasicEntity(array $array);
protected abstract function toBasicArray(Entity $entity); protected abstract function toBasicArray(Entity $entity);
protected function dbTimeToEntityTime($time) protected function dbTimeToEntityTime($time)
{ {
if ($time === null) if ($time === null)
return null; return null;
return date('c', strtotime($time)); return date('c', strtotime($time));
} }
protected function entityTimeToDbTime($time) protected function entityTimeToDbTime($time)
{ {
return $time === null ? null : date('Y-m-d H:i:s', strtotime($time)); return $time === null ? null : date('Y-m-d H:i:s', strtotime($time));
} }
} }

View file

@ -5,27 +5,27 @@ use Szurubooru\Entities\Entity;
class CommentEntityConverter extends AbstractEntityConverter implements IEntityConverter class CommentEntityConverter extends AbstractEntityConverter implements IEntityConverter
{ {
public function toBasicArray(Entity $entity) public function toBasicArray(Entity $entity)
{ {
return return
[ [
'userId' => $entity->getUserId(), 'userId' => $entity->getUserId(),
'postId' => $entity->getPostId(), 'postId' => $entity->getPostId(),
'text' => $entity->getText(), 'text' => $entity->getText(),
'creationTime' => $this->entityTimeToDbTime($entity->getCreationTime()), 'creationTime' => $this->entityTimeToDbTime($entity->getCreationTime()),
'lastEditTime' => $this->entityTimeToDbTime($entity->getLastEditTime()), 'lastEditTime' => $this->entityTimeToDbTime($entity->getLastEditTime()),
]; ];
} }
public function toBasicEntity(array $array) public function toBasicEntity(array $array)
{ {
$entity = new Comment(intval($array['id'])); $entity = new Comment(intval($array['id']));
$entity->setUserId($array['userId']); $entity->setUserId($array['userId']);
$entity->setPostId($array['postId']); $entity->setPostId($array['postId']);
$entity->setText($array['text']); $entity->setText($array['text']);
$entity->setCreationTime($this->dbTimeToEntityTime($array['creationTime'])); $entity->setCreationTime($this->dbTimeToEntityTime($array['creationTime']));
$entity->setLastEditTime($this->dbTimeToEntityTime($array['lastEditTime'])); $entity->setLastEditTime($this->dbTimeToEntityTime($array['lastEditTime']));
$entity->setMeta(Comment::META_SCORE, intval($array['score'])); $entity->setMeta(Comment::META_SCORE, intval($array['score']));
return $entity; return $entity;
} }
} }

View file

@ -5,22 +5,22 @@ use Szurubooru\Entities\Favorite;
class FavoriteEntityConverter extends AbstractEntityConverter implements IEntityConverter class FavoriteEntityConverter extends AbstractEntityConverter implements IEntityConverter
{ {
public function toBasicArray(Entity $entity) public function toBasicArray(Entity $entity)
{ {
return return
[ [
'userId' => $entity->getUserId(), 'userId' => $entity->getUserId(),
'postId' => $entity->getPostId(), 'postId' => $entity->getPostId(),
'time' => $this->entityTimeToDbTime($entity->getTime()), 'time' => $this->entityTimeToDbTime($entity->getTime()),
]; ];
} }
public function toBasicEntity(array $array) public function toBasicEntity(array $array)
{ {
$entity = new Favorite(intval($array['id'])); $entity = new Favorite(intval($array['id']));
$entity->setUserId($array['userId']); $entity->setUserId($array['userId']);
$entity->setPostId($array['postId']); $entity->setPostId($array['postId']);
$entity->setTime($this->dbTimeToEntityTime($array['time'])); $entity->setTime($this->dbTimeToEntityTime($array['time']));
return $entity; return $entity;
} }
} }

View file

@ -5,20 +5,20 @@ use Szurubooru\Entities\GlobalParam;
class GlobalParamEntityConverter extends AbstractEntityConverter implements IEntityConverter class GlobalParamEntityConverter extends AbstractEntityConverter implements IEntityConverter
{ {
public function toBasicArray(Entity $entity) public function toBasicArray(Entity $entity)
{ {
return return
[ [
'dataKey' => $entity->getKey(), 'dataKey' => $entity->getKey(),
'dataValue' => $entity->getValue(), 'dataValue' => $entity->getValue(),
]; ];
} }
public function toBasicEntity(array $array) public function toBasicEntity(array $array)
{ {
$entity = new GlobalParam(intval($array['id'])); $entity = new GlobalParam(intval($array['id']));
$entity->setKey($array['dataKey']); $entity->setKey($array['dataKey']);
$entity->setValue($array['dataValue']); $entity->setValue($array['dataValue']);
return $entity; return $entity;
} }
} }

View file

@ -4,7 +4,7 @@ use Szurubooru\Entities\Entity;
interface IEntityConverter interface IEntityConverter
{ {
public function toArray(Entity $entity); public function toArray(Entity $entity);
public function toEntity(array $array); public function toEntity(array $array);
} }

View file

@ -5,52 +5,52 @@ use Szurubooru\Entities\Post;
class PostEntityConverter extends AbstractEntityConverter implements IEntityConverter class PostEntityConverter extends AbstractEntityConverter implements IEntityConverter
{ {
public function toBasicArray(Entity $entity) public function toBasicArray(Entity $entity)
{ {
return return
[ [
'name' => $entity->getName(), 'name' => $entity->getName(),
'userId' => $entity->getUserId(), 'userId' => $entity->getUserId(),
'uploadTime' => $this->entityTimeToDbTime($entity->getUploadTime()), 'uploadTime' => $this->entityTimeToDbTime($entity->getUploadTime()),
'lastEditTime' => $this->entityTimeToDbTime($entity->getLastEditTime()), 'lastEditTime' => $this->entityTimeToDbTime($entity->getLastEditTime()),
'safety' => $entity->getSafety(), 'safety' => $entity->getSafety(),
'contentType' => $entity->getContentType(), 'contentType' => $entity->getContentType(),
'contentChecksum' => $entity->getContentChecksum(), 'contentChecksum' => $entity->getContentChecksum(),
'contentMimeType' => $entity->getContentMimeType(), 'contentMimeType' => $entity->getContentMimeType(),
'source' => $entity->getSource(), 'source' => $entity->getSource(),
'imageWidth' => $entity->getImageWidth(), 'imageWidth' => $entity->getImageWidth(),
'imageHeight' => $entity->getImageHeight(), 'imageHeight' => $entity->getImageHeight(),
'originalFileSize' => $entity->getOriginalFileSize(), 'originalFileSize' => $entity->getOriginalFileSize(),
'originalFileName' => $entity->getOriginalFileName(), 'originalFileName' => $entity->getOriginalFileName(),
'featureCount' => $entity->getFeatureCount(), 'featureCount' => $entity->getFeatureCount(),
'lastFeatureTime' => $this->entityTimeToDbTime($entity->getLastFeatureTime()), 'lastFeatureTime' => $this->entityTimeToDbTime($entity->getLastFeatureTime()),
'flags' => $entity->getFlags(), 'flags' => $entity->getFlags(),
]; ];
} }
public function toBasicEntity(array $array) public function toBasicEntity(array $array)
{ {
$entity = new Post(intval($array['id'])); $entity = new Post(intval($array['id']));
$entity->setName($array['name']); $entity->setName($array['name']);
$entity->setUserId($array['userId']); $entity->setUserId($array['userId']);
$entity->setUploadTime($this->dbTimeToEntityTime($array['uploadTime'])); $entity->setUploadTime($this->dbTimeToEntityTime($array['uploadTime']));
$entity->setLastEditTime($this->dbTimeToEntityTime($array['lastEditTime'])); $entity->setLastEditTime($this->dbTimeToEntityTime($array['lastEditTime']));
$entity->setSafety(intval($array['safety'])); $entity->setSafety(intval($array['safety']));
$entity->setContentType(intval($array['contentType'])); $entity->setContentType(intval($array['contentType']));
$entity->setContentChecksum($array['contentChecksum']); $entity->setContentChecksum($array['contentChecksum']);
$entity->setContentMimeType($array['contentMimeType']); $entity->setContentMimeType($array['contentMimeType']);
$entity->setSource($array['source']); $entity->setSource($array['source']);
$entity->setImageWidth($array['imageWidth']); $entity->setImageWidth($array['imageWidth']);
$entity->setImageHeight($array['imageHeight']); $entity->setImageHeight($array['imageHeight']);
$entity->setOriginalFileSize($array['originalFileSize']); $entity->setOriginalFileSize($array['originalFileSize']);
$entity->setOriginalFileName($array['originalFileName']); $entity->setOriginalFileName($array['originalFileName']);
$entity->setFeatureCount(intval($array['featureCount'])); $entity->setFeatureCount(intval($array['featureCount']));
$entity->setLastFeatureTime($this->dbTimeToEntityTime($array['lastFeatureTime'])); $entity->setLastFeatureTime($this->dbTimeToEntityTime($array['lastFeatureTime']));
$entity->setFlags(intval($array['flags'])); $entity->setFlags(intval($array['flags']));
$entity->setMeta(Post::META_TAG_COUNT, intval($array['tagCount'])); $entity->setMeta(Post::META_TAG_COUNT, intval($array['tagCount']));
$entity->setMeta(Post::META_FAV_COUNT, intval($array['favCount'])); $entity->setMeta(Post::META_FAV_COUNT, intval($array['favCount']));
$entity->setMeta(Post::META_COMMENT_COUNT, intval($array['commentCount'])); $entity->setMeta(Post::META_COMMENT_COUNT, intval($array['commentCount']));
$entity->setMeta(Post::META_SCORE, intval($array['score'])); $entity->setMeta(Post::META_SCORE, intval($array['score']));
return $entity; return $entity;
} }
} }

View file

@ -5,28 +5,28 @@ use Szurubooru\Entities\PostNote;
class PostNoteEntityConverter extends AbstractEntityConverter implements IEntityConverter class PostNoteEntityConverter extends AbstractEntityConverter implements IEntityConverter
{ {
public function toBasicArray(Entity $entity) public function toBasicArray(Entity $entity)
{ {
return return
[ [
'postId' => $entity->getPostId(), 'postId' => $entity->getPostId(),
'x' => $entity->getLeft(), 'x' => $entity->getLeft(),
'y' => $entity->getTop(), 'y' => $entity->getTop(),
'width' => $entity->getWidth(), 'width' => $entity->getWidth(),
'height' => $entity->getHeight(), 'height' => $entity->getHeight(),
'text' => $entity->getText(), 'text' => $entity->getText(),
]; ];
} }
public function toBasicEntity(array $array) public function toBasicEntity(array $array)
{ {
$entity = new PostNote(intval($array['id'])); $entity = new PostNote(intval($array['id']));
$entity->setPostId($array['postId']); $entity->setPostId($array['postId']);
$entity->setLeft(floatval($array['x'])); $entity->setLeft(floatval($array['x']));
$entity->setTop(floatval($array['y'])); $entity->setTop(floatval($array['y']));
$entity->setWidth(floatval($array['width'])); $entity->setWidth(floatval($array['width']));
$entity->setHeight(floatval($array['height'])); $entity->setHeight(floatval($array['height']));
$entity->setText($array['text']); $entity->setText($array['text']);
return $entity; return $entity;
} }
} }

View file

@ -5,26 +5,26 @@ use Szurubooru\Entities\Score;
class ScoreEntityConverter extends AbstractEntityConverter implements IEntityConverter class ScoreEntityConverter extends AbstractEntityConverter implements IEntityConverter
{ {
public function toBasicArray(Entity $entity) public function toBasicArray(Entity $entity)
{ {
return return
[ [
'userId' => $entity->getUserId(), 'userId' => $entity->getUserId(),
'postId' => $entity->getPostId(), 'postId' => $entity->getPostId(),
'commentId' => $entity->getCommentId(), 'commentId' => $entity->getCommentId(),
'time' => $this->entityTimeToDbTime($entity->getTime()), 'time' => $this->entityTimeToDbTime($entity->getTime()),
'score' => $entity->getScore(), 'score' => $entity->getScore(),
]; ];
} }
public function toBasicEntity(array $array) public function toBasicEntity(array $array)
{ {
$entity = new Score(intval($array['id'])); $entity = new Score(intval($array['id']));
$entity->setUserId($array['userId']); $entity->setUserId($array['userId']);
$entity->setPostId($array['postId']); $entity->setPostId($array['postId']);
$entity->setCommentId($array['commentId']); $entity->setCommentId($array['commentId']);
$entity->setTime($this->dbTimeToEntityTime($array['time'])); $entity->setTime($this->dbTimeToEntityTime($array['time']));
$entity->setScore(intval($array['score'])); $entity->setScore(intval($array['score']));
return $entity; return $entity;
} }
} }

View file

@ -5,30 +5,30 @@ use Szurubooru\Entities\Snapshot;
class SnapshotEntityConverter extends AbstractEntityConverter implements IEntityConverter class SnapshotEntityConverter extends AbstractEntityConverter implements IEntityConverter
{ {
public function toBasicArray(Entity $entity) public function toBasicArray(Entity $entity)
{ {
return return
[ [
'time' => $this->entityTimeToDbTime($entity->getTime()), 'time' => $this->entityTimeToDbTime($entity->getTime()),
'type' => $entity->getType(), 'type' => $entity->getType(),
'primaryKey' => $entity->getPrimaryKey(), 'primaryKey' => $entity->getPrimaryKey(),
'userId' => $entity->getUserId(), 'userId' => $entity->getUserId(),
'operation' => $entity->getOperation(), 'operation' => $entity->getOperation(),
'data' => gzdeflate(json_encode($entity->getData())), 'data' => gzdeflate(json_encode($entity->getData())),
'dataDifference' => gzdeflate(json_encode($entity->getDataDifference())), 'dataDifference' => gzdeflate(json_encode($entity->getDataDifference())),
]; ];
} }
public function toBasicEntity(array $array) public function toBasicEntity(array $array)
{ {
$entity = new Snapshot(intval($array['id'])); $entity = new Snapshot(intval($array['id']));
$entity->setTime($this->dbTimeToEntityTime($array['time'])); $entity->setTime($this->dbTimeToEntityTime($array['time']));
$entity->setType(intval($array['type'])); $entity->setType(intval($array['type']));
$entity->setPrimaryKey($array['primaryKey']); $entity->setPrimaryKey($array['primaryKey']);
$entity->setUserId(intval($array['userId'])); $entity->setUserId(intval($array['userId']));
$entity->setOperation($array['operation']); $entity->setOperation($array['operation']);
$entity->setData(json_decode(gzinflate($array['data']), true)); $entity->setData(json_decode(gzinflate($array['data']), true));
$entity->setDataDifference(json_decode(gzinflate($array['dataDifference']), true)); $entity->setDataDifference(json_decode(gzinflate($array['dataDifference']), true));
return $entity; return $entity;
} }
} }

View file

@ -5,25 +5,25 @@ use Szurubooru\Entities\Tag;
class TagEntityConverter extends AbstractEntityConverter implements IEntityConverter class TagEntityConverter extends AbstractEntityConverter implements IEntityConverter
{ {
public function toBasicArray(Entity $entity) public function toBasicArray(Entity $entity)
{ {
return return
[ [
'name' => $entity->getName(), 'name' => $entity->getName(),
'creationTime' => $this->entityTimeToDbTime($entity->getCreationTime()), 'creationTime' => $this->entityTimeToDbTime($entity->getCreationTime()),
'banned' => intval($entity->isBanned()), 'banned' => intval($entity->isBanned()),
'category' => $entity->getCategory(), 'category' => $entity->getCategory(),
]; ];
} }
public function toBasicEntity(array $array) public function toBasicEntity(array $array)
{ {
$entity = new Tag(intval($array['id'])); $entity = new Tag(intval($array['id']));
$entity->setName($array['name']); $entity->setName($array['name']);
$entity->setCreationTime($this->dbTimeToEntityTime($array['creationTime'])); $entity->setCreationTime($this->dbTimeToEntityTime($array['creationTime']));
$entity->setMeta(Tag::META_USAGES, intval($array['usages'])); $entity->setMeta(Tag::META_USAGES, intval($array['usages']));
$entity->setBanned($array['banned']); $entity->setBanned($array['banned']);
$entity->setCategory($array['category']); $entity->setCategory($array['category']);
return $entity; return $entity;
} }
} }

View file

@ -5,22 +5,22 @@ use Szurubooru\Entities\Token;
class TokenEntityConverter extends AbstractEntityConverter implements IEntityConverter class TokenEntityConverter extends AbstractEntityConverter implements IEntityConverter
{ {
public function toBasicArray(Entity $entity) public function toBasicArray(Entity $entity)
{ {
return return
[ [
'name' => $entity->getName(), 'name' => $entity->getName(),
'purpose' => $entity->getPurpose(), 'purpose' => $entity->getPurpose(),
'additionalData' => $entity->getAdditionalData(), 'additionalData' => $entity->getAdditionalData(),
]; ];
} }
public function toBasicEntity(array $array) public function toBasicEntity(array $array)
{ {
$entity = new Token(intval($array['id'])); $entity = new Token(intval($array['id']));
$entity->setName($array['name']); $entity->setName($array['name']);
$entity->setPurpose($array['purpose']); $entity->setPurpose($array['purpose']);
$entity->setAdditionalData($array['additionalData']); $entity->setAdditionalData($array['additionalData']);
return $entity; return $entity;
} }
} }

View file

@ -5,40 +5,40 @@ use Szurubooru\Entities\User;
class UserEntityConverter extends AbstractEntityConverter implements IEntityConverter class UserEntityConverter extends AbstractEntityConverter implements IEntityConverter
{ {
public function toBasicArray(Entity $entity) public function toBasicArray(Entity $entity)
{ {
return return
[ [
'name' => $entity->getName(), 'name' => $entity->getName(),
'email' => $entity->getEmail(), 'email' => $entity->getEmail(),
'emailUnconfirmed' => $entity->getEmailUnconfirmed(), 'emailUnconfirmed' => $entity->getEmailUnconfirmed(),
'passwordHash' => $entity->getPasswordHash(), 'passwordHash' => $entity->getPasswordHash(),
'passwordSalt' => $entity->getPasswordSalt(), 'passwordSalt' => $entity->getPasswordSalt(),
'accessRank' => $entity->getAccessRank(), 'accessRank' => $entity->getAccessRank(),
'registrationTime' => $this->entityTimeToDbTime($entity->getRegistrationTime()), 'registrationTime' => $this->entityTimeToDbTime($entity->getRegistrationTime()),
'lastLoginTime' => $this->entityTimeToDbTime($entity->getLastLoginTime()), 'lastLoginTime' => $this->entityTimeToDbTime($entity->getLastLoginTime()),
'avatarStyle' => $entity->getAvatarStyle(), 'avatarStyle' => $entity->getAvatarStyle(),
'browsingSettings' => json_encode($entity->getBrowsingSettings()), 'browsingSettings' => json_encode($entity->getBrowsingSettings()),
'accountConfirmed' => intval($entity->isAccountConfirmed()), 'accountConfirmed' => intval($entity->isAccountConfirmed()),
'banned' => intval($entity->isBanned()), 'banned' => intval($entity->isBanned()),
]; ];
} }
public function toBasicEntity(array $array) public function toBasicEntity(array $array)
{ {
$entity = new User(intval($array['id'])); $entity = new User(intval($array['id']));
$entity->setName($array['name']); $entity->setName($array['name']);
$entity->setEmail($array['email']); $entity->setEmail($array['email']);
$entity->setEmailUnconfirmed($array['emailUnconfirmed']); $entity->setEmailUnconfirmed($array['emailUnconfirmed']);
$entity->setPasswordHash($array['passwordHash']); $entity->setPasswordHash($array['passwordHash']);
$entity->setPasswordSalt($array['passwordSalt']); $entity->setPasswordSalt($array['passwordSalt']);
$entity->setAccessRank(intval($array['accessRank'])); $entity->setAccessRank(intval($array['accessRank']));
$entity->setRegistrationTime($this->dbTimeToEntityTime($array['registrationTime'])); $entity->setRegistrationTime($this->dbTimeToEntityTime($array['registrationTime']));
$entity->setLastLoginTime($this->dbTimeToEntityTime($array['lastLoginTime'])); $entity->setLastLoginTime($this->dbTimeToEntityTime($array['lastLoginTime']));
$entity->setAvatarStyle(intval($array['avatarStyle'])); $entity->setAvatarStyle(intval($array['avatarStyle']));
$entity->setBrowsingSettings(json_decode($array['browsingSettings'])); $entity->setBrowsingSettings(json_decode($array['browsingSettings']));
$entity->setAccountConfirmed($array['accountConfirmed']); $entity->setAccountConfirmed($array['accountConfirmed']);
$entity->setBanned($array['banned']); $entity->setBanned($array['banned']);
return $entity; return $entity;
} }
} }

View file

@ -10,65 +10,65 @@ use Szurubooru\Services\TimeService;
class FavoritesDao extends AbstractDao implements ICrudDao class FavoritesDao extends AbstractDao implements ICrudDao
{ {
private $timeService; private $timeService;
public function __construct( public function __construct(
DatabaseConnection $databaseConnection, DatabaseConnection $databaseConnection,
TimeService $timeService) TimeService $timeService)
{ {
parent::__construct( parent::__construct(
$databaseConnection, $databaseConnection,
'favorites', 'favorites',
new FavoriteEntityConverter()); new FavoriteEntityConverter());
$this->timeService = $timeService; $this->timeService = $timeService;
} }
public function findByEntity(Entity $entity) public function findByEntity(Entity $entity)
{ {
if ($entity instanceof Post) if ($entity instanceof Post)
return $this->findBy('postId', $entity->getId()); return $this->findBy('postId', $entity->getId());
else else
throw new \InvalidArgumentException(); throw new \InvalidArgumentException();
} }
public function set(User $user, Entity $entity) public function set(User $user, Entity $entity)
{ {
$favorite = $this->get($user, $entity); $favorite = $this->get($user, $entity);
if (!$favorite) if (!$favorite)
{ {
$favorite = new Favorite(); $favorite = new Favorite();
$favorite->setTime($this->timeService->getCurrentTime()); $favorite->setTime($this->timeService->getCurrentTime());
$favorite->setUserId($user->getId()); $favorite->setUserId($user->getId());
if ($entity instanceof Post) if ($entity instanceof Post)
$favorite->setPostId($entity->getId()); $favorite->setPostId($entity->getId());
else else
throw new \InvalidArgumentException(); throw new \InvalidArgumentException();
$this->save($favorite); $this->save($favorite);
} }
return $favorite; return $favorite;
} }
public function delete(User $user, Entity $entity) public function delete(User $user, Entity $entity)
{ {
$favorite = $this->get($user, $entity); $favorite = $this->get($user, $entity);
if ($favorite) if ($favorite)
$this->deleteById($favorite->getId()); $this->deleteById($favorite->getId());
} }
private function get(User $user, Entity $entity) private function get(User $user, Entity $entity)
{ {
$query = $this->pdo->from($this->tableName)->where('userId', $user->getId()); $query = $this->pdo->from($this->tableName)->where('userId', $user->getId());
if ($entity instanceof Post) if ($entity instanceof Post)
$query->where('postId', $entity->getId()); $query->where('postId', $entity->getId());
else else
throw new \InvalidArgumentException(); throw new \InvalidArgumentException();
$arrayEntities = iterator_to_array($query); $arrayEntities = iterator_to_array($query);
$entities = $this->arrayToEntities($arrayEntities); $entities = $this->arrayToEntities($arrayEntities);
return array_shift($entities); return array_shift($entities);
} }
} }

View file

@ -4,93 +4,93 @@ use Szurubooru\Dao\IFileDao;
class FileDao implements IFileDao class FileDao implements IFileDao
{ {
private $directory; private $directory;
public function __construct($directory) public function __construct($directory)
{ {
$this->directory = $directory; $this->directory = $directory;
} }
public function load($fileName) public function load($fileName)
{ {
$fullPath = $this->getFullPath($fileName); $fullPath = $this->getFullPath($fileName);
return file_exists($fullPath) return file_exists($fullPath)
? file_get_contents($fullPath) ? file_get_contents($fullPath)
: null; : null;
} }
public function save($fileName, $data) public function save($fileName, $data)
{ {
$fullPath = $this->getFullPath($fileName); $fullPath = $this->getFullPath($fileName);
$this->createFolders($fileName); $this->createFolders($fileName);
file_put_contents($fullPath, $data); file_put_contents($fullPath, $data);
} }
public function delete($fileName) public function delete($fileName)
{ {
$fullPath = $this->getFullPath($fileName); $fullPath = $this->getFullPath($fileName);
if (file_exists($fullPath)) if (file_exists($fullPath))
unlink($fullPath); unlink($fullPath);
} }
public function exists($fileName) public function exists($fileName)
{ {
$fullPath = $this->getFullPath($fileName); $fullPath = $this->getFullPath($fileName);
return file_exists($fullPath); return file_exists($fullPath);
} }
public function getFullPath($fileName) public function getFullPath($fileName)
{ {
return $this->directory . DIRECTORY_SEPARATOR . $fileName; return $this->directory . DIRECTORY_SEPARATOR . $fileName;
} }
public function listAll() public function listAll()
{ {
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory)); $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory));
$files = []; $files = [];
foreach ($iterator as $path) foreach ($iterator as $path)
{ {
if (!$path->isDir()) if (!$path->isDir())
$files[] = $this->getRelativePath($this->directory, $path->getPathName()); $files[] = $this->getRelativePath($this->directory, $path->getPathName());
} }
return $files; return $files;
} }
private function createFolders($fileName) private function createFolders($fileName)
{ {
$fullPath = dirname($this->getFullPath($fileName)); $fullPath = dirname($this->getFullPath($fileName));
if (!file_exists($fullPath)) if (!file_exists($fullPath))
mkdir($fullPath, 0777, true); mkdir($fullPath, 0777, true);
} }
private function getRelativePath($from, $to) private function getRelativePath($from, $to)
{ {
$from = is_dir($from) ? rtrim($from, '\/') . '/' : $from; $from = is_dir($from) ? rtrim($from, '\/') . '/' : $from;
$to = is_dir($to) ? rtrim($to, '\/') . '/' : $to; $to = is_dir($to) ? rtrim($to, '\/') . '/' : $to;
$from = explode('/', str_replace('\\', '/', $from)); $from = explode('/', str_replace('\\', '/', $from));
$to = explode('/', str_replace('\\', '/', $to)); $to = explode('/', str_replace('\\', '/', $to));
$relPath = $to; $relPath = $to;
foreach($from as $depth => $dir) foreach($from as $depth => $dir)
{ {
if($dir === $to[$depth]) if($dir === $to[$depth])
{ {
array_shift($relPath); array_shift($relPath);
} }
else else
{ {
$remaining = count($from) - $depth; $remaining = count($from) - $depth;
if ($remaining > 1) if ($remaining > 1)
{ {
$padLength = (count($relPath) + $remaining - 1) * -1; $padLength = (count($relPath) + $remaining - 1) * -1;
$relPath = array_pad($relPath, $padLength, '..'); $relPath = array_pad($relPath, $padLength, '..');
break; break;
} }
else else
{ {
$relPath[0] = $relPath[0]; $relPath[0] = $relPath[0];
} }
} }
} }
return implode('/', $relPath); return implode('/', $relPath);
} }
} }

View file

@ -5,32 +5,32 @@ use Szurubooru\DatabaseConnection;
class GlobalParamDao extends AbstractDao implements ICrudDao class GlobalParamDao extends AbstractDao implements ICrudDao
{ {
public function __construct(DatabaseConnection $databaseConnection) public function __construct(DatabaseConnection $databaseConnection)
{ {
parent::__construct( parent::__construct(
$databaseConnection, $databaseConnection,
'globals', 'globals',
new GlobalParamEntityConverter()); new GlobalParamEntityConverter());
} }
public function save(&$entity) public function save(&$entity)
{ {
if (!$entity->getId()) if (!$entity->getId())
{ {
$otherEntityWithThisKey = $this->findByKey($entity->getKey()); $otherEntityWithThisKey = $this->findByKey($entity->getKey());
if ($otherEntityWithThisKey) if ($otherEntityWithThisKey)
$entity->setId($otherEntityWithThisKey->getId()); $entity->setId($otherEntityWithThisKey->getId());
} }
parent::save($entity); parent::save($entity);
} }
public function findByKey($key) public function findByKey($key)
{ {
return $this->findOneBy('dataKey', $key); return $this->findOneBy('dataKey', $key);
} }
public function deleteByKey($key) public function deleteByKey($key)
{ {
return $this->deleteBy('dataKey', $key); return $this->deleteBy('dataKey', $key);
} }
} }

View file

@ -3,9 +3,9 @@ namespace Szurubooru\Dao;
interface IBatchDao interface IBatchDao
{ {
public function findAll(); public function findAll();
public function deleteAll(); public function deleteAll();
public function batchSave(array $objects); public function batchSave(array $objects);
} }

View file

@ -3,9 +3,9 @@ namespace Szurubooru\Dao;
interface ICrudDao interface ICrudDao
{ {
public function findById($objectId); public function findById($objectId);
public function save(&$object); public function save(&$object);
public function deleteById($objectId); public function deleteById($objectId);
} }

View file

@ -3,11 +3,11 @@ namespace Szurubooru\Dao;
interface IFileDao interface IFileDao
{ {
public function load($fileName); public function load($fileName);
public function save($fileName, $contents); public function save($fileName, $contents);
public function delete($fileName); public function delete($fileName);
public function exists($fileName); public function exists($fileName);
} }

View file

@ -13,295 +13,295 @@ use Szurubooru\Services\ThumbnailService;
class PostDao extends AbstractDao implements ICrudDao class PostDao extends AbstractDao implements ICrudDao
{ {
private $tagDao; private $tagDao;
private $userDao; private $userDao;
private $fileDao; private $fileDao;
private $thumbnailService; private $thumbnailService;
public function __construct( public function __construct(
DatabaseConnection $databaseConnection, DatabaseConnection $databaseConnection,
TagDao $tagDao, TagDao $tagDao,
UserDao $userDao, UserDao $userDao,
PublicFileDao $fileDao, PublicFileDao $fileDao,
ThumbnailService $thumbnailService) ThumbnailService $thumbnailService)
{ {
parent::__construct( parent::__construct(
$databaseConnection, $databaseConnection,
'posts', 'posts',
new PostEntityConverter()); new PostEntityConverter());
$this->tagDao = $tagDao; $this->tagDao = $tagDao;
$this->userDao = $userDao; $this->userDao = $userDao;
$this->fileDao = $fileDao; $this->fileDao = $fileDao;
$this->thumbnailService = $thumbnailService; $this->thumbnailService = $thumbnailService;
} }
public function getCount() public function getCount()
{ {
return count($this->pdo->from($this->tableName)); return count($this->pdo->from($this->tableName));
} }
public function getTotalFileSize() public function getTotalFileSize()
{ {
$query = $this->pdo->from($this->tableName)->select('SUM(originalFileSize) AS __sum'); $query = $this->pdo->from($this->tableName)->select('SUM(originalFileSize) AS __sum');
return iterator_to_array($query)[0]['__sum']; return iterator_to_array($query)[0]['__sum'];
} }
public function findByName($name) public function findByName($name)
{ {
return $this->findOneBy('name', $name); return $this->findOneBy('name', $name);
} }
public function findByTagName($tagName) public function findByTagName($tagName)
{ {
$query = $this->pdo->from('posts') $query = $this->pdo->from('posts')
->innerJoin('postTags', 'postTags.postId = posts.id') ->innerJoin('postTags', 'postTags.postId = posts.id')
->innerJoin('tags', 'postTags.tagId = tags.id') ->innerJoin('tags', 'postTags.tagId = tags.id')
->where('tags.name', $tagName); ->where('tags.name', $tagName);
$arrayEntities = iterator_to_array($query); $arrayEntities = iterator_to_array($query);
return $this->arrayToEntities($arrayEntities); return $this->arrayToEntities($arrayEntities);
} }
public function findByContentChecksum($checksum) public function findByContentChecksum($checksum)
{ {
return $this->findOneBy('contentChecksum', $checksum); return $this->findOneBy('contentChecksum', $checksum);
} }
protected function afterLoad(Entity $post) protected function afterLoad(Entity $post)
{ {
$post->setLazyLoader( $post->setLazyLoader(
Post::LAZY_LOADER_CONTENT, Post::LAZY_LOADER_CONTENT,
function (Post $post) function (Post $post)
{ {
return $this->fileDao->load($post->getContentPath()); return $this->fileDao->load($post->getContentPath());
}); });
$post->setLazyLoader( $post->setLazyLoader(
Post::LAZY_LOADER_THUMBNAIL_SOURCE_CONTENT, Post::LAZY_LOADER_THUMBNAIL_SOURCE_CONTENT,
function (Post $post) function (Post $post)
{ {
return $this->fileDao->load($post->getThumbnailSourceContentPath()); return $this->fileDao->load($post->getThumbnailSourceContentPath());
}); });
$post->setLazyLoader( $post->setLazyLoader(
Post::LAZY_LOADER_USER, Post::LAZY_LOADER_USER,
function (Post $post) function (Post $post)
{ {
return $this->getUser($post); return $this->getUser($post);
}); });
$post->setLazyLoader( $post->setLazyLoader(
Post::LAZY_LOADER_TAGS, Post::LAZY_LOADER_TAGS,
function (Post $post) function (Post $post)
{ {
return $this->getTags($post); return $this->getTags($post);
}); });
$post->setLazyLoader( $post->setLazyLoader(
Post::LAZY_LOADER_RELATED_POSTS, Post::LAZY_LOADER_RELATED_POSTS,
function (Post $post) function (Post $post)
{ {
return $this->getRelatedPosts($post); return $this->getRelatedPosts($post);
}); });
} }
protected function afterSave(Entity $post) protected function afterSave(Entity $post)
{ {
$this->syncContent($post); $this->syncContent($post);
$this->syncThumbnailSourceContent($post); $this->syncThumbnailSourceContent($post);
$this->syncTags($post); $this->syncTags($post);
$this->syncPostRelations($post); $this->syncPostRelations($post);
} }
protected function decorateQueryFromRequirement($query, Requirement $requirement) protected function decorateQueryFromRequirement($query, Requirement $requirement)
{ {
if ($requirement->getType() === PostFilter::REQUIREMENT_TAG) if ($requirement->getType() === PostFilter::REQUIREMENT_TAG)
{ {
$tagName = $requirement->getValue()->getValue(); $tagName = $requirement->getValue()->getValue();
$tag = $this->tagDao->findByName($tagName); $tag = $this->tagDao->findByName($tagName);
if (!$tag) if (!$tag)
throw new \DomainException('Invalid tag: "' . $tagName . '"'); throw new \DomainException('Invalid tag: "' . $tagName . '"');
$sql = 'EXISTS ( $sql = 'EXISTS (
SELECT 1 FROM postTags SELECT 1 FROM postTags
WHERE postTags.postId = posts.id WHERE postTags.postId = posts.id
AND postTags.tagId = ?)'; AND postTags.tagId = ?)';
if ($requirement->isNegated()) if ($requirement->isNegated())
$sql = 'NOT ' . $sql; $sql = 'NOT ' . $sql;
$query->where($sql, $tag->getId()); $query->where($sql, $tag->getId());
return; return;
} }
elseif ($requirement->getType() === PostFilter::REQUIREMENT_FAVORITE) elseif ($requirement->getType() === PostFilter::REQUIREMENT_FAVORITE)
{ {
foreach ($requirement->getValue()->getValues() as $userName) foreach ($requirement->getValue()->getValues() as $userName)
{ {
$sql = 'EXISTS ( $sql = 'EXISTS (
SELECT 1 FROM favorites f SELECT 1 FROM favorites f
WHERE f.postId = posts.id WHERE f.postId = posts.id
AND f.userId = (SELECT id FROM users WHERE name = ?))'; AND f.userId = (SELECT id FROM users WHERE name = ?))';
if ($requirement->isNegated()) if ($requirement->isNegated())
$sql = 'NOT ' . $sql; $sql = 'NOT ' . $sql;
$query->where($sql, [$userName]); $query->where($sql, [$userName]);
} }
return; return;
} }
elseif ($requirement->getType() === PostFilter::REQUIREMENT_COMMENT_AUTHOR) elseif ($requirement->getType() === PostFilter::REQUIREMENT_COMMENT_AUTHOR)
{ {
foreach ($requirement->getValue()->getValues() as $userName) foreach ($requirement->getValue()->getValues() as $userName)
{ {
$sql = 'EXISTS ( $sql = 'EXISTS (
SELECT 1 FROM comments c SELECT 1 FROM comments c
WHERE c.postId = posts.id WHERE c.postId = posts.id
AND c.userId = (SELECT id FROM users WHERE name = ?))'; AND c.userId = (SELECT id FROM users WHERE name = ?))';
if ($requirement->isNegated()) if ($requirement->isNegated())
$sql = 'NOT ' . $sql; $sql = 'NOT ' . $sql;
$query->where($sql, [$userName]); $query->where($sql, [$userName]);
} }
return; return;
} }
elseif ($requirement->getType() === PostFilter::REQUIREMENT_UPLOADER) elseif ($requirement->getType() === PostFilter::REQUIREMENT_UPLOADER)
{ {
foreach ($requirement->getValue()->getValues() as $userName) foreach ($requirement->getValue()->getValues() as $userName)
{ {
$alias = 'u' . uniqid(); $alias = 'u' . uniqid();
$query->innerJoin('users ' . $alias, $alias . '.id = posts.userId'); $query->innerJoin('users ' . $alias, $alias . '.id = posts.userId');
$sql = $alias . '.name = ?'; $sql = $alias . '.name = ?';
if ($requirement->isNegated()) if ($requirement->isNegated())
$sql = 'NOT ' . $sql; $sql = 'NOT ' . $sql;
$query->where($sql, [$userName]); $query->where($sql, [$userName]);
} }
return; return;
} }
elseif ($requirement->getType() === PostFilter::REQUIREMENT_USER_SCORE) elseif ($requirement->getType() === PostFilter::REQUIREMENT_USER_SCORE)
{ {
$values = $requirement->getValue()->getValues(); $values = $requirement->getValue()->getValues();
$userName = $values[0]; $userName = $values[0];
$score = $values[1]; $score = $values[1];
$sql = 'EXISTS ( $sql = 'EXISTS (
SELECT 1 FROM scores SELECT 1 FROM scores
WHERE scores.postId = posts.id WHERE scores.postId = posts.id
AND scores.userId = (SELECT id FROM users WHERE name = ?) AND scores.userId = (SELECT id FROM users WHERE name = ?)
AND scores.score = ?)'; AND scores.score = ?)';
if ($requirement->isNegated()) if ($requirement->isNegated())
$sql = 'NOT ' . $sql; $sql = 'NOT ' . $sql;
$query->where($sql, [$userName, $score]); $query->where($sql, [$userName, $score]);
return; return;
} }
parent::decorateQueryFromRequirement($query, $requirement); parent::decorateQueryFromRequirement($query, $requirement);
} }
private function getTags(Post $post) private function getTags(Post $post)
{ {
return $this->tagDao->findByPostId($post->getId()); return $this->tagDao->findByPostId($post->getId());
} }
private function getUser(Post $post) private function getUser(Post $post)
{ {
return $this->userDao->findById($post->getUserId()); return $this->userDao->findById($post->getUserId());
} }
private function getRelatedPosts(Post $post) private function getRelatedPosts(Post $post)
{ {
$relatedPostIds = []; $relatedPostIds = [];
foreach ($this->pdo->from('postRelations')->where('post1id', $post->getId()) as $arrayEntity) foreach ($this->pdo->from('postRelations')->where('post1id', $post->getId()) as $arrayEntity)
{ {
$postId = intval($arrayEntity['post2id']); $postId = intval($arrayEntity['post2id']);
if ($postId !== $post->getId()) if ($postId !== $post->getId())
$relatedPostIds[] = $postId; $relatedPostIds[] = $postId;
} }
foreach ($this->pdo->from('postRelations')->where('post2id', $post->getId()) as $arrayEntity) foreach ($this->pdo->from('postRelations')->where('post2id', $post->getId()) as $arrayEntity)
{ {
$postId = intval($arrayEntity['post1id']); $postId = intval($arrayEntity['post1id']);
if ($postId !== $post->getId()) if ($postId !== $post->getId())
$relatedPostIds[] = $postId; $relatedPostIds[] = $postId;
} }
return $this->findByIds($relatedPostIds); return $this->findByIds($relatedPostIds);
} }
private function syncContent(Post $post) private function syncContent(Post $post)
{ {
$targetPath = $post->getContentPath(); $targetPath = $post->getContentPath();
$content = $post->getContent(); $content = $post->getContent();
if ($content) if ($content)
$this->fileDao->save($targetPath, $content); $this->fileDao->save($targetPath, $content);
else else
$this->fileDao->delete($targetPath, $content); $this->fileDao->delete($targetPath, $content);
$this->thumbnailService->deleteUsedThumbnails($targetPath); $this->thumbnailService->deleteUsedThumbnails($targetPath);
} }
private function syncThumbnailSourceContent(Post $post) private function syncThumbnailSourceContent(Post $post)
{ {
$targetPath = $post->getThumbnailSourceContentPath(); $targetPath = $post->getThumbnailSourceContentPath();
$content = $post->getThumbnailSourceContent(); $content = $post->getThumbnailSourceContent();
if ($content) if ($content)
$this->fileDao->save($targetPath, $content); $this->fileDao->save($targetPath, $content);
else else
$this->fileDao->delete($targetPath); $this->fileDao->delete($targetPath);
$this->thumbnailService->deleteUsedThumbnails($targetPath); $this->thumbnailService->deleteUsedThumbnails($targetPath);
} }
private function syncTags(Post $post) private function syncTags(Post $post)
{ {
$tagIds = array_map( $tagIds = array_map(
function ($tag) function ($tag)
{ {
if (!$tag->getId()) if (!$tag->getId())
throw new \RuntimeException('Unsaved entities found'); throw new \RuntimeException('Unsaved entities found');
return $tag->getId(); return $tag->getId();
}, },
$post->getTags()); $post->getTags());
$existingTagRelationIds = array_map( $existingTagRelationIds = array_map(
function ($arrayEntity) function ($arrayEntity)
{ {
return $arrayEntity['tagId']; return $arrayEntity['tagId'];
}, },
iterator_to_array($this->pdo->from('postTags')->where('postId', $post->getId()))); iterator_to_array($this->pdo->from('postTags')->where('postId', $post->getId())));
$tagRelationsToInsert = array_diff($tagIds, $existingTagRelationIds); $tagRelationsToInsert = array_diff($tagIds, $existingTagRelationIds);
$tagRelationsToDelete = array_diff($existingTagRelationIds, $tagIds); $tagRelationsToDelete = array_diff($existingTagRelationIds, $tagIds);
foreach ($tagRelationsToInsert as $tagId) foreach ($tagRelationsToInsert as $tagId)
{ {
$this->pdo->insertInto('postTags')->values(['postId' => $post->getId(), 'tagId' => $tagId])->execute(); $this->pdo->insertInto('postTags')->values(['postId' => $post->getId(), 'tagId' => $tagId])->execute();
} }
foreach ($tagRelationsToDelete as $tagId) foreach ($tagRelationsToDelete as $tagId)
{ {
$this->pdo->deleteFrom('postTags')->where('postId', $post->getId())->where('tagId', $tagId)->execute(); $this->pdo->deleteFrom('postTags')->where('postId', $post->getId())->where('tagId', $tagId)->execute();
} }
} }
private function syncPostRelations(Post $post) private function syncPostRelations(Post $post)
{ {
$relatedPostIds = array_filter(array_unique(array_map( $relatedPostIds = array_filter(array_unique(array_map(
function ($post) function ($post)
{ {
if (!$post->getId()) if (!$post->getId())
throw new \RuntimeException('Unsaved entities found'); throw new \RuntimeException('Unsaved entities found');
return $post->getId(); return $post->getId();
}, },
$post->getRelatedPosts()))); $post->getRelatedPosts())));
$this->pdo->deleteFrom('postRelations')->where('post1id', $post->getId())->execute(); $this->pdo->deleteFrom('postRelations')->where('post1id', $post->getId())->execute();
$this->pdo->deleteFrom('postRelations')->where('post2id', $post->getId())->execute(); $this->pdo->deleteFrom('postRelations')->where('post2id', $post->getId())->execute();
foreach ($relatedPostIds as $postId) foreach ($relatedPostIds as $postId)
{ {
$this->pdo $this->pdo
->insertInto('postRelations') ->insertInto('postRelations')
->values([ ->values([
'post1id' => $post->getId(), 'post1id' => $post->getId(),
'post2id' => $postId]) 'post2id' => $postId])
->execute(); ->execute();
} }
} }
} }

View file

@ -8,32 +8,32 @@ use Szurubooru\Entities\PostNote;
class PostNoteDao extends AbstractDao implements ICrudDao class PostNoteDao extends AbstractDao implements ICrudDao
{ {
private $postDao; private $postDao;
public function __construct( public function __construct(
DatabaseConnection $databaseConnection, DatabaseConnection $databaseConnection,
PostDao $postDao) PostDao $postDao)
{ {
parent::__construct( parent::__construct(
$databaseConnection, $databaseConnection,
'postNotes', 'postNotes',
new PostNoteEntityConverter()); new PostNoteEntityConverter());
$this->postDao = $postDao; $this->postDao = $postDao;
} }
public function findByPostId($postId) public function findByPostId($postId)
{ {
return $this->findBy('postId', $postId); return $this->findBy('postId', $postId);
} }
protected function afterLoad(Entity $postNote) protected function afterLoad(Entity $postNote)
{ {
$postNote->setLazyLoader( $postNote->setLazyLoader(
PostNote::LAZY_LOADER_POST, PostNote::LAZY_LOADER_POST,
function (PostNote $postNote) function (PostNote $postNote)
{ {
return $this->postDao->findById($postNote->getPostId()); return $this->postDao->findById($postNote->getPostId());
}); });
} }
} }

View file

@ -6,8 +6,8 @@ use Szurubooru\Dao\IFileDao;
class PublicFileDao extends FileDao implements IFileDao class PublicFileDao extends FileDao implements IFileDao
{ {
public function __construct(Config $config) public function __construct(Config $config)
{ {
parent::__construct($config->getPublicDataDirectory()); parent::__construct($config->getPublicDataDirectory());
} }
} }

View file

@ -11,70 +11,70 @@ use Szurubooru\Services\TimeService;
class ScoreDao extends AbstractDao implements ICrudDao class ScoreDao extends AbstractDao implements ICrudDao
{ {
private $timeService; private $timeService;
public function __construct( public function __construct(
DatabaseConnection $databaseConnection, DatabaseConnection $databaseConnection,
TimeService $timeService) TimeService $timeService)
{ {
parent::__construct( parent::__construct(
$databaseConnection, $databaseConnection,
'scores', 'scores',
new ScoreEntityConverter()); new ScoreEntityConverter());
$this->timeService = $timeService; $this->timeService = $timeService;
} }
public function getScoreValue(Entity $entity) public function getScoreValue(Entity $entity)
{ {
$query = $this->getBaseQuery($entity); $query = $this->getBaseQuery($entity);
$query->select(null); $query->select(null);
$query->select('SUM(score) AS score'); $query->select('SUM(score) AS score');
return iterator_to_array($query)[0]['score']; return iterator_to_array($query)[0]['score'];
} }
public function getUserScore(User $user, Entity $entity) public function getUserScore(User $user, Entity $entity)
{ {
$query = $this->getBaseQuery($entity); $query = $this->getBaseQuery($entity);
$query->where('userId', $user->getId()); $query->where('userId', $user->getId());
$arrayEntities = iterator_to_array($query); $arrayEntities = iterator_to_array($query);
$entities = $this->arrayToEntities($arrayEntities); $entities = $this->arrayToEntities($arrayEntities);
return array_shift($entities); return array_shift($entities);
} }
public function setUserScore(User $user, Entity $entity, $scoreValue) public function setUserScore(User $user, Entity $entity, $scoreValue)
{ {
$score = $this->getUserScore($user, $entity); $score = $this->getUserScore($user, $entity);
if (!$score) if (!$score)
{ {
$score = new Score(); $score = new Score();
$score->setTime($this->timeService->getCurrentTime()); $score->setTime($this->timeService->getCurrentTime());
$score->setUserId($user->getId()); $score->setUserId($user->getId());
if ($entity instanceof Post) if ($entity instanceof Post)
$score->setPostId($entity->getId()); $score->setPostId($entity->getId());
elseif ($entity instanceof Comment) elseif ($entity instanceof Comment)
$score->setCommentId($entity->getId()); $score->setCommentId($entity->getId());
else else
throw new \InvalidArgumentException(); throw new \InvalidArgumentException();
} }
$score->setScore($scoreValue); $score->setScore($scoreValue);
$this->save($score); $this->save($score);
return $score; return $score;
} }
private function getBaseQuery($entity) private function getBaseQuery($entity)
{ {
$query = $this->pdo->from($this->tableName); $query = $this->pdo->from($this->tableName);
if ($entity instanceof Post) if ($entity instanceof Post)
$query->where('postId', $entity->getId()); $query->where('postId', $entity->getId());
elseif ($entity instanceof Comment) elseif ($entity instanceof Comment)
$query->where('commentId', $entity->getId()); $query->where('commentId', $entity->getId());
else else
throw new \InvalidArgumentException(); throw new \InvalidArgumentException();
return $query; return $query;
} }
} }

View file

@ -8,47 +8,47 @@ use Szurubooru\Entities\Snapshot;
class SnapshotDao extends AbstractDao class SnapshotDao extends AbstractDao
{ {
private $userDao; private $userDao;
public function __construct( public function __construct(
DatabaseConnection $databaseConnection, DatabaseConnection $databaseConnection,
UserDao $userDao) UserDao $userDao)
{ {
parent::__construct( parent::__construct(
$databaseConnection, $databaseConnection,
'snapshots', 'snapshots',
new SnapshotEntityConverter()); new SnapshotEntityConverter());
$this->userDao = $userDao; $this->userDao = $userDao;
} }
public function findEarlierSnapshots(Snapshot $snapshot) public function findEarlierSnapshots(Snapshot $snapshot)
{ {
$query = $this->pdo $query = $this->pdo
->from($this->tableName) ->from($this->tableName)
->where('type', $snapshot->getType()) ->where('type', $snapshot->getType())
->where('primaryKey', $snapshot->getPrimaryKey()) ->where('primaryKey', $snapshot->getPrimaryKey())
->orderBy('time DESC'); ->orderBy('time DESC');
if ($snapshot->getId()) if ($snapshot->getId())
$query->where('id < ?', $snapshot->getId()); $query->where('id < ?', $snapshot->getId());
return $this->arrayToEntities(iterator_to_array($query)); return $this->arrayToEntities(iterator_to_array($query));
} }
public function afterLoad(Entity $snapshot) public function afterLoad(Entity $snapshot)
{ {
$snapshot->setLazyLoader( $snapshot->setLazyLoader(
Snapshot::LAZY_LOADER_USER, Snapshot::LAZY_LOADER_USER,
function (Snapshot $snapshot) function (Snapshot $snapshot)
{ {
return $this->getUser($snapshot); return $this->getUser($snapshot);
}); });
} }
private function getUser(Snapshot $snapshot) private function getUser(Snapshot $snapshot)
{ {
$userId = $snapshot->getUserId(); $userId = $snapshot->getUserId();
return $this->userDao->findById($userId); return $this->userDao->findById($userId);
} }
} }

View file

@ -10,213 +10,213 @@ use Szurubooru\Search\Requirements\Requirement;
class TagDao extends AbstractDao implements ICrudDao class TagDao extends AbstractDao implements ICrudDao
{ {
const TAG_RELATION_IMPLICATION = 1; const TAG_RELATION_IMPLICATION = 1;
const TAG_RELATION_SUGGESTION = 2; const TAG_RELATION_SUGGESTION = 2;
public function __construct(DatabaseConnection $databaseConnection) public function __construct(DatabaseConnection $databaseConnection)
{ {
parent::__construct( parent::__construct(
$databaseConnection, $databaseConnection,
'tags', 'tags',
new TagEntityConverter()); new TagEntityConverter());
} }
public function findByName($tagName) public function findByName($tagName)
{ {
return $this->findOneBy('name', $tagName); return $this->findOneBy('name', $tagName);
} }
public function findByNames($tagNames) public function findByNames($tagNames)
{ {
return $this->findBy('name', $tagNames); return $this->findBy('name', $tagNames);
} }
public function findByPostId($postId) public function findByPostId($postId)
{ {
return $this->findByPostIds([$postId]); return $this->findByPostIds([$postId]);
} }
public function findByPostIds($postIds) public function findByPostIds($postIds)
{ {
$query = $this->pdo->from($this->tableName) $query = $this->pdo->from($this->tableName)
->innerJoin('postTags', 'postTags.tagId = tags.id') ->innerJoin('postTags', 'postTags.tagId = tags.id')
->where('postTags.postId', $postIds); ->where('postTags.postId', $postIds);
$arrayEntities = iterator_to_array($query); $arrayEntities = iterator_to_array($query);
return $this->arrayToEntities($arrayEntities); return $this->arrayToEntities($arrayEntities);
} }
public function findSiblings($tagName) public function findSiblings($tagName)
{ {
$tag = $this->findByName($tagName); $tag = $this->findByName($tagName);
if (!$tag) if (!$tag)
return []; return [];
$tagId = $tag->getId(); $tagId = $tag->getId();
$query = $this->pdo->from($this->tableName) $query = $this->pdo->from($this->tableName)
->select('COUNT(pt2.postId) AS postCount') ->select('COUNT(pt2.postId) AS postCount')
->innerJoin('postTags pt1', 'pt1.tagId = tags.id') ->innerJoin('postTags pt1', 'pt1.tagId = tags.id')
->innerJoin('postTags pt2', 'pt2.postId = pt1.postId') ->innerJoin('postTags pt2', 'pt2.postId = pt1.postId')
->where('pt2.tagId', $tagId) ->where('pt2.tagId', $tagId)
->groupBy('tags.id') ->groupBy('tags.id')
->orderBy('postCount DESC, name ASC'); ->orderBy('postCount DESC, name ASC');
$arrayEntities = array_filter( $arrayEntities = array_filter(
iterator_to_array($query), iterator_to_array($query),
function($arrayEntity) use ($tagName) function($arrayEntity) use ($tagName)
{ {
return strcasecmp($arrayEntity['name'], $tagName) !== 0; return strcasecmp($arrayEntity['name'], $tagName) !== 0;
}); });
return $this->arrayToEntities($arrayEntities); return $this->arrayToEntities($arrayEntities);
} }
public function export() public function export()
{ {
$exported = []; $exported = [];
foreach ($this->pdo->from($this->tableName) as $arrayEntity) foreach ($this->pdo->from($this->tableName) as $arrayEntity)
{ {
$exported[$arrayEntity['id']] = [ $exported[$arrayEntity['id']] = [
'name' => $arrayEntity['name'], 'name' => $arrayEntity['name'],
'usages' => intval($arrayEntity['usages']), 'usages' => intval($arrayEntity['usages']),
'banned' => boolval($arrayEntity['banned']) 'banned' => boolval($arrayEntity['banned'])
]; ];
} }
//upgrades on old databases //upgrades on old databases
try try
{ {
$relations = iterator_to_array($this->pdo->from('tagRelations')); $relations = iterator_to_array($this->pdo->from('tagRelations'));
} }
catch (\Exception $e) catch (\Exception $e)
{ {
$relations = []; $relations = [];
} }
foreach ($relations as $arrayEntity) foreach ($relations as $arrayEntity)
{ {
$key1 = $arrayEntity['tag1id']; $key1 = $arrayEntity['tag1id'];
$key2 = $arrayEntity['tag2id']; $key2 = $arrayEntity['tag2id'];
$type = intval($arrayEntity['type']); $type = intval($arrayEntity['type']);
if ($type === self::TAG_RELATION_IMPLICATION) if ($type === self::TAG_RELATION_IMPLICATION)
$target = 'implications'; $target = 'implications';
elseif ($type === self::TAG_RELATION_SUGGESTION) elseif ($type === self::TAG_RELATION_SUGGESTION)
$target = 'suggestions'; $target = 'suggestions';
else else
continue; continue;
if (!isset($exported[$key1]) || !isset($exported[$key2])) if (!isset($exported[$key1]) || !isset($exported[$key2]))
continue; continue;
if (!isset($exported[$key1][$target])) if (!isset($exported[$key1][$target]))
$exported[$key1][$target] = []; $exported[$key1][$target] = [];
$exported[$key1][$target][] = $exported[$key2]['name']; $exported[$key1][$target][] = $exported[$key2]['name'];
} }
return array_values($exported); return array_values($exported);
} }
protected function afterLoad(Entity $tag) protected function afterLoad(Entity $tag)
{ {
$tag->setLazyLoader( $tag->setLazyLoader(
Tag::LAZY_LOADER_IMPLIED_TAGS, Tag::LAZY_LOADER_IMPLIED_TAGS,
function (Tag $tag) function (Tag $tag)
{ {
return $this->findImpliedTags($tag); return $this->findImpliedTags($tag);
}); });
$tag->setLazyLoader( $tag->setLazyLoader(
Tag::LAZY_LOADER_SUGGESTED_TAGS, Tag::LAZY_LOADER_SUGGESTED_TAGS,
function (Tag $tag) function (Tag $tag)
{ {
return $this->findSuggested($tag); return $this->findSuggested($tag);
}); });
} }
protected function afterSave(Entity $tag) protected function afterSave(Entity $tag)
{ {
$this->syncImpliedTags($tag); $this->syncImpliedTags($tag);
$this->syncSuggestedTags($tag); $this->syncSuggestedTags($tag);
} }
protected function decorateQueryFromRequirement($query, Requirement $requirement) protected function decorateQueryFromRequirement($query, Requirement $requirement)
{ {
if ($requirement->getType() === TagFilter::REQUIREMENT_PARTIAL_TAG_NAME) if ($requirement->getType() === TagFilter::REQUIREMENT_PARTIAL_TAG_NAME)
{ {
$sql = 'INSTR(LOWER(tags.name), LOWER(?)) > 0'; $sql = 'INSTR(LOWER(tags.name), LOWER(?)) > 0';
if ($requirement->isNegated()) if ($requirement->isNegated())
$sql = 'NOT ' . $sql; $sql = 'NOT ' . $sql;
$query->where($sql, $requirement->getValue()->getValue()); $query->where($sql, $requirement->getValue()->getValue());
return; return;
} }
elseif ($requirement->getType() === TagFilter::REQUIREMENT_CATEGORY) elseif ($requirement->getType() === TagFilter::REQUIREMENT_CATEGORY)
{ {
$sql = 'IFNULL(category, \'default\')'; $sql = 'IFNULL(category, \'default\')';
$requirement->setType($sql); $requirement->setType($sql);
return parent::decorateQueryFromRequirement($query, $requirement); return parent::decorateQueryFromRequirement($query, $requirement);
} }
parent::decorateQueryFromRequirement($query, $requirement); parent::decorateQueryFromRequirement($query, $requirement);
} }
private function findImpliedTags(Tag $tag) private function findImpliedTags(Tag $tag)
{ {
return $this->findRelatedTagsByType($tag, self::TAG_RELATION_IMPLICATION); return $this->findRelatedTagsByType($tag, self::TAG_RELATION_IMPLICATION);
} }
private function findSuggested(Tag $tag) private function findSuggested(Tag $tag)
{ {
return $this->findRelatedTagsByType($tag, self::TAG_RELATION_SUGGESTION); return $this->findRelatedTagsByType($tag, self::TAG_RELATION_SUGGESTION);
} }
private function syncImpliedTags($tag) private function syncImpliedTags($tag)
{ {
$this->syncRelatedTagsByType($tag, $tag->getImpliedTags(), self::TAG_RELATION_IMPLICATION); $this->syncRelatedTagsByType($tag, $tag->getImpliedTags(), self::TAG_RELATION_IMPLICATION);
} }
private function syncSuggestedTags($tag) private function syncSuggestedTags($tag)
{ {
$this->syncRelatedTagsByType($tag, $tag->getSuggestedTags(), self::TAG_RELATION_SUGGESTION); $this->syncRelatedTagsByType($tag, $tag->getSuggestedTags(), self::TAG_RELATION_SUGGESTION);
} }
private function syncRelatedTagsByType(Tag $tag, array $relatedTags, $type) private function syncRelatedTagsByType(Tag $tag, array $relatedTags, $type)
{ {
$relatedTagIds = array_filter(array_unique(array_map( $relatedTagIds = array_filter(array_unique(array_map(
function ($tag) function ($tag)
{ {
if (!$tag->getId()) if (!$tag->getId())
throw new \RuntimeException('Unsaved entities found'); throw new \RuntimeException('Unsaved entities found');
return $tag->getId(); return $tag->getId();
}, },
$relatedTags))); $relatedTags)));
$this->pdo->deleteFrom('tagRelations') $this->pdo->deleteFrom('tagRelations')
->where('tag1id', $tag->getId()) ->where('tag1id', $tag->getId())
->where('type', $type) ->where('type', $type)
->execute(); ->execute();
foreach ($relatedTagIds as $tagId) foreach ($relatedTagIds as $tagId)
{ {
$this->pdo $this->pdo
->insertInto('tagRelations') ->insertInto('tagRelations')
->values([ ->values([
'tag1id' => $tag->getId(), 'tag1id' => $tag->getId(),
'tag2id' => $tagId, 'tag2id' => $tagId,
'type' => $type]) 'type' => $type])
->execute(); ->execute();
} }
} }
private function findRelatedTagsByType(Tag $tag, $type) private function findRelatedTagsByType(Tag $tag, $type)
{ {
$tagId = $tag->getId(); $tagId = $tag->getId();
$query = $this->pdo->from($this->tableName) $query = $this->pdo->from($this->tableName)
->innerJoin('tagRelations tr', 'tags.id = tr.tag2id') ->innerJoin('tagRelations tr', 'tags.id = tr.tag2id')
->where('tr.type', $type) ->where('tr.type', $type)
->where('tr.tag1id', $tagId); ->where('tr.tag1id', $tagId);
$arrayEntities = iterator_to_array($query); $arrayEntities = iterator_to_array($query);
return $this->arrayToEntities($arrayEntities); return $this->arrayToEntities($arrayEntities);
} }
} }

View file

@ -5,39 +5,39 @@ use Szurubooru\DatabaseConnection;
class TokenDao extends AbstractDao class TokenDao extends AbstractDao
{ {
public function __construct(DatabaseConnection $databaseConnection) public function __construct(DatabaseConnection $databaseConnection)
{ {
parent::__construct( parent::__construct(
$databaseConnection, $databaseConnection,
'tokens', 'tokens',
new TokenEntityConverter()); new TokenEntityConverter());
} }
public function findByName($tokenName) public function findByName($tokenName)
{ {
return $this->findOneBy('name', $tokenName); return $this->findOneBy('name', $tokenName);
} }
public function findByAdditionalDataAndPurpose($additionalData, $purpose) public function findByAdditionalDataAndPurpose($additionalData, $purpose)
{ {
$query = $this->pdo->from($this->tableName) $query = $this->pdo->from($this->tableName)
->where('additionalData', $additionalData) ->where('additionalData', $additionalData)
->where('purpose', $purpose); ->where('purpose', $purpose);
$arrayEntities = iterator_to_array($query); $arrayEntities = iterator_to_array($query);
$entities = $this->arrayToEntities($arrayEntities); $entities = $this->arrayToEntities($arrayEntities);
if (!$entities || !count($entities)) if (!$entities || !count($entities))
return null; return null;
$entity = array_shift($entities); $entity = array_shift($entities);
return $entity; return $entity;
} }
public function deleteByName($tokenName) public function deleteByName($tokenName)
{ {
return $this->deleteBy('name', $tokenName); return $this->deleteBy('name', $tokenName);
} }
public function deleteByAdditionalData($additionalData) public function deleteByAdditionalData($additionalData)
{ {
return $this->deleteBy('additionalData', $additionalData); return $this->deleteBy('additionalData', $additionalData);
} }
} }

View file

@ -4,40 +4,40 @@ use Szurubooru\DatabaseConnection;
class TransactionManager class TransactionManager
{ {
private $databaseConnection; private $databaseConnection;
public function __construct(DatabaseConnection $databaseConnection) public function __construct(DatabaseConnection $databaseConnection)
{ {
$this->databaseConnection = $databaseConnection; $this->databaseConnection = $databaseConnection;
} }
public function commit($callback) public function commit($callback)
{ {
return $this->doInTransaction($callback, 'commit'); return $this->doInTransaction($callback, 'commit');
} }
public function rollback($callback) public function rollback($callback)
{ {
return $this->doInTransaction($callback, 'rollBack'); return $this->doInTransaction($callback, 'rollBack');
} }
public function doInTransaction($callback, $operation) public function doInTransaction($callback, $operation)
{ {
$pdo = $this->databaseConnection->getPDO(); $pdo = $this->databaseConnection->getPDO();
if ($pdo->inTransaction()) if ($pdo->inTransaction())
return $callback(); return $callback();
$pdo->beginTransaction(); $pdo->beginTransaction();
try try
{ {
$ret = $callback(); $ret = $callback();
$pdo->$operation(); $pdo->$operation();
return $ret; return $ret;
} }
catch (\Exception $e) catch (\Exception $e)
{ {
$pdo->rollBack(); $pdo->rollBack();
throw $e; throw $e;
} }
} }
} }

View file

@ -9,78 +9,78 @@ use Szurubooru\Services\ThumbnailService;
class UserDao extends AbstractDao implements ICrudDao class UserDao extends AbstractDao implements ICrudDao
{ {
const ORDER_NAME = 'name'; const ORDER_NAME = 'name';
const ORDER_REGISTRATION_TIME = 'registrationTime'; const ORDER_REGISTRATION_TIME = 'registrationTime';
private $fileDao; private $fileDao;
private $thumbnailService; private $thumbnailService;
public function __construct( public function __construct(
DatabaseConnection $databaseConnection, DatabaseConnection $databaseConnection,
PublicFileDao $fileDao, PublicFileDao $fileDao,
ThumbnailService $thumbnailService) ThumbnailService $thumbnailService)
{ {
parent::__construct( parent::__construct(
$databaseConnection, $databaseConnection,
'users', 'users',
new UserEntityConverter()); new UserEntityConverter());
$this->fileDao = $fileDao; $this->fileDao = $fileDao;
$this->thumbnailService = $thumbnailService; $this->thumbnailService = $thumbnailService;
} }
public function findByName($userName) public function findByName($userName)
{ {
return $this->findOneBy('name', $userName); return $this->findOneBy('name', $userName);
} }
public function findByEmail($userEmail, $allowUnconfirmed = false) public function findByEmail($userEmail, $allowUnconfirmed = false)
{ {
$result = $this->findOneBy('email', $userEmail); $result = $this->findOneBy('email', $userEmail);
if (!$result && $allowUnconfirmed) if (!$result && $allowUnconfirmed)
{ {
$result = $this->findOneBy('emailUnconfirmed', $userEmail); $result = $this->findOneBy('emailUnconfirmed', $userEmail);
} }
return $result; return $result;
} }
public function hasAnyUsers() public function hasAnyUsers()
{ {
return $this->hasAnyRecords(); return $this->hasAnyRecords();
} }
public function deleteByName($userName) public function deleteByName($userName)
{ {
$this->deleteBy('name', $userName); $this->deleteBy('name', $userName);
$this->pdo->deleteFrom('tokens')->where('additionalData', $userName); $this->pdo->deleteFrom('tokens')->where('additionalData', $userName);
} }
protected function afterLoad(Entity $user) protected function afterLoad(Entity $user)
{ {
$user->setLazyLoader( $user->setLazyLoader(
User::LAZY_LOADER_CUSTOM_AVATAR_SOURCE_CONTENT, User::LAZY_LOADER_CUSTOM_AVATAR_SOURCE_CONTENT,
function(User $user) function(User $user)
{ {
$avatarSource = $user->getCustomAvatarSourceContentPath(); $avatarSource = $user->getCustomAvatarSourceContentPath();
return $this->fileDao->load($avatarSource); return $this->fileDao->load($avatarSource);
}); });
} }
protected function afterSave(Entity $user) protected function afterSave(Entity $user)
{ {
$targetPath = $user->getCustomAvatarSourceContentPath(); $targetPath = $user->getCustomAvatarSourceContentPath();
$content = $user->getCustomAvatarSourceContent(); $content = $user->getCustomAvatarSourceContent();
if ($content) if ($content)
$this->fileDao->save($targetPath, $content); $this->fileDao->save($targetPath, $content);
else else
$this->fileDao->delete($targetPath); $this->fileDao->delete($targetPath);
$this->thumbnailService->deleteUsedThumbnails($targetPath); $this->thumbnailService->deleteUsedThumbnails($targetPath);
} }
protected function afterDelete(Entity $user) protected function afterDelete(Entity $user)
{ {
$avatarSource = $user->getCustomAvatarSourceContentPath(); $avatarSource = $user->getCustomAvatarSourceContentPath();
$this->fileDao->delete($avatarSource); $this->fileDao->delete($avatarSource);
$this->thumbnailService->deleteUsedThumbnails($avatarSource); $this->thumbnailService->deleteUsedThumbnails($avatarSource);
} }
} }

View file

@ -5,45 +5,45 @@ use Szurubooru\PDOEx\PDOEx;
class DatabaseConnection class DatabaseConnection
{ {
private $pdo; private $pdo;
private $config; private $config;
public function __construct(Config $config) public function __construct(Config $config)
{ {
$this->config = $config; $this->config = $config;
} }
public function getPDO() public function getPDO()
{ {
if (!$this->pdo) if (!$this->pdo)
{ {
$this->createPDO(); $this->createPDO();
} }
return $this->pdo; return $this->pdo;
} }
public function getDriver() public function getDriver()
{ {
return $this->getPDO()->getAttribute(\PDO::ATTR_DRIVER_NAME); return $this->getPDO()->getAttribute(\PDO::ATTR_DRIVER_NAME);
} }
public function close() public function close()
{ {
$this->pdo = null; $this->pdo = null;
} }
private function createPDO() private function createPDO()
{ {
$cwd = getcwd(); $cwd = getcwd();
if ($this->config->getDataDirectory()) if ($this->config->getDataDirectory())
chdir($this->config->getDataDirectory()); chdir($this->config->getDataDirectory());
$this->pdo = new PDOEx( $this->pdo = new PDOEx(
$this->config->database->dsn, $this->config->database->dsn,
$this->config->database->user, $this->config->database->user,
$this->config->database->password); $this->config->database->password);
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
chdir($cwd); chdir($cwd);
} }
} }

View file

@ -10,78 +10,78 @@ use Szurubooru\Services\TokenService;
final class Dispatcher final class Dispatcher
{ {
private $router; private $router;
private $config; private $config;
private $databaseConnection; private $databaseConnection;
private $authService; private $authService;
private $tokenService; private $tokenService;
public function __construct( public function __construct(
Router $router, Router $router,
Config $config, Config $config,
DatabaseConnection $databaseConnection, DatabaseConnection $databaseConnection,
HttpHelper $httpHelper, HttpHelper $httpHelper,
AuthService $authService, AuthService $authService,
TokenService $tokenService, TokenService $tokenService,
RouteRepository $routeRepository) RouteRepository $routeRepository)
{ {
$this->router = $router; $this->router = $router;
$this->config = $config; $this->config = $config;
$this->databaseConnection = $databaseConnection; $this->databaseConnection = $databaseConnection;
$this->httpHelper = $httpHelper; $this->httpHelper = $httpHelper;
$this->authService = $authService; $this->authService = $authService;
$this->tokenService = $tokenService; $this->tokenService = $tokenService;
//if script fails prematurely, mark it as fail from advance //if script fails prematurely, mark it as fail from advance
$this->httpHelper->setResponseCode(500); $this->httpHelper->setResponseCode(500);
$routeRepository->injectRoutes($router); $routeRepository->injectRoutes($router);
} }
public function run($requestMethod, $requestUri) public function run($requestMethod, $requestUri)
{ {
try try
{ {
$code = 200; $code = 200;
$this->authorizeFromRequestHeader(); $this->authorizeFromRequestHeader();
$json = (array) $this->router->handle($requestMethod, $requestUri); $json = (array) $this->router->handle($requestMethod, $requestUri);
} }
catch (\Exception $e) catch (\Exception $e)
{ {
$code = 400; $code = 400;
$trace = $e->getTrace(); $trace = $e->getTrace();
foreach ($trace as &$item) foreach ($trace as &$item)
unset($item['args']); unset($item['args']);
$json = [ $json = [
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'trace' => $trace, 'trace' => $trace,
]; ];
} }
$end = microtime(true); $end = microtime(true);
$json['__time'] = $end - Bootstrap::getStartTime(); $json['__time'] = $end - Bootstrap::getStartTime();
if ($this->config->misc->dumpSqlIntoQueries) if ($this->config->misc->dumpSqlIntoQueries)
{ {
$json['__queries'] = $this->databaseConnection->getPDO()->getQueryCount(); $json['__queries'] = $this->databaseConnection->getPDO()->getQueryCount();
$json['__statements'] = $this->databaseConnection->getPDO()->getStatements(); $json['__statements'] = $this->databaseConnection->getPDO()->getStatements();
} }
if (!$this->httpHelper->isRedirecting()) if (!$this->httpHelper->isRedirecting())
{ {
$this->httpHelper->setResponseCode($code); $this->httpHelper->setResponseCode($code);
$this->httpHelper->setHeader('Content-Type', 'application/json'); $this->httpHelper->setHeader('Content-Type', 'application/json');
$this->httpHelper->outputJSON($json); $this->httpHelper->outputJSON($json);
} }
return $json; return $json;
} }
private function authorizeFromRequestHeader() private function authorizeFromRequestHeader()
{ {
$loginTokenName = $this->httpHelper->getRequestHeader('X-Authorization-Token'); $loginTokenName = $this->httpHelper->getRequestHeader('X-Authorization-Token');
if ($loginTokenName) if ($loginTokenName)
{ {
$token = $this->tokenService->getByName($loginTokenName); $token = $this->tokenService->getByName($loginTokenName);
$this->authService->loginFromToken($token); $this->authService->loginFromToken($token);
} }
} }
} }

View file

@ -5,91 +5,91 @@ use Szurubooru\Entities\User;
final class Comment extends Entity final class Comment extends Entity
{ {
private $postId; private $postId;
private $userId; private $userId;
private $creationTime; private $creationTime;
private $lastEditTime; private $lastEditTime;
private $text; private $text;
const LAZY_LOADER_USER = 'user'; const LAZY_LOADER_USER = 'user';
const LAZY_LOADER_POST = 'post'; const LAZY_LOADER_POST = 'post';
const META_SCORE = 'score'; const META_SCORE = 'score';
public function getUserId() public function getUserId()
{ {
return $this->userId; return $this->userId;
} }
public function setUserId($userId) public function setUserId($userId)
{ {
$this->userId = $userId; $this->userId = $userId;
} }
public function getPostId() public function getPostId()
{ {
return $this->postId; return $this->postId;
} }
public function setPostId($postId) public function setPostId($postId)
{ {
$this->postId = $postId; $this->postId = $postId;
} }
public function getCreationTime() public function getCreationTime()
{ {
return $this->creationTime; return $this->creationTime;
} }
public function setCreationTime($creationTime) public function setCreationTime($creationTime)
{ {
$this->creationTime = $creationTime; $this->creationTime = $creationTime;
} }
public function getLastEditTime() public function getLastEditTime()
{ {
return $this->lastEditTime; return $this->lastEditTime;
} }
public function setLastEditTime($lastEditTime) public function setLastEditTime($lastEditTime)
{ {
$this->lastEditTime = $lastEditTime; $this->lastEditTime = $lastEditTime;
} }
public function getText() public function getText()
{ {
return $this->text; return $this->text;
} }
public function setText($text) public function setText($text)
{ {
$this->text = $text; $this->text = $text;
} }
public function getUser() public function getUser()
{ {
return $this->lazyLoad(self::LAZY_LOADER_USER, null); return $this->lazyLoad(self::LAZY_LOADER_USER, null);
} }
public function setUser(User $user = null) public function setUser(User $user = null)
{ {
$this->lazySave(self::LAZY_LOADER_USER, $user); $this->lazySave(self::LAZY_LOADER_USER, $user);
$this->userId = $user ? $user->getId() : null; $this->userId = $user ? $user->getId() : null;
} }
public function getPost() public function getPost()
{ {
return $this->lazyLoad(self::LAZY_LOADER_POST, null); return $this->lazyLoad(self::LAZY_LOADER_POST, null);
} }
public function setPost(Post $post) public function setPost(Post $post)
{ {
$this->lazySave(self::LAZY_LOADER_POST, $post); $this->lazySave(self::LAZY_LOADER_POST, $post);
$this->postId = $post->getId(); $this->postId = $post->getId();
} }
public function getScore() public function getScore()
{ {
return $this->getMeta(self::META_SCORE, 0); return $this->getMeta(self::META_SCORE, 0);
} }
} }

View file

@ -3,74 +3,74 @@ namespace Szurubooru\Entities;
abstract class Entity abstract class Entity
{ {
protected $id = null; protected $id = null;
private $lazyLoaders = []; private $lazyLoaders = [];
private $lazyContainers = []; private $lazyContainers = [];
private $meta; private $meta;
public function __construct($id = null) public function __construct($id = null)
{ {
$this->id = $id === null ? null : intval($id); $this->id = $id === null ? null : intval($id);
} }
public function getId() public function getId()
{ {
return $this->id; return $this->id;
} }
public function setId($id) public function setId($id)
{ {
$this->id = $id; $this->id = $id;
} }
public function getMeta($metaName, $default = null) public function getMeta($metaName, $default = null)
{ {
if (!isset($this->meta[$metaName])) if (!isset($this->meta[$metaName]))
return $default; return $default;
return $this->meta[$metaName]; return $this->meta[$metaName];
} }
public function setMeta($metaName, $value) public function setMeta($metaName, $value)
{ {
$this->meta[$metaName] = $value; $this->meta[$metaName] = $value;
} }
public function resetMeta() public function resetMeta()
{ {
$this->meta = []; $this->meta = [];
} }
public function resetLazyLoaders() public function resetLazyLoaders()
{ {
$this->lazyLoaders = []; $this->lazyLoaders = [];
$this->lazyContainers = []; $this->lazyContainers = [];
} }
public function setLazyLoader($lazyContainerName, $getter) public function setLazyLoader($lazyContainerName, $getter)
{ {
$this->lazyLoaders[$lazyContainerName] = $getter; $this->lazyLoaders[$lazyContainerName] = $getter;
} }
protected function lazyLoad($lazyContainerName, $defaultValue) protected function lazyLoad($lazyContainerName, $defaultValue)
{ {
if (!isset($this->lazyContainers[$lazyContainerName])) if (!isset($this->lazyContainers[$lazyContainerName]))
{ {
if (!isset($this->lazyLoaders[$lazyContainerName])) if (!isset($this->lazyLoaders[$lazyContainerName]))
{ {
return $defaultValue; return $defaultValue;
} }
$result = $this->lazyLoaders[$lazyContainerName]($this); $result = $this->lazyLoaders[$lazyContainerName]($this);
$this->lazySave($lazyContainerName, $result); $this->lazySave($lazyContainerName, $result);
} }
else else
{ {
$result = $this->lazyContainers[$lazyContainerName]; $result = $this->lazyContainers[$lazyContainerName];
} }
return $result; return $result;
} }
protected function lazySave($lazyContainerName, $value) protected function lazySave($lazyContainerName, $value)
{ {
$this->lazyContainers[$lazyContainerName] = $value; $this->lazyContainers[$lazyContainerName] = $value;
} }
} }

View file

@ -5,62 +5,62 @@ use Szurubooru\Entities\User;
final class Favorite extends Entity final class Favorite extends Entity
{ {
private $postId; private $postId;
private $userId; private $userId;
private $time; private $time;
const LAZY_LOADER_USER = 'user'; const LAZY_LOADER_USER = 'user';
const LAZY_LOADER_POST = 'post'; const LAZY_LOADER_POST = 'post';
public function getUserId() public function getUserId()
{ {
return $this->userId; return $this->userId;
} }
public function setUserId($userId) public function setUserId($userId)
{ {
$this->userId = $userId; $this->userId = $userId;
} }
public function getPostId() public function getPostId()
{ {
return $this->postId; return $this->postId;
} }
public function setPostId($postId) public function setPostId($postId)
{ {
$this->postId = $postId; $this->postId = $postId;
} }
public function getTime() public function getTime()
{ {
return $this->time; return $this->time;
} }
public function setTime($time) public function setTime($time)
{ {
$this->time = $time; $this->time = $time;
} }
public function getUser() public function getUser()
{ {
return $this->lazyLoad(self::LAZY_LOADER_USER, null); return $this->lazyLoad(self::LAZY_LOADER_USER, null);
} }
public function setUser(User $user) public function setUser(User $user)
{ {
$this->lazySave(self::LAZY_LOADER_USER, $user); $this->lazySave(self::LAZY_LOADER_USER, $user);
$this->userId = $user->getId(); $this->userId = $user->getId();
} }
public function getPost() public function getPost()
{ {
return $this->lazyLoad(self::LAZY_LOADER_POST, null); return $this->lazyLoad(self::LAZY_LOADER_POST, null);
} }
public function setPost(Post $post) public function setPost(Post $post)
{ {
$this->lazySave(self::LAZY_LOADER_POST, $post); $this->lazySave(self::LAZY_LOADER_POST, $post);
$this->postId = $post->getId(); $this->postId = $post->getId();
} }
} }

View file

@ -3,31 +3,31 @@ namespace Szurubooru\Entities;
final class GlobalParam extends Entity final class GlobalParam extends Entity
{ {
const KEY_FEATURED_POST_USER = 'featuredPostUser'; const KEY_FEATURED_POST_USER = 'featuredPostUser';
const KEY_FEATURED_POST = 'featuredPost'; const KEY_FEATURED_POST = 'featuredPost';
const KEY_POST_SIZE = 'postSize'; const KEY_POST_SIZE = 'postSize';
const KEY_POST_COUNT = 'postCount'; const KEY_POST_COUNT = 'postCount';
private $key; private $key;
private $value; private $value;
public function getKey() public function getKey()
{ {
return $this->key; return $this->key;
} }
public function setKey($key) public function setKey($key)
{ {
$this->key = $key; $this->key = $key;
} }
public function getValue() public function getValue()
{ {
return $this->value; return $this->value;
} }
public function setValue($value) public function setValue($value)
{ {
$this->value = $value; $this->value = $value;
} }
} }

View file

@ -4,290 +4,290 @@ use Szurubooru\Entities\User;
final class Post extends Entity final class Post extends Entity
{ {
const POST_SAFETY_SAFE = 1; const POST_SAFETY_SAFE = 1;
const POST_SAFETY_SKETCHY = 2; const POST_SAFETY_SKETCHY = 2;
const POST_SAFETY_UNSAFE = 3; const POST_SAFETY_UNSAFE = 3;
const POST_TYPE_IMAGE = 1; const POST_TYPE_IMAGE = 1;
const POST_TYPE_FLASH = 2; const POST_TYPE_FLASH = 2;
const POST_TYPE_VIDEO = 3; const POST_TYPE_VIDEO = 3;
const POST_TYPE_YOUTUBE = 4; const POST_TYPE_YOUTUBE = 4;
const POST_TYPE_ANIMATED_IMAGE = 5; const POST_TYPE_ANIMATED_IMAGE = 5;
const FLAG_LOOP = 1; const FLAG_LOOP = 1;
const LAZY_LOADER_USER = 'user'; const LAZY_LOADER_USER = 'user';
const LAZY_LOADER_TAGS = 'tags'; const LAZY_LOADER_TAGS = 'tags';
const LAZY_LOADER_CONTENT = 'content'; const LAZY_LOADER_CONTENT = 'content';
const LAZY_LOADER_THUMBNAIL_SOURCE_CONTENT = 'thumbnailSourceContent'; const LAZY_LOADER_THUMBNAIL_SOURCE_CONTENT = 'thumbnailSourceContent';
const LAZY_LOADER_RELATED_POSTS = 'relatedPosts'; const LAZY_LOADER_RELATED_POSTS = 'relatedPosts';
const META_TAG_COUNT = 'tagCount'; const META_TAG_COUNT = 'tagCount';
const META_FAV_COUNT = 'favCount'; const META_FAV_COUNT = 'favCount';
const META_COMMENT_COUNT = 'commentCount'; const META_COMMENT_COUNT = 'commentCount';
const META_SCORE = 'score'; const META_SCORE = 'score';
private $name; private $name;
private $userId; private $userId;
private $uploadTime; private $uploadTime;
private $lastEditTime; private $lastEditTime;
private $safety; private $safety;
private $contentType; private $contentType;
private $contentChecksum; private $contentChecksum;
private $contentMimeType; private $contentMimeType;
private $source; private $source;
private $imageWidth; private $imageWidth;
private $imageHeight; private $imageHeight;
private $originalFileSize; private $originalFileSize;
private $originalFileName; private $originalFileName;
private $featureCount = 0; private $featureCount = 0;
private $lastFeatureTime; private $lastFeatureTime;
private $flags = 0; private $flags = 0;
public function getIdMarkdown() public function getIdMarkdown()
{ {
return '@' . $this->id; return '@' . $this->id;
} }
public function getName() public function getName()
{ {
return $this->name; return $this->name;
} }
public function setName($name) public function setName($name)
{ {
$this->name = $name; $this->name = $name;
} }
public function getUserId() public function getUserId()
{ {
return $this->userId; return $this->userId;
} }
public function setUserId($userId) public function setUserId($userId)
{ {
$this->userId = $userId; $this->userId = $userId;
} }
public function getSafety() public function getSafety()
{ {
return $this->safety; return $this->safety;
} }
public function setSafety($safety) public function setSafety($safety)
{ {
$this->safety = $safety; $this->safety = $safety;
} }
public function getUploadTime() public function getUploadTime()
{ {
return $this->uploadTime; return $this->uploadTime;
} }
public function setUploadTime($uploadTime) public function setUploadTime($uploadTime)
{ {
$this->uploadTime = $uploadTime; $this->uploadTime = $uploadTime;
} }
public function getLastEditTime() public function getLastEditTime()
{ {
return $this->lastEditTime; return $this->lastEditTime;
} }
public function setLastEditTime($lastEditTime) public function setLastEditTime($lastEditTime)
{ {
$this->lastEditTime = $lastEditTime; $this->lastEditTime = $lastEditTime;
} }
public function getContentType() public function getContentType()
{ {
return $this->contentType; return $this->contentType;
} }
public function setContentType($contentType) public function setContentType($contentType)
{ {
$this->contentType = $contentType; $this->contentType = $contentType;
} }
public function getContentChecksum() public function getContentChecksum()
{ {
return $this->contentChecksum; return $this->contentChecksum;
} }
public function setContentChecksum($contentChecksum) public function setContentChecksum($contentChecksum)
{ {
$this->contentChecksum = $contentChecksum; $this->contentChecksum = $contentChecksum;
} }
public function getContentMimeType() public function getContentMimeType()
{ {
return $this->contentMimeType; return $this->contentMimeType;
} }
public function setContentMimeType($contentMimeType) public function setContentMimeType($contentMimeType)
{ {
$this->contentMimeType = $contentMimeType; $this->contentMimeType = $contentMimeType;
} }
public function getSource() public function getSource()
{ {
return $this->source; return $this->source;
} }
public function setSource($source) public function setSource($source)
{ {
$this->source = $source; $this->source = $source;
} }
public function getImageWidth() public function getImageWidth()
{ {
return $this->imageWidth; return $this->imageWidth;
} }
public function setImageWidth($imageWidth) public function setImageWidth($imageWidth)
{ {
$this->imageWidth = $imageWidth; $this->imageWidth = $imageWidth;
} }
public function getImageHeight() public function getImageHeight()
{ {
return $this->imageHeight; return $this->imageHeight;
} }
public function setImageHeight($imageHeight) public function setImageHeight($imageHeight)
{ {
$this->imageHeight = $imageHeight; $this->imageHeight = $imageHeight;
} }
public function getOriginalFileSize() public function getOriginalFileSize()
{ {
return $this->originalFileSize; return $this->originalFileSize;
} }
public function setOriginalFileSize($originalFileSize) public function setOriginalFileSize($originalFileSize)
{ {
$this->originalFileSize = $originalFileSize; $this->originalFileSize = $originalFileSize;
} }
public function getOriginalFileName() public function getOriginalFileName()
{ {
return $this->originalFileName; return $this->originalFileName;
} }
public function setOriginalFileName($originalFileName) public function setOriginalFileName($originalFileName)
{ {
$this->originalFileName = $originalFileName; $this->originalFileName = $originalFileName;
} }
public function getFeatureCount() public function getFeatureCount()
{ {
return $this->featureCount; return $this->featureCount;
} }
public function setFeatureCount($featureCount) public function setFeatureCount($featureCount)
{ {
$this->featureCount = $featureCount; $this->featureCount = $featureCount;
} }
public function getLastFeatureTime() public function getLastFeatureTime()
{ {
return $this->lastFeatureTime; return $this->lastFeatureTime;
} }
public function setLastFeatureTime($lastFeatureTime) public function setLastFeatureTime($lastFeatureTime)
{ {
$this->lastFeatureTime = $lastFeatureTime; $this->lastFeatureTime = $lastFeatureTime;
} }
public function getFlags() public function getFlags()
{ {
return $this->flags; return $this->flags;
} }
public function setFlags($flags) public function setFlags($flags)
{ {
$this->flags = $flags; $this->flags = $flags;
} }
public function getTags() public function getTags()
{ {
return $this->lazyLoad(self::LAZY_LOADER_TAGS, []); return $this->lazyLoad(self::LAZY_LOADER_TAGS, []);
} }
public function setTags(array $tags) public function setTags(array $tags)
{ {
$this->lazySave(self::LAZY_LOADER_TAGS, $tags); $this->lazySave(self::LAZY_LOADER_TAGS, $tags);
$this->setMeta(self::META_TAG_COUNT, count($tags)); $this->setMeta(self::META_TAG_COUNT, count($tags));
} }
public function getRelatedPosts() public function getRelatedPosts()
{ {
return $this->lazyLoad(self::LAZY_LOADER_RELATED_POSTS, []); return $this->lazyLoad(self::LAZY_LOADER_RELATED_POSTS, []);
} }
public function setRelatedPosts(array $relatedPosts) public function setRelatedPosts(array $relatedPosts)
{ {
$this->lazySave(self::LAZY_LOADER_RELATED_POSTS, $relatedPosts); $this->lazySave(self::LAZY_LOADER_RELATED_POSTS, $relatedPosts);
} }
public function getUser() public function getUser()
{ {
return $this->lazyLoad(self::LAZY_LOADER_USER, null); return $this->lazyLoad(self::LAZY_LOADER_USER, null);
} }
public function setUser(User $user = null) public function setUser(User $user = null)
{ {
$this->lazySave(self::LAZY_LOADER_USER, $user); $this->lazySave(self::LAZY_LOADER_USER, $user);
$this->userId = $user ? $user->getId() : null; $this->userId = $user ? $user->getId() : null;
} }
public function getContent() public function getContent()
{ {
return $this->lazyLoad(self::LAZY_LOADER_CONTENT, null); return $this->lazyLoad(self::LAZY_LOADER_CONTENT, null);
} }
public function setContent($content) public function setContent($content)
{ {
$this->lazySave(self::LAZY_LOADER_CONTENT, $content); $this->lazySave(self::LAZY_LOADER_CONTENT, $content);
} }
public function getThumbnailSourceContent() public function getThumbnailSourceContent()
{ {
return $this->lazyLoad(self::LAZY_LOADER_THUMBNAIL_SOURCE_CONTENT, null); return $this->lazyLoad(self::LAZY_LOADER_THUMBNAIL_SOURCE_CONTENT, null);
} }
public function setThumbnailSourceContent($content) public function setThumbnailSourceContent($content)
{ {
$this->lazySave(self::LAZY_LOADER_THUMBNAIL_SOURCE_CONTENT, $content); $this->lazySave(self::LAZY_LOADER_THUMBNAIL_SOURCE_CONTENT, $content);
} }
public function getContentPath() public function getContentPath()
{ {
return 'posts' . DIRECTORY_SEPARATOR . $this->getName(); return 'posts' . DIRECTORY_SEPARATOR . $this->getName();
} }
public function getThumbnailSourceContentPath() public function getThumbnailSourceContentPath()
{ {
return 'posts' . DIRECTORY_SEPARATOR . $this->getName() . '-custom-thumb'; return 'posts' . DIRECTORY_SEPARATOR . $this->getName() . '-custom-thumb';
} }
public function getTagCount() public function getTagCount()
{ {
return $this->getMeta(self::META_TAG_COUNT, 0); return $this->getMeta(self::META_TAG_COUNT, 0);
} }
public function getFavoriteCount() public function getFavoriteCount()
{ {
return $this->getMeta(self::META_FAV_COUNT, 0); return $this->getMeta(self::META_FAV_COUNT, 0);
} }
public function getCommentCount() public function getCommentCount()
{ {
return $this->getMeta(self::META_COMMENT_COUNT, 0); return $this->getMeta(self::META_COMMENT_COUNT, 0);
} }
public function getScore() public function getScore()
{ {
return $this->getMeta(self::META_SCORE, 0); return $this->getMeta(self::META_SCORE, 0);
} }
} }

View file

@ -3,83 +3,83 @@ namespace Szurubooru\Entities;
final class PostNote extends Entity final class PostNote extends Entity
{ {
private $postId; private $postId;
private $left; private $left;
private $top; private $top;
private $width; private $width;
private $height; private $height;
private $text; private $text;
const LAZY_LOADER_POST = 'post'; const LAZY_LOADER_POST = 'post';
public function getPostId() public function getPostId()
{ {
return $this->postId; return $this->postId;
} }
public function setPostId($postId) public function setPostId($postId)
{ {
$this->postId = $postId; $this->postId = $postId;
} }
public function getLeft() public function getLeft()
{ {
return $this->left; return $this->left;
} }
public function setLeft($left) public function setLeft($left)
{ {
$this->left = $left; $this->left = $left;
} }
public function getTop() public function getTop()
{ {
return $this->top; return $this->top;
} }
public function setTop($top) public function setTop($top)
{ {
$this->top = $top; $this->top = $top;
} }
public function getWidth() public function getWidth()
{ {
return $this->width; return $this->width;
} }
public function setWidth($width) public function setWidth($width)
{ {
$this->width = $width; $this->width = $width;
} }
public function getHeight() public function getHeight()
{ {
return $this->height; return $this->height;
} }
public function setHeight($height) public function setHeight($height)
{ {
$this->height = $height; $this->height = $height;
} }
public function getText() public function getText()
{ {
return $this->text; return $this->text;
} }
public function setText($text) public function setText($text)
{ {
$this->text = $text; $this->text = $text;
} }
public function getPost() public function getPost()
{ {
return $this->lazyLoad(self::LAZY_LOADER_POST, null); return $this->lazyLoad(self::LAZY_LOADER_POST, null);
} }
public function setPost(Post $post) public function setPost(Post $post)
{ {
$this->lazySave(self::LAZY_LOADER_POST, $post); $this->lazySave(self::LAZY_LOADER_POST, $post);
$this->postId = $post->getId(); $this->postId = $post->getId();
} }
} }

View file

@ -3,59 +3,59 @@ namespace Szurubooru\Entities;
final class Score extends Entity final class Score extends Entity
{ {
private $postId; private $postId;
private $commentId; private $commentId;
private $score; private $score;
private $time; private $time;
private $userId; private $userId;
public function getUserId() public function getUserId()
{ {
return $this->userId; return $this->userId;
} }
public function setUserId($userId) public function setUserId($userId)
{ {
$this->userId = $userId; $this->userId = $userId;
} }
public function getPostId() public function getPostId()
{ {
return $this->postId; return $this->postId;
} }
public function setPostId($postId) public function setPostId($postId)
{ {
$this->postId = $postId; $this->postId = $postId;
} }
public function getCommentId() public function getCommentId()
{ {
return $this->commentId; return $this->commentId;
} }
public function setCommentId($commentId) public function setCommentId($commentId)
{ {
$this->commentId = $commentId; $this->commentId = $commentId;
} }
public function getTime() public function getTime()
{ {
return $this->time; return $this->time;
} }
public function setTime($time) public function setTime($time)
{ {
$this->time = $time; $this->time = $time;
} }
public function getScore() public function getScore()
{ {
return $this->score; return $this->score;
} }
public function setScore($score) public function setScore($score)
{ {
$this->score = $score; $this->score = $score;
} }
} }

View file

@ -4,101 +4,101 @@ use Szurubooru\Entities\User;
final class Snapshot extends Entity final class Snapshot extends Entity
{ {
const TYPE_POST = 0; const TYPE_POST = 0;
const TYPE_TAG = 1; const TYPE_TAG = 1;
const OPERATION_CREATION = 0; const OPERATION_CREATION = 0;
const OPERATION_CHANGE = 1; const OPERATION_CHANGE = 1;
const OPERATION_DELETE = 2; const OPERATION_DELETE = 2;
const LAZY_LOADER_USER = 'user'; const LAZY_LOADER_USER = 'user';
private $time; private $time;
private $type; private $type;
private $primaryKey; private $primaryKey;
private $operation; private $operation;
private $userId; private $userId;
private $data; private $data;
private $dataDifference; private $dataDifference;
public function getTime() public function getTime()
{ {
return $this->time; return $this->time;
} }
public function setTime($time) public function setTime($time)
{ {
$this->time = $time; $this->time = $time;
} }
public function getType() public function getType()
{ {
return $this->type; return $this->type;
} }
public function setType($type) public function setType($type)
{ {
$this->type = $type; $this->type = $type;
} }
public function getPrimaryKey() public function getPrimaryKey()
{ {
return $this->primaryKey; return $this->primaryKey;
} }
public function setPrimaryKey($primaryKey) public function setPrimaryKey($primaryKey)
{ {
$this->primaryKey = $primaryKey; $this->primaryKey = $primaryKey;
} }
public function getOperation() public function getOperation()
{ {
return $this->operation; return $this->operation;
} }
public function setOperation($operation) public function setOperation($operation)
{ {
$this->operation = $operation; $this->operation = $operation;
} }
public function getUserId() public function getUserId()
{ {
return $this->userId; return $this->userId;
} }
public function setUserId($userId) public function setUserId($userId)
{ {
$this->userId = $userId; $this->userId = $userId;
} }
public function getData() public function getData()
{ {
return $this->data; return $this->data;
} }
public function setData($data) public function setData($data)
{ {
$this->data = $data; $this->data = $data;
} }
public function getDataDifference() public function getDataDifference()
{ {
return $this->dataDifference; return $this->dataDifference;
} }
public function setDataDifference($dataDifference) public function setDataDifference($dataDifference)
{ {
$this->dataDifference = $dataDifference; $this->dataDifference = $dataDifference;
} }
public function getUser() public function getUser()
{ {
return $this->lazyLoad(self::LAZY_LOADER_USER, null); return $this->lazyLoad(self::LAZY_LOADER_USER, null);
} }
public function setUser(User $user = null) public function setUser(User $user = null)
{ {
$this->lazySave(self::LAZY_LOADER_USER, $user); $this->lazySave(self::LAZY_LOADER_USER, $user);
$this->userId = $user ? $user->getId() : null; $this->userId = $user ? $user->getId() : null;
} }
} }

View file

@ -3,78 +3,78 @@ namespace Szurubooru\Entities;
final class Tag extends Entity final class Tag extends Entity
{ {
private $name; private $name;
private $creationTime; private $creationTime;
private $banned = false; private $banned = false;
private $category = 'default'; private $category = 'default';
const LAZY_LOADER_IMPLIED_TAGS = 'implications'; const LAZY_LOADER_IMPLIED_TAGS = 'implications';
const LAZY_LOADER_SUGGESTED_TAGS = 'suggestions'; const LAZY_LOADER_SUGGESTED_TAGS = 'suggestions';
const META_USAGES = 'usages'; const META_USAGES = 'usages';
public function getName() public function getName()
{ {
return $this->name; return $this->name;
} }
public function setName($name) public function setName($name)
{ {
$this->name = $name; $this->name = $name;
} }
public function getCreationTime() public function getCreationTime()
{ {
return $this->creationTime; return $this->creationTime;
} }
public function setCreationTime($creationTime) public function setCreationTime($creationTime)
{ {
$this->creationTime = $creationTime; $this->creationTime = $creationTime;
} }
public function isBanned() public function isBanned()
{ {
return $this->banned; return $this->banned;
} }
public function setBanned($banned) public function setBanned($banned)
{ {
$this->banned = boolval($banned); $this->banned = boolval($banned);
} }
public function getCategory() public function getCategory()
{ {
return $this->category; return $this->category;
} }
public function setCategory($category) public function setCategory($category)
{ {
$this->category = $category; $this->category = $category;
} }
public function getUsages() public function getUsages()
{ {
return $this->getMeta(self::META_USAGES); return $this->getMeta(self::META_USAGES);
} }
public function getImpliedTags() public function getImpliedTags()
{ {
return $this->lazyLoad(self::LAZY_LOADER_IMPLIED_TAGS, []); return $this->lazyLoad(self::LAZY_LOADER_IMPLIED_TAGS, []);
} }
public function setImpliedTags(array $impliedTags) public function setImpliedTags(array $impliedTags)
{ {
$this->lazySave(self::LAZY_LOADER_IMPLIED_TAGS, $impliedTags); $this->lazySave(self::LAZY_LOADER_IMPLIED_TAGS, $impliedTags);
} }
public function getSuggestedTags() public function getSuggestedTags()
{ {
return $this->lazyLoad(self::LAZY_LOADER_SUGGESTED_TAGS, []); return $this->lazyLoad(self::LAZY_LOADER_SUGGESTED_TAGS, []);
} }
public function setSuggestedTags(array $suggestedTags) public function setSuggestedTags(array $suggestedTags)
{ {
$this->lazySave(self::LAZY_LOADER_SUGGESTED_TAGS, $suggestedTags); $this->lazySave(self::LAZY_LOADER_SUGGESTED_TAGS, $suggestedTags);
} }
} }

View file

@ -3,41 +3,41 @@ namespace Szurubooru\Entities;
final class Token extends Entity final class Token extends Entity
{ {
const PURPOSE_LOGIN = 1; const PURPOSE_LOGIN = 1;
const PURPOSE_ACTIVATE = 2; const PURPOSE_ACTIVATE = 2;
const PURPOSE_PASSWORD_RESET = 3; const PURPOSE_PASSWORD_RESET = 3;
private $name; private $name;
private $purpose; private $purpose;
private $additionalData; private $additionalData;
public function getName() public function getName()
{ {
return $this->name; return $this->name;
} }
public function setName($name) public function setName($name)
{ {
$this->name = $name; $this->name = $name;
} }
public function getPurpose() public function getPurpose()
{ {
return $this->purpose; return $this->purpose;
} }
public function setPurpose($purpose) public function setPurpose($purpose)
{ {
$this->purpose = intval($purpose); $this->purpose = intval($purpose);
} }
public function getAdditionalData() public function getAdditionalData()
{ {
return $this->additionalData; return $this->additionalData;
} }
public function setAdditionalData($additionalData) public function setAdditionalData($additionalData)
{ {
$this->additionalData = $additionalData; $this->additionalData = $additionalData;
} }
} }

View file

@ -3,165 +3,165 @@ namespace Szurubooru\Entities;
final class User extends Entity final class User extends Entity
{ {
const ACCESS_RANK_NOBODY = 0; const ACCESS_RANK_NOBODY = 0;
const ACCESS_RANK_ANONYMOUS = 1; const ACCESS_RANK_ANONYMOUS = 1;
const ACCESS_RANK_RESTRICTED_USER = 2; const ACCESS_RANK_RESTRICTED_USER = 2;
const ACCESS_RANK_REGULAR_USER = 3; const ACCESS_RANK_REGULAR_USER = 3;
const ACCESS_RANK_POWER_USER = 4; const ACCESS_RANK_POWER_USER = 4;
const ACCESS_RANK_MODERATOR = 5; const ACCESS_RANK_MODERATOR = 5;
const ACCESS_RANK_ADMINISTRATOR = 6; const ACCESS_RANK_ADMINISTRATOR = 6;
const AVATAR_STYLE_GRAVATAR = 1; const AVATAR_STYLE_GRAVATAR = 1;
const AVATAR_STYLE_MANUAL = 2; const AVATAR_STYLE_MANUAL = 2;
const AVATAR_STYLE_BLANK = 3; const AVATAR_STYLE_BLANK = 3;
const LAZY_LOADER_CUSTOM_AVATAR_SOURCE_CONTENT = 'customAvatarContent'; const LAZY_LOADER_CUSTOM_AVATAR_SOURCE_CONTENT = 'customAvatarContent';
private $name; private $name;
private $email; private $email;
private $emailUnconfirmed; private $emailUnconfirmed;
private $passwordHash; private $passwordHash;
private $passwordSalt; private $passwordSalt;
private $accessRank; private $accessRank;
private $registrationTime; private $registrationTime;
private $lastLoginTime; private $lastLoginTime;
private $avatarStyle; private $avatarStyle;
private $browsingSettings; private $browsingSettings;
private $accountConfirmed = false; private $accountConfirmed = false;
private $banned = false; private $banned = false;
public function getName() public function getName()
{ {
return $this->name; return $this->name;
} }
public function setName($name) public function setName($name)
{ {
$this->name = $name; $this->name = $name;
} }
public function getEmail() public function getEmail()
{ {
return $this->email; return $this->email;
} }
public function setEmail($email) public function setEmail($email)
{ {
$this->email = $email; $this->email = $email;
} }
public function getEmailUnconfirmed() public function getEmailUnconfirmed()
{ {
return $this->emailUnconfirmed; return $this->emailUnconfirmed;
} }
public function setEmailUnconfirmed($emailUnconfirmed) public function setEmailUnconfirmed($emailUnconfirmed)
{ {
$this->emailUnconfirmed = $emailUnconfirmed; $this->emailUnconfirmed = $emailUnconfirmed;
} }
public function isBanned() public function isBanned()
{ {
return $this->banned; return $this->banned;
} }
public function setBanned($banned) public function setBanned($banned)
{ {
$this->banned = boolval($banned); $this->banned = boolval($banned);
} }
public function isAccountConfirmed() public function isAccountConfirmed()
{ {
return $this->accountConfirmed; return $this->accountConfirmed;
} }
public function setAccountConfirmed($accountConfirmed) public function setAccountConfirmed($accountConfirmed)
{ {
$this->accountConfirmed = boolval($accountConfirmed); $this->accountConfirmed = boolval($accountConfirmed);
} }
public function getPasswordHash() public function getPasswordHash()
{ {
return $this->passwordHash; return $this->passwordHash;
} }
public function setPasswordHash($passwordHash) public function setPasswordHash($passwordHash)
{ {
$this->passwordHash = $passwordHash; $this->passwordHash = $passwordHash;
} }
public function getPasswordSalt() public function getPasswordSalt()
{ {
return $this->passwordSalt; return $this->passwordSalt;
} }
public function setPasswordSalt($passwordSalt) public function setPasswordSalt($passwordSalt)
{ {
$this->passwordSalt = $passwordSalt; $this->passwordSalt = $passwordSalt;
} }
public function getAccessRank() public function getAccessRank()
{ {
return $this->accessRank; return $this->accessRank;
} }
public function setAccessRank($accessRank) public function setAccessRank($accessRank)
{ {
$this->accessRank = $accessRank; $this->accessRank = $accessRank;
} }
public function getRegistrationTime() public function getRegistrationTime()
{ {
return $this->registrationTime; return $this->registrationTime;
} }
public function setRegistrationTime($registrationTime) public function setRegistrationTime($registrationTime)
{ {
$this->registrationTime = $registrationTime; $this->registrationTime = $registrationTime;
} }
public function getLastLoginTime() public function getLastLoginTime()
{ {
return $this->lastLoginTime; return $this->lastLoginTime;
} }
public function setLastLoginTime($lastLoginTime) public function setLastLoginTime($lastLoginTime)
{ {
$this->lastLoginTime = $lastLoginTime; $this->lastLoginTime = $lastLoginTime;
} }
public function getAvatarStyle() public function getAvatarStyle()
{ {
return $this->avatarStyle; return $this->avatarStyle;
} }
public function setAvatarStyle($avatarStyle) public function setAvatarStyle($avatarStyle)
{ {
$this->avatarStyle = $avatarStyle; $this->avatarStyle = $avatarStyle;
} }
public function getBrowsingSettings() public function getBrowsingSettings()
{ {
return $this->browsingSettings; return $this->browsingSettings;
} }
public function setBrowsingSettings($browsingSettings) public function setBrowsingSettings($browsingSettings)
{ {
$this->browsingSettings = $browsingSettings; $this->browsingSettings = $browsingSettings;
} }
public function getCustomAvatarSourceContent() public function getCustomAvatarSourceContent()
{ {
return $this->lazyLoad(self::LAZY_LOADER_CUSTOM_AVATAR_SOURCE_CONTENT, null); return $this->lazyLoad(self::LAZY_LOADER_CUSTOM_AVATAR_SOURCE_CONTENT, null);
} }
public function setCustomAvatarSourceContent($content) public function setCustomAvatarSourceContent($content)
{ {
$this->lazySave(self::LAZY_LOADER_CUSTOM_AVATAR_SOURCE_CONTENT, $content); $this->lazySave(self::LAZY_LOADER_CUSTOM_AVATAR_SOURCE_CONTENT, $content);
} }
public function getCustomAvatarSourceContentPath() public function getCustomAvatarSourceContentPath()
{ {
return 'avatars' . DIRECTORY_SEPARATOR . $this->getId(); return 'avatars' . DIRECTORY_SEPARATOR . $this->getId();
} }
} }

View file

@ -5,19 +5,19 @@ use Szurubooru\Validator;
class LoginFormData implements IValidatable class LoginFormData implements IValidatable
{ {
public $userNameOrEmail; public $userNameOrEmail;
public $password; public $password;
public function __construct($inputReader = null) public function __construct($inputReader = null)
{ {
if ($inputReader !== null) if ($inputReader !== null)
{ {
$this->userNameOrEmail = trim($inputReader->userNameOrEmail); $this->userNameOrEmail = trim($inputReader->userNameOrEmail);
$this->password = $inputReader->password; $this->password = $inputReader->password;
} }
} }
public function validate(Validator $validator) public function validate(Validator $validator)
{ {
} }
} }

View file

@ -6,46 +6,46 @@ use Szurubooru\Validator;
class PostEditFormData implements IValidatable class PostEditFormData implements IValidatable
{ {
public $content; public $content;
public $thumbnail; public $thumbnail;
public $safety; public $safety;
public $source; public $source;
public $tags; public $tags;
public $relations; public $relations;
public $flags; public $flags;
public $seenEditTime; public $seenEditTime;
public function __construct($inputReader = null) public function __construct($inputReader = null)
{ {
if ($inputReader !== null) if ($inputReader !== null)
{ {
$this->content = $inputReader->readFile('content'); $this->content = $inputReader->readFile('content');
$this->thumbnail = $inputReader->readFile('thumbnail'); $this->thumbnail = $inputReader->readFile('thumbnail');
if ($inputReader->safety) if ($inputReader->safety)
$this->safety = EnumHelper::postSafetyFromString($inputReader->safety); $this->safety = EnumHelper::postSafetyFromString($inputReader->safety);
if ($inputReader->source !== null) if ($inputReader->source !== null)
$this->source = $inputReader->source; $this->source = $inputReader->source;
$this->tags = preg_split('/[\s+]/', $inputReader->tags); $this->tags = preg_split('/[\s+]/', $inputReader->tags);
if ($inputReader->relations !== null) if ($inputReader->relations !== null)
$this->relations = array_filter(preg_split('/[\s+]/', $inputReader->relations)); $this->relations = array_filter(preg_split('/[\s+]/', $inputReader->relations));
$this->seenEditTime = $inputReader->seenEditTime; $this->seenEditTime = $inputReader->seenEditTime;
$this->flags = new \StdClass; $this->flags = new \StdClass;
$this->flags->loop = !empty($inputReader->loop); $this->flags->loop = !empty($inputReader->loop);
} }
} }
public function validate(Validator $validator) public function validate(Validator $validator)
{ {
$validator->validatePostTags($this->tags); $validator->validatePostTags($this->tags);
if ($this->source !== null) if ($this->source !== null)
$validator->validatePostSource($this->source); $validator->validatePostSource($this->source);
if ($this->relations) if ($this->relations)
{ {
foreach ($this->relations as $relatedPostId) foreach ($this->relations as $relatedPostId)
$validator->validateNumber($relatedPostId); $validator->validateNumber($relatedPostId);
} }
} }
} }

View file

@ -5,26 +5,26 @@ use Szurubooru\Validator;
class PostNoteFormData implements IValidatable class PostNoteFormData implements IValidatable
{ {
public $left; public $left;
public $top; public $top;
public $width; public $width;
public $height; public $height;
public $text; public $text;
public function __construct($inputReader = null) public function __construct($inputReader = null)
{ {
if ($inputReader !== null) if ($inputReader !== null)
{ {
$this->left = floatval($inputReader->left); $this->left = floatval($inputReader->left);
$this->top = floatval($inputReader->top); $this->top = floatval($inputReader->top);
$this->width = floatval($inputReader->width); $this->width = floatval($inputReader->width);
$this->height = floatval($inputReader->height); $this->height = floatval($inputReader->height);
$this->text = trim($inputReader->text); $this->text = trim($inputReader->text);
} }
} }
public function validate(Validator $validator) public function validate(Validator $validator)
{ {
$validator->validateMinLength($this->text, 3, 'Post note content'); $validator->validateMinLength($this->text, 3, 'Post note content');
} }
} }

View file

@ -5,24 +5,24 @@ use Szurubooru\Validator;
class RegistrationFormData implements IValidatable class RegistrationFormData implements IValidatable
{ {
public $userName; public $userName;
public $password; public $password;
public $email; public $email;
public function __construct($inputReader = null) public function __construct($inputReader = null)
{ {
if ($inputReader !== null) if ($inputReader !== null)
{ {
$this->userName = trim($inputReader->userName); $this->userName = trim($inputReader->userName);
$this->password = $inputReader->password; $this->password = $inputReader->password;
$this->email = trim($inputReader->email); $this->email = trim($inputReader->email);
} }
} }
public function validate(Validator $validator) public function validate(Validator $validator)
{ {
$validator->validateUserName($this->userName); $validator->validateUserName($this->userName);
$validator->validatePassword($this->password); $validator->validatePassword($this->password);
$validator->validateEmail($this->email); $validator->validateEmail($this->email);
} }
} }

View file

@ -5,40 +5,40 @@ use Szurubooru\Validator;
class TagEditFormData implements IValidatable class TagEditFormData implements IValidatable
{ {
public $name; public $name;
public $banned; public $banned;
public $category; public $category;
public $implications; public $implications;
public $suggestions; public $suggestions;
public function __construct($inputReader = null) public function __construct($inputReader = null)
{ {
if ($inputReader !== null) if ($inputReader !== null)
{ {
$this->name = trim($inputReader->name); $this->name = trim($inputReader->name);
$this->category = strtolower(trim($inputReader->category)); $this->category = strtolower(trim($inputReader->category));
if ($inputReader->banned !== null) if ($inputReader->banned !== null)
$this->banned = boolval($inputReader->banned); $this->banned = boolval($inputReader->banned);
$this->implications = array_filter(array_unique(preg_split('/[\s+]/', $inputReader->implications))); $this->implications = array_filter(array_unique(preg_split('/[\s+]/', $inputReader->implications)));
$this->suggestions = array_filter(array_unique(preg_split('/[\s+]/', $inputReader->suggestions))); $this->suggestions = array_filter(array_unique(preg_split('/[\s+]/', $inputReader->suggestions)));
} }
} }
public function validate(Validator $validator) public function validate(Validator $validator)
{ {
if ($this->category !== null) if ($this->category !== null)
$validator->validateLength($this->category, 1, 25, 'Tag category'); $validator->validateLength($this->category, 1, 25, 'Tag category');
if ($this->name !== null) if ($this->name !== null)
$validator->validatePostTags([$this->name]); $validator->validatePostTags([$this->name]);
if (!empty($this->implications)) if (!empty($this->implications))
$validator->validatePostTags($this->implications); $validator->validatePostTags($this->implications);
if (!empty($this->suggestions)) if (!empty($this->suggestions))
$validator->validatePostTags($this->suggestions); $validator->validatePostTags($this->suggestions);
} }
} }

View file

@ -6,37 +6,37 @@ use Szurubooru\Validator;
class UploadFormData implements IValidatable class UploadFormData implements IValidatable
{ {
public $contentFileName; public $contentFileName;
public $content; public $content;
public $url; public $url;
public $anonymous; public $anonymous;
public $safety; public $safety;
public $source; public $source;
public $tags; public $tags;
public function __construct($inputReader = null) public function __construct($inputReader = null)
{ {
if ($inputReader !== null) if ($inputReader !== null)
{ {
$this->contentFileName = $inputReader->contentFileName; $this->contentFileName = $inputReader->contentFileName;
$this->content = $inputReader->readFile('content'); $this->content = $inputReader->readFile('content');
$this->url = $inputReader->url; $this->url = $inputReader->url;
$this->anonymous = $inputReader->anonymous; $this->anonymous = $inputReader->anonymous;
$this->safety = EnumHelper::postSafetyFromString($inputReader->safety); $this->safety = EnumHelper::postSafetyFromString($inputReader->safety);
$this->source = $inputReader->source; $this->source = $inputReader->source;
$this->tags = preg_split('/[\s+]/', $inputReader->tags); $this->tags = preg_split('/[\s+]/', $inputReader->tags);
} }
} }
public function validate(Validator $validator) public function validate(Validator $validator)
{ {
if ($this->content === null && $this->url === null) if ($this->content === null && $this->url === null)
throw new \DomainException('Neither data or URL provided.'); throw new \DomainException('Neither data or URL provided.');
$validator->validatePostTags($this->tags); $validator->validatePostTags($this->tags);
if ($this->source !== null) if ($this->source !== null)
$validator->validatePostSource($this->source); $validator->validatePostSource($this->source);
} }
} }

View file

@ -7,60 +7,60 @@ use Szurubooru\Validator;
class UserEditFormData implements IValidatable class UserEditFormData implements IValidatable
{ {
public $userName; public $userName;
public $email; public $email;
public $password; public $password;
public $accessRank; public $accessRank;
public $avatarStyle; public $avatarStyle;
public $avatarContent; public $avatarContent;
public $browsingSettings; public $browsingSettings;
public $banned; public $banned;
public function __construct($inputReader = null) public function __construct($inputReader = null)
{ {
if ($inputReader !== null) if ($inputReader !== null)
{ {
$this->userName = $inputReader->userName; $this->userName = $inputReader->userName;
$this->email = $inputReader->email; $this->email = $inputReader->email;
$this->password = $inputReader->password; $this->password = $inputReader->password;
if ($inputReader->accessRank !== null) if ($inputReader->accessRank !== null)
$this->accessRank = EnumHelper::accessRankFromString($inputReader->accessRank); $this->accessRank = EnumHelper::accessRankFromString($inputReader->accessRank);
if ($inputReader->avatarStyle !== null) if ($inputReader->avatarStyle !== null)
$this->avatarStyle = EnumHelper::avatarStyleFromString($inputReader->avatarStyle); $this->avatarStyle = EnumHelper::avatarStyleFromString($inputReader->avatarStyle);
$this->avatarContent = $inputReader->readFile('avatarContent'); $this->avatarContent = $inputReader->readFile('avatarContent');
$this->browsingSettings = json_decode($inputReader->browsingSettings); $this->browsingSettings = json_decode($inputReader->browsingSettings);
if ($inputReader->banned !== null) if ($inputReader->banned !== null)
$this->banned = boolval($inputReader->banned); $this->banned = boolval($inputReader->banned);
} }
} }
public function validate(Validator $validator) public function validate(Validator $validator)
{ {
if ($this->userName !== null) if ($this->userName !== null)
$validator->validateUserName($this->userName); $validator->validateUserName($this->userName);
if ($this->password !== null) if ($this->password !== null)
$validator->validatePassword($this->password); $validator->validatePassword($this->password);
if ($this->email !== null) if ($this->email !== null)
$validator->validateEmail($this->email); $validator->validateEmail($this->email);
if (strlen($this->avatarContent) > 1024 * 512) if (strlen($this->avatarContent) > 1024 * 512)
throw new \DomainException('Avatar content must have at most 512 kilobytes.'); throw new \DomainException('Avatar content must have at most 512 kilobytes.');
if ($this->avatarContent) if ($this->avatarContent)
{ {
$avatarContentMimeType = MimeHelper::getMimeTypeFromBuffer($this->avatarContent); $avatarContentMimeType = MimeHelper::getMimeTypeFromBuffer($this->avatarContent);
if (!MimeHelper::isImage($avatarContentMimeType)) if (!MimeHelper::isImage($avatarContentMimeType))
throw new \DomainException('Avatar must be an image (detected: ' . $avatarContentMimeType . ').'); throw new \DomainException('Avatar must be an image (detected: ' . $avatarContentMimeType . ').');
} }
if ($this->browsingSettings !== null) if ($this->browsingSettings !== null)
{ {
if (!is_object($this->browsingSettings)) if (!is_object($this->browsingSettings))
throw new \InvalidArgumentException('Browsing settings must be valid JSON.'); throw new \InvalidArgumentException('Browsing settings must be valid JSON.');
else if (strlen(json_encode($this->browsingSettings)) > 300) else if (strlen(json_encode($this->browsingSettings)) > 300)
throw new \InvalidArgumentException('Stringified browsing settings can have at most 300 characters.'); throw new \InvalidArgumentException('Stringified browsing settings can have at most 300 characters.');
} }
} }
} }

View file

@ -6,111 +6,111 @@ use Szurubooru\Entities\User;
class EnumHelper class EnumHelper
{ {
private static $accessRankMap = private static $accessRankMap =
[ [
'anonymous' => User::ACCESS_RANK_ANONYMOUS, 'anonymous' => User::ACCESS_RANK_ANONYMOUS,
'restrictedUser' => User::ACCESS_RANK_RESTRICTED_USER, 'restrictedUser' => User::ACCESS_RANK_RESTRICTED_USER,
'regularUser' => User::ACCESS_RANK_REGULAR_USER, 'regularUser' => User::ACCESS_RANK_REGULAR_USER,
'powerUser' => User::ACCESS_RANK_POWER_USER, 'powerUser' => User::ACCESS_RANK_POWER_USER,
'moderator' => User::ACCESS_RANK_MODERATOR, 'moderator' => User::ACCESS_RANK_MODERATOR,
'administrator' => User::ACCESS_RANK_ADMINISTRATOR, 'administrator' => User::ACCESS_RANK_ADMINISTRATOR,
]; ];
private static $avatarStyleMap = private static $avatarStyleMap =
[ [
'gravatar' => User::AVATAR_STYLE_GRAVATAR, 'gravatar' => User::AVATAR_STYLE_GRAVATAR,
'manual' => User::AVATAR_STYLE_MANUAL, 'manual' => User::AVATAR_STYLE_MANUAL,
'none' => User::AVATAR_STYLE_BLANK, 'none' => User::AVATAR_STYLE_BLANK,
'blank' => User::AVATAR_STYLE_BLANK, 'blank' => User::AVATAR_STYLE_BLANK,
]; ];
private static $postSafetyMap = private static $postSafetyMap =
[ [
'safe' => Post::POST_SAFETY_SAFE, 'safe' => Post::POST_SAFETY_SAFE,
'sketchy' => Post::POST_SAFETY_SKETCHY, 'sketchy' => Post::POST_SAFETY_SKETCHY,
'unsafe' => Post::POST_SAFETY_UNSAFE, 'unsafe' => Post::POST_SAFETY_UNSAFE,
]; ];
private static $postTypeMap = private static $postTypeMap =
[ [
'image' => Post::POST_TYPE_IMAGE, 'image' => Post::POST_TYPE_IMAGE,
'video' => Post::POST_TYPE_VIDEO, 'video' => Post::POST_TYPE_VIDEO,
'flash' => Post::POST_TYPE_FLASH, 'flash' => Post::POST_TYPE_FLASH,
'youtube' => Post::POST_TYPE_YOUTUBE, 'youtube' => Post::POST_TYPE_YOUTUBE,
'animation' => Post::POST_TYPE_ANIMATED_IMAGE, 'animation' => Post::POST_TYPE_ANIMATED_IMAGE,
]; ];
private static $snapshotTypeMap = private static $snapshotTypeMap =
[ [
'post' => Snapshot::TYPE_POST, 'post' => Snapshot::TYPE_POST,
]; ];
public static function accessRankToString($accessRank) public static function accessRankToString($accessRank)
{ {
return self::enumToString(self::$accessRankMap, $accessRank); return self::enumToString(self::$accessRankMap, $accessRank);
} }
public static function accessRankFromString($accessRankString) public static function accessRankFromString($accessRankString)
{ {
return self::stringToEnum(self::$accessRankMap, $accessRankString); return self::stringToEnum(self::$accessRankMap, $accessRankString);
} }
public static function avatarStyleToString($avatarStyle) public static function avatarStyleToString($avatarStyle)
{ {
return self::enumToString(self::$avatarStyleMap, $avatarStyle); return self::enumToString(self::$avatarStyleMap, $avatarStyle);
} }
public static function avatarStyleFromString($avatarStyleString) public static function avatarStyleFromString($avatarStyleString)
{ {
return self::stringToEnum(self::$avatarStyleMap, $avatarStyleString); return self::stringToEnum(self::$avatarStyleMap, $avatarStyleString);
} }
public static function postSafetyToString($postSafety) public static function postSafetyToString($postSafety)
{ {
return self::enumToString(self::$postSafetyMap, $postSafety); return self::enumToString(self::$postSafetyMap, $postSafety);
} }
public static function postSafetyFromString($postSafetyString) public static function postSafetyFromString($postSafetyString)
{ {
return self::stringToEnum(self::$postSafetyMap, $postSafetyString); return self::stringToEnum(self::$postSafetyMap, $postSafetyString);
} }
public static function postTypeToString($postType) public static function postTypeToString($postType)
{ {
return self::enumToString(self::$postTypeMap, $postType); return self::enumToString(self::$postTypeMap, $postType);
} }
public static function postTypeFromString($postTypeString) public static function postTypeFromString($postTypeString)
{ {
return self::stringToEnum(self::$postTypeMap, $postTypeString); return self::stringToEnum(self::$postTypeMap, $postTypeString);
} }
public static function snapshotTypeFromString($snapshotTypeString) public static function snapshotTypeFromString($snapshotTypeString)
{ {
return self::stringToEnum(self::$snapshotTypeMap, $snapshotTypeString); return self::stringToEnum(self::$snapshotTypeMap, $snapshotTypeString);
} }
private static function enumToString($enumMap, $enumValue) private static function enumToString($enumMap, $enumValue)
{ {
$reverseMap = array_flip($enumMap); $reverseMap = array_flip($enumMap);
if (!isset($reverseMap[$enumValue])) if (!isset($reverseMap[$enumValue]))
throw new \RuntimeException('Invalid value!'); throw new \RuntimeException('Invalid value!');
return $reverseMap[$enumValue]; return $reverseMap[$enumValue];
} }
private static function stringToEnum($enumMap, $enumString) private static function stringToEnum($enumMap, $enumString)
{ {
$key = trim(strtolower($enumString)); $key = trim(strtolower($enumString));
$lowerEnumMap = array_change_key_case($enumMap, \CASE_LOWER); $lowerEnumMap = array_change_key_case($enumMap, \CASE_LOWER);
if (!isset($lowerEnumMap[$key])) if (!isset($lowerEnumMap[$key]))
{ {
throw new \DomainException(sprintf( throw new \DomainException(sprintf(
'Unrecognized value: %s.' . PHP_EOL . 'Possible values: %s', 'Unrecognized value: %s.' . PHP_EOL . 'Possible values: %s',
$enumString, $enumString,
join(', ', array_keys($lowerEnumMap)))); join(', ', array_keys($lowerEnumMap))));
} }
return $lowerEnumMap[$key]; return $lowerEnumMap[$key];
} }
} }

View file

@ -3,89 +3,89 @@ namespace Szurubooru\Helpers;
class HttpHelper class HttpHelper
{ {
private $redirected = false; private $redirected = false;
public function setResponseCode($code) public function setResponseCode($code)
{ {
http_response_code($code); http_response_code($code);
} }
public function setHeader($key, $value) public function setHeader($key, $value)
{ {
header($key . ': ' . $value); header($key . ': ' . $value);
} }
public function outputJSON($data) public function outputJSON($data)
{ {
$encodedJson = json_encode((array) $data); $encodedJson = json_encode((array) $data);
$lastError = json_last_error(); $lastError = json_last_error();
if ($lastError !== JSON_ERROR_NONE) if ($lastError !== JSON_ERROR_NONE)
$this->output('Fatal error while encoding JSON: ' . $lastError . PHP_EOL . PHP_EOL . print_r($data, true)); $this->output('Fatal error while encoding JSON: ' . $lastError . PHP_EOL . PHP_EOL . print_r($data, true));
else else
$this->output($encodedJson); $this->output($encodedJson);
} }
public function output($data) public function output($data)
{ {
echo $data; echo $data;
} }
public function getRequestHeaders() public function getRequestHeaders()
{ {
if (function_exists('getallheaders')) if (function_exists('getallheaders'))
{ {
return getallheaders(); return getallheaders();
} }
$result = []; $result = [];
foreach ($_SERVER as $key => $value) foreach ($_SERVER as $key => $value)
{ {
if (substr($key, 0, 5) == "HTTP_") if (substr($key, 0, 5) == "HTTP_")
{ {
$key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5))))); $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5)))));
$result[$key] = $value; $result[$key] = $value;
} }
else else
{ {
$result[$key] = $value; $result[$key] = $value;
} }
} }
return $result; return $result;
} }
public function getRequestHeader($key) public function getRequestHeader($key)
{ {
$headers = $this->getRequestHeaders(); $headers = $this->getRequestHeaders();
return isset($headers[$key]) ? $headers[$key] : null; return isset($headers[$key]) ? $headers[$key] : null;
} }
public function getRequestMethod() public function getRequestMethod()
{ {
return $_SERVER['REQUEST_METHOD']; return $_SERVER['REQUEST_METHOD'];
} }
public function getRequestUri() public function getRequestUri()
{ {
$requestUri = $_SERVER['REQUEST_URI']; $requestUri = $_SERVER['REQUEST_URI'];
$requestUri = preg_replace('/\?.*$/', '', $requestUri); $requestUri = preg_replace('/\?.*$/', '', $requestUri);
return $requestUri; return $requestUri;
} }
public function redirect($destination) public function redirect($destination)
{ {
$this->setResponseCode(307); $this->setResponseCode(307);
$this->setHeader('Location', $destination); $this->setHeader('Location', $destination);
$this->redirected = true; $this->redirected = true;
} }
public function nonCachedRedirect($destination) public function nonCachedRedirect($destination)
{ {
$this->setResponseCode(303); $this->setResponseCode(303);
$this->setHeader('Location', $destination); $this->setHeader('Location', $destination);
$this->redirected = true; $this->redirected = true;
} }
public function isRedirecting() public function isRedirecting()
{ {
return $this->redirected; return $this->redirected;
} }
} }

View file

@ -3,36 +3,36 @@ namespace Szurubooru\Helpers;
final class InputReader extends \ArrayObject final class InputReader extends \ArrayObject
{ {
public function __construct() public function __construct()
{ {
parent::setFlags(parent::ARRAY_AS_PROPS | parent::STD_PROP_LIST); parent::setFlags(parent::ARRAY_AS_PROPS | parent::STD_PROP_LIST);
$_PUT = []; $_PUT = [];
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT')
parse_str(file_get_contents('php://input'), $_PUT); parse_str(file_get_contents('php://input'), $_PUT);
foreach ([$_GET, $_POST, $_PUT] as $source) foreach ([$_GET, $_POST, $_PUT] as $source)
{ {
foreach ($source as $key => $value) foreach ($source as $key => $value)
$this->offsetSet($key, $value); $this->offsetSet($key, $value);
} }
} }
public function offsetGet($index) public function offsetGet($index)
{ {
if (!parent::offsetExists($index)) if (!parent::offsetExists($index))
return null; return null;
return parent::offsetGet($index); return parent::offsetGet($index);
} }
public function readFile($fileName) public function readFile($fileName)
{ {
if (!isset($_FILES[$fileName])) if (!isset($_FILES[$fileName]))
return null; return null;
if (!$_FILES[$fileName]['tmp_name']) if (!$_FILES[$fileName]['tmp_name'])
throw new \Exception('File is probably too big.'); throw new \Exception('File is probably too big.');
return file_get_contents($_FILES[$fileName]['tmp_name']); return file_get_contents($_FILES[$fileName]['tmp_name']);
} }
} }

Some files were not shown because too many files have changed in this diff Show more