diff --git a/TODO b/TODO index d9fdc0a4..85926b04 100644 --- a/TODO +++ b/TODO @@ -11,15 +11,7 @@ first major release. - tags: add tag edit snapshots (backed-only) - post notes - - "add note" in sidebar creates new note in the middle of image - - hovering notes shows note text - - dragging and resizing notes is always possible - - clicking note reveals modal popup with textarea to input text - - under textarea there are buttons for saving and/or deleting the note. - - post notes should have separate table and shouldn't be stored as json - column. - - post notes should be visible in post edit history. - (move post snapshot factory methods to PostService) + - post notes history refactors: - add enum validation in IValidatables (needs refactors of enums and diff --git a/public_html/css/post.css b/public_html/css/post.css index 7194a8d8..50e97026 100644 --- a/public_html/css/post.css +++ b/public_html/css/post.css @@ -174,6 +174,10 @@ .post-content { position: relative; } +.post-notes-target, +.post-notes { + pointer-events: none; +} .post-notes { position: absolute; left: 0; @@ -181,17 +185,35 @@ right: 0; bottom: 0; } + +.post-note-edit { + pointer-events: auto; + display: none; + position: absolute; + z-index: 2; + background: white; + border: 1px solid black; + padding: 1em; + left: 10%; + top: 10%; +} +.post-note-edit textarea { + width: 20em; + height: 5em; +} +.post-note-edit .actions { + margin-top: 0.5em; +} +.post-note-edit .actions button:not(:last-child) { + margin-right: 0.5em; +} + .post-note { + pointer-events: auto; position: absolute; background: rgba(255, 255, 255, 0.3); border: 1px solid rgba(0, 0, 0, 0.3); } -.post-note .dragger { - position: absolute; - width: 100%; - height: 100%; - cursor: move; -} .post-note .text-wrapper { position: absolute; display: none; @@ -199,6 +221,9 @@ left: -1px; padding-top: 0.5em; cursor: pointer; + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; } .post-note .text { padding: 0.5em; @@ -209,6 +234,19 @@ display: block; } +.post-note .text p:first-of-type { + margin-top: 0; +} +.post-note .text p:last-of-type { + margin-bottom: 0; +} + +.post-note .dragger { + position: absolute; + width: 100%; + height: 100%; + cursor: move; +} .post-note .resizer { position: absolute; cursor: nwse-resize; diff --git a/public_html/js/Presenters/PostContentPresenter.js b/public_html/js/Presenters/PostContentPresenter.js index a3978c17..6dc3413e 100644 --- a/public_html/js/Presenters/PostContentPresenter.js +++ b/public_html/js/Presenters/PostContentPresenter.js @@ -40,9 +40,14 @@ App.Presenters.PostContentPresenter = function( function() {}); } + function addNewPostNote() { + postNotesPresenter.addNewPostNote(); + } + return { init: init, render: render, + addNewPostNote: addNewPostNote, }; }; diff --git a/public_html/js/Presenters/PostNotesPresenter.js b/public_html/js/Presenters/PostNotesPresenter.js index 80bc2725..50ce00a9 100644 --- a/public_html/js/Presenters/PostNotesPresenter.js +++ b/public_html/js/Presenters/PostNotesPresenter.js @@ -4,18 +4,25 @@ App.Presenters = App.Presenters || {}; App.Presenters.PostNotesPresenter = function( jQuery, util, - promise) { + promise, + api, + auth) { var post; var notes; var templates = {}; var $target; + var $form; + var privileges = {}; function init(params, loaded) { $target = params.$target; post = params.post; notes = params.notes || []; + privileges.canDeletePostNotes = auth.hasPrivilege(auth.privileges.deletePostNotes); + privileges.canEditPostNotes = auth.hasPrivilege(auth.privileges.editPostNotes); + promise.wait(util.promiseTemplate('post-notes')) .then(function(postNotesTemplate) { templates.postNotes = postNotesTemplate; @@ -27,26 +34,100 @@ App.Presenters.PostNotesPresenter = function( }); } - function addNewNote() { - notes.push({left: 50, top: 50, width: 50, height: 50, text: '…'}); + function addNewPostNote() { + notes.push({left: 50, top: 50, width: 50, height: 50, text: '…'}); } - function addNewNoteAndRender() { - addNewNote(); + function addNewPostNoteAndRender() { + addNewPostNote(); render(); } function render() { - $target.html(templates.postNotes({post: post, notes: notes})); + $target.html(templates.postNotes({ + privileges: privileges, + post: post, + notes: notes, + formatMarkdown: util.formatMarkdown})); + + $form = $target.find('.post-note-edit'); var $postNotes = $target.find('.post-note'); $postNotes.each(function(i) { + var postNote = notes[i]; var $postNote = jQuery(this); - $postNote.data('postNote', notes[i]); - $postNote.find('.text-wrapper').mouseup(postNoteClicked); + $postNote.data('postNote', postNote); + $postNote.find('.text-wrapper').click(postNoteClicked); + postNote.$element = $postNote; makeDraggable($postNote); makeResizable($postNote); }); + + $form.find('button').click(formSubmitted); + } + + function formSubmitted(e) { + e.preventDefault(); + var $button = jQuery(e.target); + var sender = $button.val(); + + var postNote = $form.data('postNote'); + postNote.left = postNote.$element.offset().left - $target.offset().left; + postNote.top = postNote.$element.offset().top - $target.offset().top; + postNote.width = postNote.$element.width(); + postNote.height = postNote.$element.height(); + postNote.text = $form.find('textarea').val(); + + if (sender === 'cancel') { + hideForm(); + } else if (sender === 'remove') { + removePostNote(postNote); + } else if (sender === 'save') { + savePostNote(postNote); + } + } + + function removePostNote(postNote) { + if (postNote.id) { + if (window.confirm('Are you sure you want to delete this note?')) { + promise.wait(api.delete('/notes/' + postNote.id)) + .then(function() { + hideForm(); + postNote.$element.remove(); + }).fail(function(response) { + window.alert(response.json && response.json.error || response); + }); + } + } else { + postNote.$element.remove(); + hideForm(); + } + } + + function savePostNote(postNote) { + if (window.confirm('Are you sure you want to save this note?')) { + var formData = { + left: postNote.left, + top: postNote.top, + width: postNote.width, + height: postNote.height, + text: postNote.text, + }; + + var p = postNote.id ? + api.put('/notes/' + postNote.id, formData) : + api.post('/notes/' + post.id, formData); + + promise.wait(p) + .then(function(response) { + hideForm(); + postNote.id = response.json.id; + postNote.$element.data('postNote', postNote); + render(); + }).fail(function(response) { + window.alert(response.json && response.json.error || response); + }); + } } function postNoteClicked(e) { @@ -55,8 +136,19 @@ App.Presenters.PostNotesPresenter = function( if ($postNote.hasClass('resizing') || $postNote.hasClass('dragging')) { return; } + showFormForPostNote($postNote); } + function showFormForPostNote($postNote) { + var postNote = $postNote.data('postNote'); + $form.data('postNote', postNote); + $form.find('textarea').val(postNote.text); + $form.show(); + } + + function hideForm() { + $form.hide(); + } function makeDraggable($element) { var $dragger = jQuery('
'); @@ -103,7 +195,6 @@ App.Presenters.PostNotesPresenter = function( e.stopPropagation(); $element.addClass('resizing'); - var $parent = $element.parent(); var deltaX = $element.width() - e.clientX; var deltaY = $element.height() - e.clientY; @@ -129,7 +220,7 @@ App.Presenters.PostNotesPresenter = function( return { init: init, render: render, - addNewNote: addNewNoteAndRender, + addNewPostNote: addNewPostNoteAndRender, }; }; @@ -137,5 +228,7 @@ App.Presenters.PostNotesPresenter = function( App.DI.register('postNotesPresenter', [ 'jQuery', 'util', - 'promise'], + 'promise', + 'api', + 'auth'], App.Presenters.PostNotesPresenter); diff --git a/public_html/js/Presenters/PostPresenter.js b/public_html/js/Presenters/PostPresenter.js index 0296e052..74c9e7e2 100644 --- a/public_html/js/Presenters/PostPresenter.js +++ b/public_html/js/Presenters/PostPresenter.js @@ -36,6 +36,7 @@ App.Presenters.PostPresenter = function( privileges.canDeletePosts = auth.hasPrivilege(auth.privileges.deletePosts); privileges.canFeaturePosts = auth.hasPrivilege(auth.privileges.featurePosts); privileges.canViewHistory = auth.hasPrivilege(auth.privileges.viewHistory); + privileges.canAddPostNotes = auth.hasPrivilege(auth.privileges.addPostNotes); promise.wait( util.promiseTemplate('post'), @@ -70,7 +71,7 @@ App.Presenters.PostPresenter = function( [postContentPresenter, {post: post, $target: $el.find('#post-content-target')}], [postEditPresenter, {post: post, $target: $el.find('#post-edit-target'), updateCallback: postEdited}], [postCommentListPresenter, {post: post, $target: $el.find('#post-comments-target')}]], - function() {}); + function() { }); }).fail(function() { console.log(arguments); @@ -182,6 +183,12 @@ App.Presenters.PostPresenter = function( $el.find('#sidebar .delete-favorite').click(deleteFavoriteButtonClicked); $el.find('#sidebar .score-up').click(scoreUpButtonClicked); $el.find('#sidebar .score-down').click(scoreDownButtonClicked); + $el.find('#sidebar .add-note').click(addNoteButtonClicked); + } + + function addNoteButtonClicked(e) { + e.preventDefault(); + postContentPresenter.addNewPostNote(); } function deleteButtonClicked(e) { diff --git a/public_html/templates/post-notes.tpl b/public_html/templates/post-notes.tpl index dee33416..b37c565d 100644 --- a/public_html/templates/post-notes.tpl +++ b/public_html/templates/post-notes.tpl @@ -8,10 +8,23 @@
- <%= note.text %> + <%= formatMarkdown(note.text) %>
<% }) %> + +
+ +
<% if (privileges.canEditPostNotes) { %><% } %><% if (privileges.canDeletePostNotes) { %><% } %>
+
diff --git a/public_html/templates/post.tpl b/public_html/templates/post.tpl index 5f1dfd50..c9a724bf 100644 --- a/public_html/templates/post.tpl +++ b/public_html/templates/post.tpl @@ -207,6 +207,14 @@ <% } %> + <% if (privileges.canAddPostNotes) { %> +
  • + + Add new note + +
  • + <% } %> + <% if (privileges.canDeletePosts) { %>