Added post note editing to frontend

This commit is contained in:
Marcin Kurczewski 2014-10-26 01:01:16 +02:00
parent e983e72013
commit 50608074c6
7 changed files with 184 additions and 28 deletions

10
TODO
View file

@ -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

View file

@ -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;

View file

@ -40,9 +40,14 @@ App.Presenters.PostContentPresenter = function(
function() {});
}
function addNewPostNote() {
postNotesPresenter.addNewPostNote();
}
return {
init: init,
render: render,
addNewPostNote: addNewPostNote,
};
};

View file

@ -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('<div class="dragger"></div>');
@ -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);

View file

@ -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) {

View file

@ -8,10 +8,23 @@
<div class="text-wrapper">
<div class="text">
<%= note.text %>
<%= formatMarkdown(note.text) %>
</div>
</div>
</div>
<% }) %>
</div>
<form class="post-note-edit">
<textarea></textarea>
<div class="actions"><!--
--><% if (privileges.canEditPostNotes) { %><!--
--><button type="submit" name="sender" value="save">Save</button><!--
--><% } %><!--
--><button type="submit" name="sender" value="cancel">Cancel</button><!--
--><% if (privileges.canDeletePostNotes) { %><!--
--><button type="submit" name="sender" value="remove">Remove</button><!--
--><% } %><!--
--></div>
</form>

View file

@ -207,6 +207,14 @@
</li>
<% } %>
<% if (privileges.canAddPostNotes) { %>
<li>
<a class="add-note" href="#">
Add new note
</a>
</li>
<% } %>
<% if (privileges.canDeletePosts) { %>
<li>
<a class="delete" href="#">