Added post note editing to frontend
This commit is contained in:
parent
e983e72013
commit
50608074c6
7 changed files with 184 additions and 28 deletions
10
TODO
10
TODO
|
@ -11,15 +11,7 @@ first major release.
|
||||||
- tags: add tag edit snapshots (backed-only)
|
- tags: add tag edit snapshots (backed-only)
|
||||||
|
|
||||||
- post notes
|
- post notes
|
||||||
- "add note" in sidebar creates new note in the middle of image
|
- post notes history
|
||||||
- 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)
|
|
||||||
|
|
||||||
refactors:
|
refactors:
|
||||||
- add enum validation in IValidatables (needs refactors of enums and
|
- add enum validation in IValidatables (needs refactors of enums and
|
||||||
|
|
|
@ -174,6 +174,10 @@
|
||||||
.post-content {
|
.post-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.post-notes-target,
|
||||||
|
.post-notes {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
.post-notes {
|
.post-notes {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -181,17 +185,35 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 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 {
|
.post-note {
|
||||||
|
pointer-events: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: rgba(255, 255, 255, 0.3);
|
background: rgba(255, 255, 255, 0.3);
|
||||||
border: 1px solid rgba(0, 0, 0, 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 {
|
.post-note .text-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -199,6 +221,9 @@
|
||||||
left: -1px;
|
left: -1px;
|
||||||
padding-top: 0.5em;
|
padding-top: 0.5em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
width: -webkit-max-content;
|
||||||
|
width: -moz-max-content;
|
||||||
|
width: max-content;
|
||||||
}
|
}
|
||||||
.post-note .text {
|
.post-note .text {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
@ -209,6 +234,19 @@
|
||||||
display: block;
|
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 {
|
.post-note .resizer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: nwse-resize;
|
cursor: nwse-resize;
|
||||||
|
|
|
@ -40,9 +40,14 @@ App.Presenters.PostContentPresenter = function(
|
||||||
function() {});
|
function() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addNewPostNote() {
|
||||||
|
postNotesPresenter.addNewPostNote();
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
render: render,
|
render: render,
|
||||||
|
addNewPostNote: addNewPostNote,
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,18 +4,25 @@ App.Presenters = App.Presenters || {};
|
||||||
App.Presenters.PostNotesPresenter = function(
|
App.Presenters.PostNotesPresenter = function(
|
||||||
jQuery,
|
jQuery,
|
||||||
util,
|
util,
|
||||||
promise) {
|
promise,
|
||||||
|
api,
|
||||||
|
auth) {
|
||||||
|
|
||||||
var post;
|
var post;
|
||||||
var notes;
|
var notes;
|
||||||
var templates = {};
|
var templates = {};
|
||||||
var $target;
|
var $target;
|
||||||
|
var $form;
|
||||||
|
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.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;
|
||||||
|
@ -27,26 +34,100 @@ App.Presenters.PostNotesPresenter = function(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewNote() {
|
function addNewPostNote() {
|
||||||
notes.push({left: 50, top: 50, width: 50, height: 50, text: '…'});
|
notes.push({left: 50, top: 50, width: 50, height: 50, text: '…'});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewNoteAndRender() {
|
function addNewPostNoteAndRender() {
|
||||||
addNewNote();
|
addNewPostNote();
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
function 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');
|
var $postNotes = $target.find('.post-note');
|
||||||
|
|
||||||
$postNotes.each(function(i) {
|
$postNotes.each(function(i) {
|
||||||
|
var postNote = notes[i];
|
||||||
var $postNote = jQuery(this);
|
var $postNote = jQuery(this);
|
||||||
$postNote.data('postNote', notes[i]);
|
$postNote.data('postNote', postNote);
|
||||||
$postNote.find('.text-wrapper').mouseup(postNoteClicked);
|
$postNote.find('.text-wrapper').click(postNoteClicked);
|
||||||
|
postNote.$element = $postNote;
|
||||||
makeDraggable($postNote);
|
makeDraggable($postNote);
|
||||||
makeResizable($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) {
|
function postNoteClicked(e) {
|
||||||
|
@ -55,8 +136,19 @@ App.Presenters.PostNotesPresenter = function(
|
||||||
if ($postNote.hasClass('resizing') || $postNote.hasClass('dragging')) {
|
if ($postNote.hasClass('resizing') || $postNote.hasClass('dragging')) {
|
||||||
return;
|
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) {
|
function makeDraggable($element) {
|
||||||
var $dragger = jQuery('<div class="dragger"></div>');
|
var $dragger = jQuery('<div class="dragger"></div>');
|
||||||
|
@ -103,7 +195,6 @@ App.Presenters.PostNotesPresenter = function(
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
$element.addClass('resizing');
|
$element.addClass('resizing');
|
||||||
|
|
||||||
var $parent = $element.parent();
|
|
||||||
var deltaX = $element.width() - e.clientX;
|
var deltaX = $element.width() - e.clientX;
|
||||||
var deltaY = $element.height() - e.clientY;
|
var deltaY = $element.height() - e.clientY;
|
||||||
|
|
||||||
|
@ -129,7 +220,7 @@ App.Presenters.PostNotesPresenter = function(
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
render: render,
|
render: render,
|
||||||
addNewNote: addNewNoteAndRender,
|
addNewPostNote: addNewPostNoteAndRender,
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -137,5 +228,7 @@ App.Presenters.PostNotesPresenter = function(
|
||||||
App.DI.register('postNotesPresenter', [
|
App.DI.register('postNotesPresenter', [
|
||||||
'jQuery',
|
'jQuery',
|
||||||
'util',
|
'util',
|
||||||
'promise'],
|
'promise',
|
||||||
|
'api',
|
||||||
|
'auth'],
|
||||||
App.Presenters.PostNotesPresenter);
|
App.Presenters.PostNotesPresenter);
|
||||||
|
|
|
@ -36,6 +36,7 @@ App.Presenters.PostPresenter = function(
|
||||||
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);
|
||||||
|
|
||||||
promise.wait(
|
promise.wait(
|
||||||
util.promiseTemplate('post'),
|
util.promiseTemplate('post'),
|
||||||
|
@ -182,6 +183,12 @@ App.Presenters.PostPresenter = function(
|
||||||
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNoteButtonClicked(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
postContentPresenter.addNewPostNote();
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteButtonClicked(e) {
|
function deleteButtonClicked(e) {
|
||||||
|
|
|
@ -8,10 +8,23 @@
|
||||||
|
|
||||||
<div class="text-wrapper">
|
<div class="text-wrapper">
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<%= note.text %>
|
<%= formatMarkdown(note.text) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</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>
|
||||||
|
|
|
@ -207,6 +207,14 @@
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
|
<% if (privileges.canAddPostNotes) { %>
|
||||||
|
<li>
|
||||||
|
<a class="add-note" href="#">
|
||||||
|
Add new note
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<% if (privileges.canDeletePosts) { %>
|
<% if (privileges.canDeletePosts) { %>
|
||||||
<li>
|
<li>
|
||||||
<a class="delete" href="#">
|
<a class="delete" href="#">
|
||||||
|
|
Loading…
Reference in a new issue