diff --git a/TODO b/TODO index ff06bd99..8eef49ec 100644 --- a/TODO +++ b/TODO @@ -8,11 +8,7 @@ everything related to posts: - fav - score (see notes about scoring) - editing - - tags - - source - - safety - - content - - thumb + - concurrency - relations - ability to loop video posts - post edit history (think diff --git a/data/config.ini b/data/config.ini index d1551c18..cd8e89a7 100644 --- a/data/config.ini +++ b/data/config.ini @@ -13,6 +13,7 @@ activationBodyPath = mail/activation.txt [database] dsn = sqlite:db.sqlite maxPostSize = 10485760 ;10mb +maxCustomThumbnailSize = 1048576 ;1mb [security] secret = change @@ -43,6 +44,11 @@ uploadPosts = regularUser, powerUser, moderator, administrator uploadPostsAnonymously = regularUser, powerUser, moderator, administrator deletePosts = moderator, administrator featurePosts = moderator, administrator +changePostSafety = powerUser, moderator, administrator +changePostSource = regularUser, powerUser, moderator, administrator +changePostTags = regularUser, powerUser, moderator, administrator +changePostContent = regularUser, powerUser, moderator, administrator +changePostThumbnail = regularUser, powerUser, moderator, administrator listTags = anonymous, regularUser, powerUser, moderator, administrator diff --git a/public_html/css/post.css b/public_html/css/post.css index 52641281..a0ffe758 100644 --- a/public_html/css/post.css +++ b/public_html/css/post.css @@ -113,3 +113,14 @@ text-align: center; font-size: 12px; } + +#post-view-wrapper .post-edit-wrapper { + padding: 1em; + position: absolute; + background: rgba(255, 255, 255, 0.8); + display: none; +} + +#post-view-wrapper .post-edit-wrapper .file-handler { + margin: 0.5em 0; +} diff --git a/public_html/js/Auth.js b/public_html/js/Auth.js index bc0ca76b..14e52219 100644 --- a/public_html/js/Auth.js +++ b/public_html/js/Auth.js @@ -25,6 +25,11 @@ App.Auth = function(_, jQuery, util, api, appState, promise) { uploadPostsAnonymously: 'uploadPostsAnonymously', deletePosts: 'deletePosts', featurePosts: 'featurePosts', + changePostSafety: 'changePostSafety', + changePostSource: 'changePostSource', + changePostTags: 'changePostTags', + changePostContent: 'changePostContent', + changePostThumbnail: 'changePostThumbnail', listTags: 'listTags', }; diff --git a/public_html/js/Controls/TagInput.js b/public_html/js/Controls/TagInput.js index 0b5b133b..1d06598b 100644 --- a/public_html/js/Controls/TagInput.js +++ b/public_html/js/Controls/TagInput.js @@ -12,11 +12,13 @@ App.Controls.TagInput = function( var KEY_SPACE = 32; var KEY_BACKSPACE = 8; var tagConfirmKeys = [KEY_RETURN, KEY_SPACE]; + var inputConfirmKeys = [KEY_RETURN]; var tags = []; var options = { beforeTagAdded: null, beforeTagRemoved: null, + inputConfirmed: null, }; if ($underlyingInput.length !== 1) { @@ -40,6 +42,9 @@ App.Controls.TagInput = function( }); $input.attr('placeholder', $underlyingInput.attr('placeholder')); + pasteText($underlyingInput.val()); + $underlyingInput.val(''); + $input.unbind('focus').bind('focus', function(e) { $wrapper.addClass('focused'); }); @@ -58,16 +63,25 @@ App.Controls.TagInput = function( } else { pastedText = (e.originalEvent || e).clipboardData.getData('text/plain'); } - var patedTags = pastedText.split(/\s+/); - _.each(patedTags, function(tag) { - addTag(tag); - }); + pasteText(pastedText); }); + function pasteText(pastedText) { + var pastedTags = pastedText.split(/\s+/); + _.each(pastedTags, function(tag) { + addTag(tag); + }); + } + $input.unbind('keydown').bind('keydown', function(e) { - if (_.contains(tagConfirmKeys, e.which)) { + if (_.contains(inputConfirmKeys, e.which) && !$input.val()) { e.preventDefault(); + if (typeof(options.inputConfirmed) !== 'undefined') { + options.inputConfirmed(); + } + } else if (_.contains(tagConfirmKeys, e.which)) { var tag = $input.val(); + e.preventDefault(); addTag(tag); $input.val(''); } else if (e.which === KEY_BACKSPACE && jQuery(this).val().length === 0) { diff --git a/public_html/js/Presenters/PostPresenter.js b/public_html/js/Presenters/PostPresenter.js index 0efa840a..a9a4925a 100644 --- a/public_html/js/Presenters/PostPresenter.js +++ b/public_html/js/Presenters/PostPresenter.js @@ -9,16 +9,24 @@ App.Presenters.PostPresenter = function( api, auth, router, + keyboard, topNavigationPresenter, messagePresenter) { var $el = jQuery('#content'); var $messages = $el; var postTemplate; + var postEditTemplate; var postContentTemplate; var post; var postNameOrId; var privileges = {}; + var editPrivileges = {}; + var tagInput; + var postContentFileDropper; + var postThumbnailFileDropper; + var postContent; + var postThumbnail; function init(args, loaded) { postNameOrId = args.postNameOrId; @@ -26,16 +34,24 @@ App.Presenters.PostPresenter = function( privileges.canDeletePosts = auth.hasPrivilege(auth.privileges.deletePosts); privileges.canFeaturePosts = auth.hasPrivilege(auth.privileges.featurePosts); + editPrivileges.canChangeSafety = auth.hasPrivilege(auth.privileges.changePostSafety); + editPrivileges.canChangeSource = auth.hasPrivilege(auth.privileges.changePostSource); + editPrivileges.canChangeTags = auth.hasPrivilege(auth.privileges.changePostTags); + editPrivileges.canChangeContent = auth.hasPrivilege(auth.privileges.changePostContent); + editPrivileges.canChangeThumbnail = auth.hasPrivilege(auth.privileges.changePostThumbnail); promise.waitAll( util.promiseTemplate('post'), + util.promiseTemplate('post-edit'), util.promiseTemplate('post-content'), api.get('/posts/' + postNameOrId)) .then(function( postTemplateHtml, + postEditTemplateHtml, postContentTemplateHtml, response) { postTemplate = _.template(postTemplateHtml); + postEditTemplate = _.template(postEditTemplateHtml); postContentTemplate = _.template(postContentTemplateHtml); post = response.json; @@ -50,17 +66,44 @@ App.Presenters.PostPresenter = function( } function render() { - $el.html(postTemplate({ + $el.html(renderPostTemplate()); + $messages = $el.find('.messages'); + + tagInput = App.Controls.TagInput($el.find('form [name=tags]'), _, jQuery); + tagInput.inputConfirmed = editPost; + + postContentFileDropper = new App.Controls.FileDropper($el.find('form [name=content]'), _, jQuery); + postContentFileDropper.onChange = postContentChanged; + postContentFileDropper.setNames = true; + postThumbnailFileDropper = new App.Controls.FileDropper($el.find('form [name=thumbnail]'), _, jQuery); + postThumbnailFileDropper.onChange = postThumbnailChanged; + postThumbnailFileDropper.setNames = true; + + keyboard.keyup('e', function() { + editButtonClicked(null); + }); + + + $el.find('.post-edit-wrapper form').submit(editFormSubmitted); + $el.find('.delete').click(deleteButtonClicked); + $el.find('.feature').click(featureButtonClicked); + $el.find('.edit').click(editButtonClicked); + } + + function renderSidebar() { + $el.find('#sidebar').html(jQuery(renderPostTemplate()).find('#sidebar').html()); + } + + function renderPostTemplate() { + return postTemplate({ post: post, formatRelativeTime: util.formatRelativeTime, formatFileSize: util.formatFileSize, postContentTemplate: postContentTemplate, + postEditTemplate: postEditTemplate, privileges: privileges, - })); - $messages = $el.find('.messages'); - - $el.find('.delete').click(deleteButtonClicked); - $el.find('.feature').click(featureButtonClicked); + editPrivileges: editPrivileges, + }); } function deleteButtonClicked(e) { @@ -94,6 +137,89 @@ App.Presenters.PostPresenter = function( .fail(showGenericError); } + function editButtonClicked(e) { + if (e) { + e.preventDefault(); + } + messagePresenter.hideMessages($messages); + if ($el.find('.post-edit-wrapper').is(':visible')) { + hideEditForm(); + } else { + showEditForm(); + } + } + + function editFormSubmitted(e) { + e.preventDefault(); + editPost(); + } + + function showEditForm() { + $el.find('.post-edit-wrapper').slideDown('fast'); + util.enableExitConfirmation(); + tagInput.focus(); + } + + function hideEditForm() { + $el.find('.post-edit-wrapper').slideUp('fast'); + util.disableExitConfirmation(); + } + + function editPost() { + var $form = $el.find('form'); + var formData = {}; + + if (editPrivileges.canChangeContent && postContent) { + formData.content = postContent; + } + + if (editPrivileges.canChangeThumbnail && postThumbnail) { + formData.thumbnail = postThumbnail; + } + + if (editPrivileges.canChangeSource) { + formData.source = $form.find('[name=source]').val(); + } + + if (editPrivileges.canChangeSafety) { + formData.safety = $form.find('[name=safety]:checked').val(); + } + + if (editPrivileges.canChangeTags) { + formData.tags = tagInput.getTags().join(' '); + } + + if (post.tags.length === 0) { + showEditError('No tags set.'); + return; + } + + promise.wait(api.put('/posts/' + post.id, formData)) + .then(function(response) { + post = response.json; + hideEditForm(); + renderSidebar(); + }).fail(function(response) { + showEditError(response); + }); + } + + function postContentChanged(files) { + postContentFileDropper.readAsDataURL(files[0], function(content) { + postContent = content; + }); + } + + function postThumbnailChanged(files) { + postThumbnailFileDropper.readAsDataURL(files[0], function(content) { + postThumbnail = content; + }); + } + + function showEditError(response) { + window.alert(response.json && response.json.error || response); + } + function showGenericError(response) { messagePresenter.showError($messages, response.json && response.json.error || response); } @@ -105,4 +231,4 @@ App.Presenters.PostPresenter = function( }; -App.DI.register('postPresenter', ['_', 'jQuery', 'util', 'promise', 'api', 'auth', 'router', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.PostPresenter); +App.DI.register('postPresenter', ['_', 'jQuery', 'util', 'promise', 'api', 'auth', 'router', 'keyboard', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.PostPresenter); diff --git a/public_html/templates/post-edit.tpl b/public_html/templates/post-edit.tpl new file mode 100644 index 00000000..8fcfb365 --- /dev/null +++ b/public_html/templates/post-edit.tpl @@ -0,0 +1,66 @@ +
diff --git a/public_html/templates/post.tpl b/public_html/templates/post.tpl index be6b0bfb..6b8463a9 100644 --- a/public_html/templates/post.tpl +++ b/public_html/templates/post.tpl @@ -87,10 +87,18 @@ - <% if (_.any(privileges)) { %> + <% if (_.any(privileges) || _.any(editPrivileges)) { %>