From 268ee8e23555c5458247a1631f5e03171cbd51f5 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Thu, 16 Apr 2020 15:58:04 +0200 Subject: [PATCH] Big Refactor Add missing classes to identify elements Translations: remove the ":", they can be added via css if needed. Adjust french translation. Add hint translation for all languages Simplify the templates, remove not needed containers, use subtemplate to avoid repetition Use acl-write class for approve button instead of hidden them inserting css clean css: style it again from scratch Fixs position of comments (take in account containers padding) Fix comment icons : if displayCommentAsIcon is true, then we hide the comment boxes, and display only comment icons. On clicking icon the comment appear with a modal Allow to create a comment without text, including only a suggest change Improve coloring of commented text on editor (no more conflict with skins or font-color) Adds message for etherpad 1.8.3 required --- commentManager.js | 17 + index.js | 9 + locales/de.json | 14 +- locales/en.json | 14 +- locales/fr.json | 18 +- locales/pl.json | 14 +- locales/pt-BR.json | 12 +- static/css/comment.css | 455 +++++---------- static/css/commentIcon.css | 31 +- static/js/commentBoxes.js | 87 +-- static/js/commentIcons.js | 83 +-- static/js/index.js | 518 ++++++++---------- static/js/newComment.js | 199 ++----- .../tests/frontend/specs/commentCopyPaste.js | 2 +- static/tests/frontend/specs/commentDelete.js | 4 +- static/tests/frontend/specs/commentEdit.js | 2 +- static/tests/frontend/specs/commentIcons.js | 4 +- static/tests/frontend/specs/commentReply.js | 12 +- .../tests/frontend/specs/commentSuggestion.js | 6 +- static/tests/frontend/specs/comment_l10n.js | 10 +- .../tests/frontend/specs/comment_settings.js | 4 +- static/tests/frontend/specs/preCommentMark.js | 4 +- templates/commentBarButtons.ejs | 4 +- templates/comments.html | 209 +++---- templates/styles.html | 1 + 25 files changed, 707 insertions(+), 1026 deletions(-) diff --git a/commentManager.js b/commentManager.js index 00e180ff..505db063 100644 --- a/commentManager.js +++ b/commentManager.js @@ -27,6 +27,23 @@ exports.getComments = function (padId, callback) }); }; +exports.deleteComment = function (padId, commentId, callback) +{ + db.get('comments:' + padId, function(err, comments) + { + if(ERR(err, callback)) return; + + // the entry doesn't exist so far, let's create it + if(comments == null) comments = {}; + + delete comments[commentId]; + db.set("comments:" + padId, comments); + + callback(padId, commentId); + + }); +}; + exports.deleteComments = function (padId, callback) { db.remove('comments:' + padId, function(err) diff --git a/index.js b/index.js index 2642aa41..dcb2b8f0 100644 --- a/index.js +++ b/index.js @@ -72,6 +72,15 @@ exports.socketio = function (hook_name, args, cb){ }); }); + socket.on('deleteComment', function(data, callback) { + // delete the comment on the database + commentManager.deleteComment(data.padId, data.commentId, function (){ + // Broadcast to all other users that this comment was deleted + socket.broadcast.to(data.padId).emit('commentDeleted', data.commentId); + }); + + }); + socket.on('revertChange', function(data, callback) { // Broadcast to all other users that this change was accepted. // Note that commentId here can either be the commentId or replyId.. diff --git a/locales/de.json b/locales/de.json index 6bd85884..06a445b8 100644 --- a/locales/de.json +++ b/locales/de.json @@ -2,19 +2,21 @@ "ep_comments_page.comment" : "Kommentar", "ep_comments_page.comments" : "Kommentare", "ep_comments_page.add_comment.title" : "Kommentar zur Auswahl hinzufügen", + "ep_comments_page.add_comment.hint" : "Bitte wählen Sie zuerst den zu kommentierenden Text aus", "ep_comments_page.delete_comment.title" : "Diesen Kommentar löschen", "ep_comments_page.show_comments" : "Kommentare anzeigen", - "ep_comments_page.comments_template.suggested_change" : "Vorgeschlagene Änderung:", - "ep_comments_page.comments_template.from" : "von:", + "ep_comments_page.comments_template.suggested_change" : "Vorgeschlagene Änderung", + "ep_comments_page.comments_template.from" : "von", "ep_comments_page.comments_template.accept_change.value" : "Änderung akzeptieren", "ep_comments_page.comments_template.revert_change.value" : "Änderung zurücknehmen", - "ep_comments_page.comments_template.suggested_change_from" : "Vorgeschlagene Änderung von:", - "ep_comments_page.comments_template.suggest_change_from" : "von:", - "ep_comments_page.comments_template.to" : "zu:", + "ep_comments_page.comments_template.suggested_change_from" : "Vorgeschlagene Änderung von", + "ep_comments_page.comments_template.suggest_change_from" : "von", + "ep_comments_page.comments_template.to" : "zu", "ep_comments_page.comments_template.include_suggestion" : "Änderung vorschlagen", "ep_comments_page.comments_template.comment.value" : "Kommentar", "ep_comments_page.comments_template.cancel.value" : "Abbrechen", - "ep_comments_page.comments_template.reply_input_label.placeholder":"antworten: (mit ENTER)", + "ep_comments_page.comments_template.reply.value": "Antworten", + "ep_comments_page.comments_template.reply.placeholder": "Antworten", "ep_comments_page.time.seconds.past" : "vor {{count}} Sekunden", "ep_comments_page.time.seconds.future" : "{{count}} Sekunden von jetzt an", "ep_comments_page.time.one_minute.past" : "vor 1 Minute", diff --git a/locales/en.json b/locales/en.json index 419780c8..4ce1875b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -2,20 +2,22 @@ "ep_comments_page.comment" : "Comment", "ep_comments_page.comments" : "Comments", "ep_comments_page.add_comment.title" : "Add new comment on selection", + "ep_comments_page.add_comment.hint" : "Please first select the text to comment", "ep_comments_page.delete_comment.title" : "Delete this comment", "ep_comments_page.edit_comment.title" : "Edit this comment", "ep_comments_page.show_comments" : "Show Comments", - "ep_comments_page.comments_template.suggested_change" : "Suggested Change:", - "ep_comments_page.comments_template.from" : "From:", + "ep_comments_page.comments_template.suggested_change" : "Suggested Change", + "ep_comments_page.comments_template.from" : "From", "ep_comments_page.comments_template.accept_change.value" : "Accept Change", "ep_comments_page.comments_template.revert_change.value" : "Revert Change", - "ep_comments_page.comments_template.suggested_change_from" : "Suggested change From:", - "ep_comments_page.comments_template.suggest_change_from" : "Suggest change From:", - "ep_comments_page.comments_template.to" : "To:", + "ep_comments_page.comments_template.suggested_change_from" : "Suggested change From", + "ep_comments_page.comments_template.suggest_change_from" : "Suggest change From", + "ep_comments_page.comments_template.to" : "To", "ep_comments_page.comments_template.include_suggestion" : "Include suggested change", "ep_comments_page.comments_template.comment.value" : "Comment", "ep_comments_page.comments_template.cancel.value" : "Cancel", - "ep_comments_page.comments_template.reply_input_label.placeholder":"Your Reply (hit ENTER to send)", + "ep_comments_page.comments_template.reply.value" : "Reply", + "ep_comments_page.comments_template.reply.placeholder" : "Reply", "ep_comments_page.time.seconds.past" : "{{count}} seconds ago", "ep_comments_page.time.seconds.future" : "{{count}} seconds from now", "ep_comments_page.time.one_minute.past" : "1 minute ago", diff --git a/locales/fr.json b/locales/fr.json index f285101f..3fd83f49 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -2,19 +2,21 @@ "ep_comments_page.comment" : "Annotation", "ep_comments_page.comments" : "Annotations", "ep_comments_page.add_comment.title" : "Annoter la sélection", + "ep_comments_page.add_comment.hint" : "Vous devez d'abord sélectionner un texte à annoter", "ep_comments_page.delete_comment.title" : "Supprimer cette annotation", "ep_comments_page.show_comments" : "Afficher les annotations", - "ep_comments_page.comments_template.suggested_change" : "Modification proposée :", - "ep_comments_page.comments_template.from" : "Remplacer :", - "ep_comments_page.comments_template.accept_change.value" : "Appliquer la modification", - "ep_comments_page.comments_template.revert_change.value" : "Annuler la modification", - "ep_comments_page.comments_template.suggested_change_from" : "Modification proposée par :", - "ep_comments_page.comments_template.suggest_change_from" : "Proposer une modification de :", - "ep_comments_page.comments_template.to" : "Par :", + "ep_comments_page.comments_template.suggested_change" : "Modification proposée", + "ep_comments_page.comments_template.from" : "Remplacer", + "ep_comments_page.comments_template.accept_change.value" : "Appliquer la proposition", + "ep_comments_page.comments_template.revert_change.value" : "Annuler la proposition", + "ep_comments_page.comments_template.suggested_change_from" : "Propose de remplacer", + "ep_comments_page.comments_template.suggest_change_from" : "Remplacer", + "ep_comments_page.comments_template.to" : "Par", "ep_comments_page.comments_template.include_suggestion" : "Proposer une modification", "ep_comments_page.comments_template.comment.value" : "Annotation", "ep_comments_page.comments_template.cancel.value" : "Annuler", - "ep_comments_page.comments_template.reply_input_label.placeholder":"Votre réponse (pressez ENTRÉE pour valider)", + "ep_comments_page.comments_template.reply.value":"Répondre", + "ep_comments_page.comments_template.reply.placeholder":"Répondre", "ep_comments_page.time.seconds.past" : "il y a {{count}} secondes", "ep_comments_page.time.seconds.future" : "dans {{count}} secondes", "ep_comments_page.time.one_minute.past" : "il y a 1 minute", diff --git a/locales/pl.json b/locales/pl.json index d006e8fe..73dc5eb2 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -2,19 +2,21 @@ "ep_comments_page.comment" : "Komentarz", "ep_comments_page.comments" : "Komentarze", "ep_comments_page.add_comment.title" : "Dodaj nowy komentarz do sekcji", + "ep_comments_page.add_comment.hint" : "Najpierw wybierz tekst do skomentowania", "ep_comments_page.delete_comment.title" : "Usuń komentarz", "ep_comments_page.show_comments" : "Pokaż komentarze", - "ep_comments_page.comments_template.suggested_change" : "Sugerowane zmiany:", - "ep_comments_page.comments_template.from" : "Od:", + "ep_comments_page.comments_template.suggested_change" : "Sugerowane zmiany", + "ep_comments_page.comments_template.from" : "Od", "ep_comments_page.comments_template.accept_change.value" : "Zaakceptuj zmiany", "ep_comments_page.comments_template.revert_change.value" : "Przywróc zmiany", - "ep_comments_page.comments_template.suggested_change_from" : "Sugerowana zmiana z:", - "ep_comments_page.comments_template.suggest_change_from" : "Zaproponuj zmiane z:", - "ep_comments_page.comments_template.to" : "Do:", + "ep_comments_page.comments_template.suggested_change_from" : "Sugerowana zmiana z", + "ep_comments_page.comments_template.suggest_change_from" : "Zaproponuj zmiane z", + "ep_comments_page.comments_template.to" : "Do", "ep_comments_page.comments_template.include_suggestion" : "Dołącz sugestie", "ep_comments_page.comments_template.comment.value" : "Komentarz", "ep_comments_page.comments_template.cancel.value" : "Anuluj", - "ep_comments_page.comments_template.reply_input_label.placeholder":"Odpowiedź (wciśnij ENTER aby wysłać)", + "ep_comments_page.comments_template.reply.value": "Odpowiedź", + "ep_comments_page.comments_template.reply.placeholder": "Odpowiedź", "ep_comments_page.time.seconds.past" : "{{count}} sekund temu", "ep_comments_page.time.seconds.future" : "{{count}} sekund od teraz", "ep_comments_page.time.one_minute.past" : "Minutę temu", diff --git a/locales/pt-BR.json b/locales/pt-BR.json index 06c924b4..94d8f818 100644 --- a/locales/pt-BR.json +++ b/locales/pt-BR.json @@ -2,20 +2,20 @@ "ep_comments_page.comment" : "Comentário", "ep_comments_page.comments" : "Comentários", "ep_comments_page.add_comment.title" : "Adicionar novo comentário ao texto selecionado", + "ep_comments_page.add_comment.hint" : "Por favor, selecione primeiro o texto para comentar", "ep_comments_page.delete_comment.title" : "Apagar este comentário", "ep_comments_page.edit_comment.title" : "Editar este comentário", "ep_comments_page.show_comments" : "Mostrar Comentários", - "ep_comments_page.comments_template.suggested_change" : "Alteração Sugerida:", - "ep_comments_page.comments_template.from" : "De:", "ep_comments_page.comments_template.accept_change.value" : "Aceitar Sugestão", "ep_comments_page.comments_template.revert_change.value" : "Reverter Sugestão", - "ep_comments_page.comments_template.suggested_change_from" : "Alteração sugerida de:", - "ep_comments_page.comments_template.suggest_change_from" : "Sugerir alteração de:", - "ep_comments_page.comments_template.to" : "Para:", + "ep_comments_page.comments_template.suggested_change_from" : "Alteração sugerida de", + "ep_comments_page.comments_template.suggest_change_from" : "Sugerir alteração de", + "ep_comments_page.comments_template.to" : "Para", "ep_comments_page.comments_template.include_suggestion" : "Incluir alteração sugerida", "ep_comments_page.comments_template.comment.value" : "Comentário", "ep_comments_page.comments_template.cancel.value" : "Cancelar", - "ep_comments_page.comments_template.reply_input_label.placeholder":"Sua Resposta (clique ENTER para enviar)", + "ep_comments_page.comments_template.reply.value":"Responder", + "ep_comments_page.comments_template.reply.placeholder":"Responder", "ep_comments_page.time.seconds.past" : "{{count}} segundos atrás", "ep_comments_page.time.seconds.future" : "daqui a {{count}} segundos", "ep_comments_page.time.one_minute.past" : "1 minuto atrás", diff --git a/static/css/comment.css b/static/css/comment.css index 48bd6a5b..28ebb4cc 100644 --- a/static/css/comment.css +++ b/static/css/comment.css @@ -1,362 +1,211 @@ -note{ - display:block; +/* Text commented inside editor */ +#innerdocbody .ace-line .comment { + background-color: #fff382; + color: #222; } - -.comment { - /*WARNING - if you change this value, you need to change it too on copyPasteEvents.js, otherwise you'll break part of the copy/paste behavior"*/ - background: #FFFACD !important; - border-radius: 0px; -} - - -.pre-selected-comment { - background: #FFE168 !important; -} - -time{ - display:none; +#innerdocbody .ace-line .comment[data-open="true"]{ + color: orange !important; } -.comment-modal .comment-text, -.sidebar-comment:hover .comment-text, -.sidebar-comment.mouseover .comment-text{ - height:auto; - white-space:normal; - text-overflow:auto; -} - -.sidebar-comment{ - line-height:24px; -} -.sidebar-comment:hover, -.sidebar-comment.mouseover{ - height:auto; - white-space:normal; - text-overflow:auto; -} - -.sidebar-comment:hover .comment-reply-button, -.sidebar-comment.mouseover .comment-reply-button { - display:block; -} - -.comment-modal{ - position:absolute; - min-width:20px; - max-width:50%; - min-height:10px; - background-color:#fff; - z-index:99999; - top:0; - left:0; - border:solid #ccc 1px; - display:none; - padding:10px 10px 0px 10px; - font-size:12px; - line-height:24px; -} - -.comment-changeTo-approve{ - margin:0px 0px 10px 0px; -} - -.reply-suggestion.active { - display: block; +/* Comment right side container */ +#comments { + width: 250px; + order: 3; + position: relative; } - -/* hide comment elements when displayed on modal, not on sidebar */ -.comment-modal .reply-comment-suggest, -.comment-modal .comment-reply-input-label, -.comment-modal .reply-suggestion, -.comment-modal input, -.comment-modal .comment-changeTo-approve{ - display:none; +#comments:not(.active) { + display: none; } - -.sidebar-comment-reply{ - padding-top:5px; - padding-left:10px; - margin-bottom: 5px; - position: relative; +@media (max-width: 900px) { + #commentIcons, #comments { + display: none !important; + } } -.sidebar-comment-reply:nth-child(even){ - background-color:#f1f1f1; +.sidebar-comment { + position: absolute; + width: 100%; + margin-top: 3px; } -.comment-reply{ - margin-bottom:5px; +/* WITH ICONS */ +#comments.with-icons { + display: none; } -.comment-reply-input{ - margin-top:10px; - width:187px; +/* NEW COMMENT FORM (included both in popup and reply) */ +.new-comment .comment-content { + width: 100%; } - -#comments { - display:none; - width:210px; - bottom:0; - position:absolute; - top:10px; - right:0px; - font-family: Helvetica, Arial, sans-serif; - z-index:1; +.comment-reply .new-comment:not(.editing) .form-more { + display: none; } - -#newComments { - display:none; - width:210px; - bottom:0; - position:absolute; - top:10px; - right:0px; - font-family: Helvetica, Arial, sans-serif; - z-index:1; +input.error, textarea.error { + border-color: red; } -#newComments.active { - display:block; - z-index: 1000; +/* COMMENT GENERAL STYLE */ +.comment-author-name { + font-weight: bold; } - -.comment-reply-button{ - float:right; - display:none; +.comment-created-at { + font-size: .8em; + opacity: .7; + margin-left: 5px; } - -.comment-reply-button:hover{ - cursor:pointer; - color:#000; +.comment-actions-wrapper { + float: right; } -.comment-reply, .suggestion{ - font-weight:bold; - color:#555 -} -.reply-suggestion .reply-comment-suggest-from, .suggestion .comment-suggest-from, .comment-changeTo-value, .comment-changeFrom-value{ - font-weight: normal; - color: black; - padding-left: 0.5em; - border:none; - border-left: 2px solid #DDD; - max-height: 5em; - width: 178px; - overflow-y: auto; - white-space: normal; - line-height: 1.3em; +/* COMMENT COMPACTED (Visible on right side) */ +.sidebar-comment:not(.full-display) .full-display-content { + display: none; } - -.sidebar-comment { +.compact-display-content { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - right:0px; - margin-left: 10px; - padding: 0px 5px 5px 5px; - background: white; - width:90%; - height:20px; - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; - -moz-box-shadow: 0 0 2px #888; - -webkit-box-shadow: 0 0 2px #888; - box-shadow: 0 0 2px #888; - position:absolute; - font-size:12px; + padding: 0 10px; + background-color: #eeeeed; } -.sidebar-comment input[type=submit]{ - padding:2px; +/* COMMENT FULL (when mouse hover) */ +.sidebar-comment.full-display { + z-index: 2; } - -.sidebar-comment:hover, -.sidebar-comment.mouseover{ - z-index:999; +.sidebar-comment.full-display .full-display-content { + display: block; + margin-top: -10px; } - -.sidebar-comment.mouseover { - - z-index:99999; - margin: 0px 0 0 14px; - white-space:normal; - height:auto; - -webkit-transition: z-index 100ms ease-in, margin 100ms ease-in, background 100ms ease-in; - -moz-transition: z-index 100ms ease-in, margin 100ms ease-in, background 100ms ease-in; - -ms-transition: z-index 100ms ease-in, margin 100ms ease-in, background 100ms ease-in; - -o-transition: z-index 100ms ease-in, margin 100ms ease-in, background 100ms ease-in; - transition: z-index 100ms ease-in, margin 100ms ease-in, background 100ms ease-in; +.sidebar-comment.full-display .compact-display-content { + display: none; } - -.comment-author-name { - color: #555; - font-weight:bold; - font-size: 1em; - display:inline; +.full-display-content { + background-color: white; + border-radius: 5px; + overflow: hidden; + box-shadow: 0 2px 4px #ddd; + z-index: 99; } - -.comment-changeTo-label, .comment-changeFrom-label{ - color: #555; - font-weight:bold; - font-size: 1em; - display:inline; +.full-display-content .comment-title-wrapper, +.full-display-content .comment-reply { + padding: 10px; } - -.comment-text { - font-size: 1em; - color: #333; - word-wrap: break-word; +.full-display-content .comment-title-wrapper .comment-text { + display: block; + margin-top: 10px; white-space: normal; - display: inline; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; } - -.sidebar-comment textarea { - border: 2px solid #DDD; - background: #fff; - width: 170px; - height: 40px; - padding: 5px; - font-size:1em; +.full-display-content .comment-title-wrapper .comment-text.default-text { + display: none; } -.comment-content:focus { - border: 2px solid #ccc; +/* SUGGESTION */ +.suggestion, .reply-suggestion { + display: none; } - -.comment-buttons input { - color: #666; - border: 2px solid #DDD; - background: #EEE; - width: 59px; - height: 30px; - font-size:12px; +.suggestion-display { + margin-top: 5px; + white-space: normal; } - -.comment-buttons input[type="submit"] { - margin-right: 5px; - width:120px; +.suggestion-display .from-label, +.suggestion-display .to-label { + margin-right: 2px; } - -.comment-buttons input:hover { - color: #333; - cursor: pointer; +.suggestion-display .from-value, +.suggestion-display .to-value { + opacity: .8; + font-style: italic; } - -#newComment.visible{ - width:250px; - right:0px; - -webkit-transition: width 500ms; - -moz-transition: width 500ms; - -ms-transition: width 500ms; - -o-transition: width 500ms; - transition: width 500ms; - height:auto; - min-height:100px; +.suggestion-display .from-value:after, .suggestion-display .from-value:before, +.suggestion-display .to-value:after, .suggestion-display .to-value:before { + content: '"'; } - -#newComment.hidden{ - width:0px; - right:-50px; - -webkit-transition: right 500ms, width 500ms; - -moz-transition: right 500ms, width 500ms; - -ms-transition: right 500ms, width 500ms; - -o-transition: right 500ms, width 500ms; - transition: right 500ms, width 500ms; +.suggestion-create .from-label, +.suggestion-create .to-label { + display: block; + font-weight: bold; + margin: 5px 0; } - -#newComment{ - position:fixed; - display:block; - width:0px; - z-index:1000; - font-size:12px; +.approve-suggestion-btn, .revert-suggestion-btn { + display: block; + margin-bottom: 10px; } - -#outerdocbody{ - width:1050px; +.suggestion-create textarea.to-value { + width: 100%; } +.comment-container .revert-suggestion-btn { display: none; } +.comment-container.change-accepted .revert-suggestion-btn { display: block; } +.comment-container.change-accepted .approve-suggestion-btn { display: none; } -/* if Page View is disabled, we use the full width. (This is only changed - by commentIcons.js) */ -#outerdocbody.pageViewDisabled{ - width:100%; +.comment-container.change-accepted .comment-replies-container .revert-suggestion-btn { display: none; } +.comment-container.change-accepted .comment-replies-container .approve-suggestion-btn { display: block; } +.comment-container.change-accepted .comment-replies-container .comment-container.change-accepted .revert-suggestion-btn { display: block; } +.comment-container.change-accepted .comment-replies-container .comment-container.change-accepted .approve-suggestion-btn { display: none; } +/* REPLIES */ +.comment-reply { + background-color: #f9f9f9; + border-top: 1px solid #eee; } - -/* dont display comments at < 955px wide */ -@media (max-width: 955px) { - #outerdocbody{ - width:100%; - padding-right:0px; - } +/* One previous reply */ +.sidebar-comment-reply { + margin-bottom: 10px; } -/* #innerdocbody is inside of another iframe, so needs a new media query */ -@media (max-width: 947px) { /* 955 - 8 (8 is #innerdocbody left position) */ - #innerdocbody{ - margin-right:0px; - } +.sidebar-comment-reply .comment-text { + display: inline; + margin-top: 5px; + white-space: normal; } - -/* display comments at > 955px wide */ -@media (min-width: 955px) { - #comments.active{ - display:block; - } +.sidebar-comment-reply .comment-edit { + display: none; + margin-left: 5px; + font-size: 1em; + opacity: .7; + transition: opacity .2s; } -/* #innerdocbody is inside of another iframe, so needs a new media query */ -@media (min-width: 947px) { /* 955 - 8 (8 is #innerdocbody left position) */ - #innerdocbody.comments:not(.innerPV){ - margin-right:200px; - } +.sidebar-comment-reply:hover:not(.editing) .comment-edit { + display: inline; } - -.suggestion, .reply-suggestion{ - display:none; +.sidebar-comment-reply:hover:not(.editing) .comment-edit:hover { + opacity: 1; } - -.sidebar-comment-reply input{ - margin-bottom:10px; -/* margin-left:40px; */ +.reply-comment-suggest, .comment-suggest { + margin-top: 10px; } -.comment-options-button{ - float: right; +/* EDITING COMMENT */ +.comment-edit-text { + width: 100%; } -.comment-options-selected{ - background-color: #d8d8d8; +.comment-edit-form + .comment-text { + display: none !important; } -.comment-options-button--icon{ - background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzLjk2OSIgaGVpZ2h0PSIyMC4xNTYiIHZpZXdCb3g9IjAgMCAzLjk2OSAyMC4xNTYiPjxzd2l0Y2g%2BPGcgZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9IiM2RTcwNzIiPjxwYXRoIGQ9Ik0zLjk1NyAyLjk2NWExLjk2NSAxLjk2NSAwIDEgMS0zLjkzIDAgMS45NjUgMS45NjUgMCAwIDEgMy45MyAwTTMuOTU3IDkuOTY1YTEuOTY1IDEuOTY1IDAgMSAxLTMuOTMgMCAxLjk2NSAxLjk2NSAwIDAgMSAzLjkzIDBNMy45NTcgMTYuOTY1YTEuOTY1IDEuOTY1IDAgMSAxLTMuOTMgMCAxLjk2NSAxLjk2NSAwIDAgMSAzLjkzIDAiLz48L2c%2BPC9zd2l0Y2g%2BPC9zdmc%2B); - background-repeat: no-repeat; - cursor: pointer; - padding: 2px; - margin: 6px; - background-size: contain; - background-position: 50% 50%; +/* MODAL FOR MOBILES */ +.comment-modal { + bottom: auto !important; + right: auto !important; } - -.comment-options{ - position: absolute; - background-color: #dcdcdc; - margin-left: 108px; - top: 24px; - right: 5px; - z-index: 999; +.comment-modal-comment { + padding: 0; } - -.comment-delete, .comment-edit { - display: block; - font-weight: bold; - cursor: pointer; - color: #555; - margin-top: 1px; - padding: 0 18px; +.comment-modal-comment .sidebar-comment { + position: relative; + top: 0; } - -.comment-edit:hover, .comment-delete:hover{ - background-color: #b3b3b3; +.comment-modal-comment .compact-display-content { + display: none; +} +.comment-modal-comment .full-display-content { + display: block !important; + margin: 0; } +.comment-modal-comment .comment-content { + margin-top: 0 !important; +} + +/* OTHER */ +.hidden { + display: none; +} \ No newline at end of file diff --git a/static/css/commentIcon.css b/static/css/commentIcon.css index 58fe1e39..a7265e44 100644 --- a/static/css/commentIcon.css +++ b/static/css/commentIcon.css @@ -1,29 +1,17 @@ #commentIcons { display: block; z-index: 1; - margin-left: 760px; - padding-left: 25px; + margin-left: 15px; + width: 50px; + position: relative; } - -/* when line numbers are not visible, we need to move icons to the left */ -#sidediv.sidedivhidden ~ #commentIcons { - padding-left: 0px; -} - -/* when page view is disabled, we need to move icons to the left */ -#outerdocbody.pageViewDisabled #commentIcons { - margin-left: calc(100% - 90px); -} - -/* this is the point where #comments will be visible (check media queries on comment.css) */ -@media (min-width: 955px) { - #outerdocbody.pageViewDisabled #commentIcons { - margin-left: calc(100% - 290px); - } +#commentIcons:not(.active) { + display: none; } .comment-icon-line { position: absolute; + margin-top: 2px; } .comment-icon { background-repeat: no-repeat; @@ -32,10 +20,11 @@ vertical-align: middle; width: 16px; margin-right: 5px; + cursor: pointer; } .comment-icon:before { font-family: "fontawesome-etherpad"; - content: "\e838"; + content: "\E850"; color:#666; font-size:14px; padding-top:2px; @@ -43,9 +32,9 @@ } .comment-icon.with-reply:before { - content: "\e828"; + content: "\E82D"; } .comment-icon.active:before { color:orange; -} +} \ No newline at end of file diff --git a/static/js/commentBoxes.js b/static/js/commentBoxes.js index 2d4fda36..ea82bdcc 100644 --- a/static/js/commentBoxes.js +++ b/static/js/commentBoxes.js @@ -15,69 +15,82 @@ var showComment = function(commentId, e) { var commentElm = getCommentsContainer().find('#'+ commentId); commentElm.show(); - highlightComment(commentId, e, false); + highlightComment(commentId, e); }; var hideComment = function(commentId, hideCommentTitle) { var commentElm = getCommentsContainer().find('#'+ commentId); - commentElm.removeClass('mouseover'); + commentElm.removeClass('full-display'); // hide even the comment title if (hideCommentTitle) commentElm.hide(); - getPadOuter().find('.comment-modal').hide(); -}; - -var hideOpenedComments = function() { - var openedComments = getCommentsContainer().find('.mouseover'); - openedComments.removeClass('mouseover').hide(); + var inner = $('iframe[name="ace_outer"]').contents().find('iframe[name="ace_inner"]'); + inner.contents().find("head .comment-style").remove(); - getPadOuter().find('.comment-modal').hide(); -} + getPadOuter().find('.comment-modal').removeClass('popup-show'); +}; var hideAllComments = function() { - getCommentsContainer().children().hide(); + getCommentsContainer().find('.sidebar-comment').removeClass('full-display'); + getPadOuter().find('.comment-modal').removeClass('popup-show'); } -var highlightComment = function(commentId, e, hideEditAndRemoveCommentWindow){ +var highlightComment = function(commentId, e, editorComment){ var container = getCommentsContainer(); var commentElm = container.find('#'+ commentId); - var commentsVisible = container.is(":visible"); - if(commentsVisible) { - // sidebar view highlight - commentElm.addClass('mouseover'); - } else { - // make a full copy of the html, including listeners - var commentElm = container.find('#'+ commentId).parent().clone(true, true); + var inner = $('iframe[name="ace_outer"]').contents().find('iframe[name="ace_inner"]'); - // clean styles - commentElm.children().removeAttr("style"); + if (container.is(":visible")) { + // hide all other comments + container.find('.sidebar-comment').each(function() { + inner.contents().find("head .comment-style").remove(); + $(this).removeClass('full-display') + }); - // only show the comment of the text selected - commentElm.find('note').not('#' + commentId).hide(); + // Then highlight new comment + commentElm.addClass('full-display'); + // now if we apply a class such as mouseover to the editor it will go shitty + // so what we need to do is add CSS for the specific ID to the document... + // It's fucked up but that's how we do it.. + var inner = $('iframe[name="ace_outer"]').contents().find('iframe[name="ace_inner"]'); + inner.contents().find("head").append(""); + } else { + // make a full copy of the html, including listeners + var commentElmCloned = commentElm.clone(true, true); - // before of appending it, we remove the classes that only makes sense on the side-bar - commentElm.children().attr('class', ''); + // before of appending clear the css (like top positionning) + commentElmCloned.attr('style', ''); + // fix checkbox, because as we are duplicating the sidebar-comment, we lose unique input names + commentElmCloned.find('.label-suggestion-checkbox').click(function() { + $(this).siblings('input[type="checkbox"]').click(); + }) // hovering comment view - getPadOuter().find('.comment-modal-comment').html(commentElm.html()); - - // if hideEditAndRemoveCommentWindow is true, it hides the comment edit/remove window - getPadOuter().find('.comment-options-wrapper').toggleClass('hidden', hideEditAndRemoveCommentWindow); - + getPadOuter().find('.comment-modal-comment').html('').append(commentElmCloned); + var padInner = getPadOuter().find('iframe[name="ace_inner"]') // get modal position var containerWidth = getPadOuter().find('#outerdocbody').outerWidth(true); var modalWitdh = getPadOuter().find('.comment-modal').outerWidth(true); var targetLeft = e.clientX; var targetTop = $(e.target).offset().top; + if (editorComment) { + targetTop += parseInt(padInner.css('padding-top').split('px')[0]) + targetTop += parseInt(padOuter.find('#outerdocbody').css('padding-top').split('px')[0]) + } else { + // mean we are clicking from a comment Icon + var targetLeft = $(e.target).offset().left - 20; + } + // if positioning modal on target left will make part of the modal to be // out of screen, we place it closer to the middle of the screen if (targetLeft + modalWitdh > containerWidth) { - targetLeft = containerWidth - modalWitdh - 2; + targetLeft = containerWidth - modalWitdh - 25; } - getPadOuter().find('.comment-modal').show().css({ - left: targetLeft +"px", - top: targetTop + 25 +"px" + var editorCommentHeight = editorComment ? editorComment.outerHeight(true) : 30; + getPadOuter().find('.comment-modal').addClass('popup-show').css({ + left: targetLeft + "px", + top: targetTop + editorCommentHeight +"px" }); } } @@ -86,8 +99,7 @@ var highlightComment = function(commentId, e, hideEditAndRemoveCommentWindow){ // height of the pad text associated to the comment, and return the affected element var adjustTopOf = function(commentId, baseTop) { var commentElement = getPadOuter().find('#'+commentId); - var targetTop = baseTop - 5; - commentElement.css("top", targetTop+"px"); + commentElement.css("top", baseTop+"px"); return commentElement; } @@ -95,7 +107,7 @@ var adjustTopOf = function(commentId, baseTop) { // Indicates if comment is on the expected position (baseTop-5) var isOnTop = function(commentId, baseTop) { var commentElement = getPadOuter().find('#'+commentId); - var expectedTop = (baseTop - 5) + "px"; + var expectedTop = baseTop + "px"; return commentElement.css("top") === expectedTop; } @@ -110,7 +122,6 @@ var shouldNotCloseComment = function(e) { exports.showComment = showComment; exports.hideComment = hideComment; -exports.hideOpenedComments = hideOpenedComments; exports.hideAllComments = hideAllComments; exports.highlightComment = highlightComment; exports.adjustTopOf = adjustTopOf; diff --git a/static/js/commentIcons.js b/static/js/commentIcons.js index c89c30de..184e9772 100644 --- a/static/js/commentIcons.js +++ b/static/js/commentIcons.js @@ -7,31 +7,6 @@ var displayIcons = function() { return clientVars.displayCommentAsIcon } -// Indicates if screen has enough space on right margin to display icons -var screenHasSpaceToDisplayIcons; -var screenHasSpaceForIcons = function() { - if (screenHasSpaceToDisplayIcons === undefined) calculateIfScreenHasSpaceForIcons(); - - return screenHasSpaceToDisplayIcons; -} - -var calculateIfScreenHasSpaceForIcons = function() { - var $firstElementOnPad = getPadInner().find("#innerdocbody > div").first(); - var availableSpaceOnTheRightOfPadLines = getSpaceAvailableOnTheRightSide($firstElementOnPad); - - screenHasSpaceToDisplayIcons = availableSpaceOnTheRightOfPadLines !== 0; -} - -// The space available can be anything like padding, margin or border -var getSpaceAvailableOnTheRightSide = function($element) { - var rightPadding = parseInt($element.css("padding-right"), 10); - var rightBorder = parseInt($element.css("border-right-width"), 10); - var rightMargin = parseInt($element.css("margin-right"), 10); - - var rightEdgeSpace = rightPadding + rightBorder + rightMargin; - return rightEdgeSpace; -} - // Easier access to outer pad var padOuter; var getPadOuter = function() { @@ -69,12 +44,11 @@ var targetCommentIdOf = function(e) { } var highlightTargetTextOf = function(commentId) { - getPadInner().find("head").append(""); + getPadInner().find("head").append(""); } -var removeHighlightOfTargetTextOf = function(commentId) { - getPadInner().find("head").append(""); - // TODO this could potentially break ep_font_color +var removeHighlightTargetText = function(commentId) { + getPadInner().find("head .comment-style").remove(); } var toggleActiveCommentIcon = function(target) { @@ -83,11 +57,12 @@ var toggleActiveCommentIcon = function(target) { var addListenersToCommentIcons = function() { getPadOuter().find('#commentIcons').on("mouseover", ".comment-icon", function(e){ + removeHighlightTargetText(); var commentId = targetCommentIdOf(e); highlightTargetTextOf(commentId); }).on("mouseout", ".comment-icon", function(e){ var commentId = targetCommentIdOf(e); - removeHighlightOfTargetTextOf(commentId); + removeHighlightTargetText(); }).on("click", ".comment-icon.active", function(e){ toggleActiveCommentIcon($(this)); @@ -96,27 +71,15 @@ var addListenersToCommentIcons = function() { }).on("click", ".comment-icon.inactive", function(e){ // deactivate/hide other comment boxes that are opened, so we have only // one comment box opened at a time - commentBoxes.hideOpenedComments(); + commentBoxes.hideAllComments(); var allActiveIcons = getPadOuter().find('#commentIcons').find(".comment-icon.active"); toggleActiveCommentIcon(allActiveIcons); // activate/show only target comment toggleActiveCommentIcon($(this)); var commentId = targetCommentIdOf(e); - commentBoxes.showComment(commentId, e, true); - }); -} - -// Listen to Page View enabling/disabling, to adjust #commentIcons position -var addListenersToPageView = function() { - $("#options-pageview").on("click", function() { - getPadOuter().find('#outerdocbody').toggleClass("pageViewDisabled"); + commentBoxes.highlightComment(commentId, e); }); - - // add class if Page View is disabled already - if(!$('#options-pageview').is(':checked')) { - getPadOuter().find('#outerdocbody').addClass("pageViewDisabled"); - } } // Listen to clicks on the page to be able to close comment when clicking @@ -165,11 +128,9 @@ var insertContainer = function() { if (!displayIcons()) return; getPadOuter().find("#sidediv").after('
'); - - adjustIconsForNewScreenSize(); + getPadOuter().find("#comments").addClass('with-icons'); addListenersToCommentIcons(); addListenersToCloseOpenedComment(); - addListenersToPageView(); } // Create a new comment icon @@ -178,7 +139,7 @@ var addIcon = function(commentId, comment){ if (!displayIcons()) return; var inlineComment = getPadInner().find(".comment."+commentId); - var top = inlineComment.get(0).offsetTop + 5; + var top = inlineComment.get(0).offsetTop; var iconsAtLine = getOrCreateIconsContainerAt(top); var icon = $('#commentIconTemplate').tmpl(comment); @@ -188,7 +149,7 @@ var addIcon = function(commentId, comment){ // Hide comment icons from container var hideIcons = function() { // we're only doing something if icons will be displayed at all - if (!displayIcons() || !screenHasSpaceForIcons()) return; + if (!displayIcons()) return; getPadOuter().find('#commentIcons').children().children().each(function(){ $(this).hide(); @@ -199,10 +160,10 @@ var hideIcons = function() { // height of the pad text associated to the comment, and return the affected icon var adjustTopOf = function(commentId, baseTop) { // we're only doing something if icons will be displayed at all - if (!displayIcons() || !screenHasSpaceForIcons()) return; + if (!displayIcons()) return; var icon = getPadOuter().find('#icon-'+commentId); - var targetTop = baseTop+5; + var targetTop = baseTop; var iconsAtLine = getOrCreateIconsContainerAt(targetTop); // move icon from one line to the other @@ -217,7 +178,7 @@ var adjustTopOf = function(commentId, baseTop) { // comment icon. var isCommentOpenedByClickOnIcon = function() { // we're only doing something if icons will be displayed at all - if (!displayIcons() || !screenHasSpaceForIcons()) return false; + if (!displayIcons()) return false; var iconClicked = getPadOuter().find('#commentIcons').find(".comment-icon.active"); var commentOpenedByClickOnIcon = iconClicked.length !== 0; @@ -241,7 +202,7 @@ var commentHasReply = function(commentId) { var shouldShow = function(sidebarComent) { var shouldShowComment = false; - if (!displayIcons() || !screenHasSpaceForIcons()) { + if (!displayIcons()) { // if icons are not being displayed, we always show comments shouldShowComment = true; } else if (sidebarComent.hasClass("mouseover")) { @@ -252,21 +213,6 @@ var shouldShow = function(sidebarComent) { return shouldShowComment; } -var adjustIconsForNewScreenSize = function() { - // we're only doing something if icons will be displayed at all - if (!displayIcons()) return; - - // now that screen has a different size, we need to force calculation - // of flag used by screenHasSpaceForIcons() before calling the function - calculateIfScreenHasSpaceForIcons(); - - if (screenHasSpaceForIcons()) { - getPadOuter().find('#commentIcons').show(); - } else { - getPadOuter().find('#commentIcons').hide(); - } -} - // Indicates if event was on one of the elements that does not close comment (any of the comment icons) var shouldNotCloseComment = function(e) { return $(e.target).closest('.comment-icon').length !== 0; @@ -279,5 +225,4 @@ exports.adjustTopOf = adjustTopOf; exports.isCommentOpenedByClickOnIcon = isCommentOpenedByClickOnIcon; exports.commentHasReply = commentHasReply; exports.shouldShow = shouldShow; -exports.adjustIconsForNewScreenSize = adjustIconsForNewScreenSize; exports.shouldNotCloseComment = shouldNotCloseComment; diff --git a/static/js/index.js b/static/js/index.js index 4f4f53f5..0270eb50 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -51,13 +51,6 @@ function ep_comments(context){ this.shouldCollectComment = false; this.init(); this.preCommentMarker = preCommentMark.init(this.ace); - - // If we're on a read only pad then hide the ability to attempt to merge a suggestion - if(clientVars.readonly){ - this.padInner.append( - ""); - } } // Init Etherpad plugin comment pads @@ -101,28 +94,16 @@ ep_comments.prototype.init = function(){ // all templates are localized html10n.bind('localized', function() { self.localizeExistingComments(); - newComment.localizeNewCommentForm(); }); - // When screen size changes (user changes device orientation, for example), - // we need to make sure all sidebar comments are on the correct place - newComment.waitForResizeToFinishThenCall(200, function() { - self.editorResized(); - }); - - // When Page View is enabled/disabled, we need to recalculate position of comments - $('#options-pageview').on('click', function(e) { - self.editorResized(); - }); - // When Page Breaks are enabled/disabled, we need to recalculate position of comments - $('#options-pagebreaks').on('click', function(e) { - self.editorResized(); + // Recalculate position when editor is resized + $('#settings input, #skin-variant-full-width').on('change', function(e) { + self.setYofComments(); }); - - // Allow recalculating the comments position by event this.padInner.contents().on(UPDATE_COMMENT_LINE_POSITION_EVENT, function(e){ - self.editorResized(); + self.setYofComments(); }); + $(window).resize(_.debounce( function() { self.setYofComments() }, 100 ) ); // On click comment icon toolbar $('.addComment').on('click', function(e){ @@ -130,38 +111,64 @@ ep_comments.prototype.init = function(){ self.displayNewCommentForm(); }); + // Import for below listener : we are using this.container.parent() so we include + // events on both comment-modal and sidebar + // Listen for events to delete a comment // All this does is remove the comment attr on the selection this.container.parent().on("click", ".comment-delete", function(){ - var commentId = $(this).closest('note')[0].id; + var commentId = $(this).closest('.comment-container')[0].id; self.deleteComment(commentId); + var padOuter = $('iframe[name="ace_outer"]').contents(); + var padInner = padOuter.find('iframe[name="ace_inner"]'); + var selector = "."+commentId; + var ace = self.ace; + ace.callWithAce(function(aceTop){ + var repArr = aceTop.ace_getRepFromSelector(selector, padInner); + // rep is an array of reps.. I will need to iterate over each to do something meaningful.. + $.each(repArr, function(index, rep){ + // I don't think we need this nested call + ace.callWithAce(function (ace){ + ace.ace_performSelectionChange(rep[0],rep[1],true); + ace.ace_setAttributeOnSelection('comment', 'comment-deleted'); + // Note that this is the correct way of doing it, instead of there being + // a commentId we now flag it as "comment-deleted" + }); + }); + },'deleteCommentedSelection', true); + // dispatch event + self.socket.emit('deleteComment', {padId: self.padId, commentId: commentId}, function (){}); }); // Listen for events to edit a comment // Here, it adds a form to edit the comment text this.container.parent().on("click", ".comment-edit", function(){ - var $commentBox = $(this).closest('note'); - - // hide the option window when it show the edit form - var $commentOptions = $commentBox.children('.comment-options'); // edit, delete actions - $commentOptions.addClass('hidden'); - - // hide the comment author name and the comment text - $commentBox.children('.comment-author-name, .comment-text').addClass('hidden'); - self.addCommentEditFormIfDontExist($commentBox); - - // place original text on the edit form - var originalText = $commentBox.children('.comment-text').text(); - $commentBox.find('.comment-edit-text').text(originalText); + var $commentBox = $(this).closest('.comment-container'); + $commentBox.addClass('editing'); + + var textBox = self.findCommentText($commentBox).last(); + + // if edit form not already there + if (textBox.siblings('.comment-edit-form').length == 0) { + // add a form to edit the field + var data = {}; + data.text = textBox.text(); + var content = $("#editCommentTemplate").tmpl(data); + // localize the comment/reply edit form + commentL10n.localize(content); + // insert form + textBox.before(content); + } }); // submit the edition on the text and update the comment text this.container.parent().on("click", ".comment-edit-submit", function(e){ e.preventDefault(); e.stopPropagation(); - var $commentBox = $(this).closest('note'); + var $commentBox = $(this).closest('.comment-container'); + var $commentForm = $(this).closest('.comment-edit-form'); var commentId = $commentBox.data('commentid'); - var commentText = $commentBox.find('.comment-edit-text')[0].value; + var commentText = $commentForm.find('.comment-edit-text').val(); var data = {}; data.commentId = commentId; data.padId = clientVars.padId; @@ -169,8 +176,8 @@ ep_comments.prototype.init = function(){ self.socket.emit('updateCommentText', data, function (err){ if(!err) { - $commentBox.children('.comment-edit-form').remove(); - $commentBox.children('.comment-author-name, .comment-text').removeClass('hidden'); + $commentForm.remove(); + $commentBox.removeClass('editing'); self.updateCommentBoxText(commentId, commentText); // although the comment or reply was saved on the data base successfully, it needs @@ -184,101 +191,54 @@ ep_comments.prototype.init = function(){ this.container.parent().on("click", ".comment-edit-cancel", function(e){ e.preventDefault(); e.stopPropagation(); - var $commentBox = $(this).closest('note'); - $commentBox.children('.comment-edit-form').remove(); - $commentBox.children('.comment-author-name, .comment-text').removeClass('hidden'); + var $commentBox = $(this).closest('.comment-container'); + var textBox = self.findCommentText($commentBox).last(); + textBox.siblings('.comment-edit-form').remove(); + $commentBox.removeClass('editing'); }); // Listen for include suggested change toggle - this.container.on("change", '.reply-suggestion-checkbox', function(){ + this.container.parent().on("change", '.suggestion-checkbox', function(){ + var parentComment = $(this).closest('.comment-container'); + var parentSuggest = $(this).closest('.comment-reply'); + if($(this).is(':checked')){ - var commentId = $(this).parent().parent().parent().data('commentid'); + var commentId = parentComment.data('commentid'); var padOuter = $('iframe[name="ace_outer"]').contents(); var padInner = padOuter.find('iframe[name="ace_inner"]'); var currentString = padInner.contents().find("."+commentId).html(); - $(this).parent().parent().find(".reply-comment-changeFrom-value").html(currentString); - $(this).parent().parent().find('.reply-suggestion').addClass("active"); - }else{ - $(this).parent().parent().find('.reply-suggestion').removeClass("active"); - } - }); - - - // Create hover modal - $('iframe[name="ace_outer"]').contents().find("body") - .append("

"); - - // DUPLICATE CODE REQUIRED FOR COMMENT REPLIES, see below for slightly different version - this.container.on("click", ".comment-reply-changeTo-approve > input", function(e){ - e.preventDefault(); - var data = {}; - data.commentId = $(this).parent().parent().parent().parent().parent()[0].id; - data.padId = clientVars.padId; - - data.replyId = $(this).parent().parent().parent()[0].id; - var padOuter = $('iframe[name="ace_outer"]').contents(); - var padInner = padOuter.find('iframe[name="ace_inner"]'); - // Are we reverting a change? - var submitButton = $(this); - var isRevert = submitButton.hasClass("revert"); - if(isRevert){ - var newString = $(this).parent().parent().parent().contents().find(".comment-changeFrom-value").html(); + parentSuggest.find(".from-value").html(currentString); + parentSuggest.find('.suggestion').show(); }else{ - var newString = $(this).parent().parent().parent().contents().find(".comment-changeTo-value").html(); + parentSuggest.find('.suggestion').hide(); } - - // Nuke all that aren't first lines of this comment - padInner.contents().find("."+data.commentId+":not(:first)").html(""); - var padCommentContent = padInner.contents().find("."+data.commentId).first(); - newString = newString.replace(/(?:\r\n|\r|\n)/g, '
'); - - // Write the new pad contents - $(padCommentContent).html(newString); - - // We change commentId to replyId in the data object so it's properly processed by the server.. This is hacky - data.commentId = data.replyId; - - if(isRevert){ - // Tell all users this change was reverted - self.socket.emit('revertChange', data, function (){}); - self.showChangeAsReverted(data.replyId); - }else{ - // Tell all users this change was accepted - self.socket.emit('acceptChange', data, function (){}); - - // Update our own comments container with the accepted change - self.showChangeAsAccepted(data.replyId); - } - }); - // User accepts a change - this.container.on("submit", ".comment-changeTo-form", function(e){ + // User accepts or revert a change + this.container.parent().on("submit", ".comment-changeTo-form", function(e){ e.preventDefault(); var data = self.getCommentData(); - data.commentId = $(this).parent().data('commentid'); + var commentEl = $(this).closest('.comment-container'); + data.commentId = commentEl.data('commentid'); var padOuter = $('iframe[name="ace_outer"]').contents(); - var padInner = padOuter.find('iframe[name="ace_inner"]'); + var padInner = padOuter.find('iframe[name="ace_inner"]').contents(); // Are we reverting a change? - var submitButton = $(this).contents().find("input[type='submit']"); - var isRevert = submitButton.hasClass("revert"); - if(isRevert){ - var newString = $(this).parent().contents().find(".comment-changeFrom-value").html(); - }else{ - var newString = $(this).parent().contents().find(".comment-changeTo-value").html(); - } + var isRevert = commentEl.hasClass("change-accepted"); + var newString = isRevert ? $(this).find(".from-value").html() : $(this).find(".to-value").html(); + // In case of suggested change is inside a reply, the parentId is different from the commentId (=replyId) + var parentId = $(this).closest('.sidebar-comment').data('commentid'); // Nuke all that aren't first lines of this comment - padInner.contents().find("."+data.commentId+":not(:first)").html(""); + padInner.find("."+parentId+":not(:first)").html(""); - var padCommentContent = padInner.contents().find("."+data.commentId).first(); + var padCommentSpan = padInner.find("."+parentId).first(); newString = newString.replace(/(?:\r\n|\r)/g, '
'); // Write the new pad contents - $(padCommentContent).html(newString); + padCommentSpan.html(newString); if(isRevert){ // Tell all users this change was reverted @@ -287,62 +247,90 @@ ep_comments.prototype.init = function(){ }else{ // Tell all users this change was accepted self.socket.emit('acceptChange', data, function (){}); - // Update our own comments container with the accepted change self.showChangeAsAccepted(data.commentId); } + + // TODO: we need ace editor to commit the change so other people get it + // currently after approving or reverting, you need to do other thing on the pad + // for ace to commit + }); + + // When input reply is focused we display more option + this.container.parent().on("focus", ".comment-content", function(e){ + $(this).closest('.new-comment').addClass('editing'); + }); + // When we leave we reset the form option to its minimal (only input) + this.container.parent().on('mouseleave', ".comment-container", function(e) { + $(this).find('.suggestion-checkbox').prop('checked', false); + $(this).find('.new-comment').removeClass('editing'); }); - // is this even used? - Yes, it is! - this.container.on("submit", ".comment-reply", function(e){ + // When a reply get submitted + this.container.parent().on("submit", ".new-comment", function(e){ e.preventDefault(); + var data = self.getCommentData(); - data.commentId = $(this).parent().data('commentid'); - data.reply = $(this).find(".comment-reply-input").val(); - data.changeTo = $(this).find(".reply-comment-suggest-to").val() || null; - data.changeFrom = $(this).find(".reply-comment-changeFrom-value").text() || null; + data.commentId = $(this).closest('.comment-container').data('commentid'); + data.reply = $(this).find(".comment-content").val(); + data.changeTo = $(this).find(".to-value").val() || null; + data.changeFrom = $(this).find(".from-value").text() || null; self.socket.emit('addCommentReply', data, function (){ - // Append the reply to the comment - // console.warn("addCommentReplyEmit WE EXPECT REPLY ID", data); - $('iframe[name="ace_outer"]').contents().find('#'+data.commentId + ' > form.comment-reply .comment-reply-input').val(""); self.getCommentReplies(function(replies){ self.commentReplies = replies; self.collectCommentReplies(); + + // Once the new reply is displayed, we clear the form + $('iframe[name="ace_outer"]').contents().find('.new-comment').removeClass('editing'); }); }); - // On submit we should hide this suggestion no? - if($(this).parent().parent().find(".reply-suggestion-checkbox").is(':checked')){ - $(this).parent().parent().find(".reply-suggestion-checkbox:checked").click(); - $(this).parent().parent().find(".reply-comment-suggest-to").val(""); - //Only uncheck checked boxes. TODO: is a cleanup operation. Should we do it here? - } + $(this).trigger('reset_reply'); + }); + this.container.parent().on("reset_reply", ".new-comment", function(e){ + // Reset the form + $(this).find('.comment-content').val(''); + $(this).find(':focus').blur(); + $(this).find('.to-value').val(''); + $(this).find('.suggestion-checkbox').prop('checked', false); + $(this).removeClass('editing'); }); + // When click cancel reply + this.container.parent().on("click", ".btn-cancel-reply", function(e) { + $(this).closest('.new-comment').trigger('reset_reply') + }); + // Enable and handle cookies if (padcookie.getPref("comments") === false) { - self.container.removeClass("active"); + self.padOuter.find('#comments, #commentIcons').removeClass("active"); $('#options-comments').attr('checked','unchecked'); $('#options-comments').attr('checked',false); - }else{ + } else { $('#options-comments').attr('checked','checked'); } - $('#options-comments').on('click', function() { - if($('#options-comments').is(':checked')) { - padcookie.setPref("comments", true); - self.container.addClass("active"); - } else { - padcookie.setPref("comments", false); - self.container.removeClass("active"); - } + $('#options-comments').on('change', function() { + $('#options-comments').is(':checked') ? enableComments() : disableComments(); }); - // Check to see if we should show already.. - if($('#options-comments').is(':checked')){ - self.container.addClass("active"); + function enableComments() { + padcookie.setPref("comments", true); + self.padOuter.find('#comments, #commentIcons').addClass("active"); + $('body').addClass('comments-active') + $('iframe[name="ace_outer"]').contents().find('body').addClass('comments-active') } + function disableComments() { + padcookie.setPref("comments", false); + self.padOuter.find('#comments, #commentIcons').removeClass("active"); + $('body').removeClass('comments-active') + $('iframe[name="ace_outer"]').contents().find('body').removeClass('comments-active') + } + + // Check to see if we should show already.. + $('#options-comments').trigger('change'); + // TODO - Implement to others browser like, Microsoft Edge, Opera, IE // Override copy, cut, paste events on Google chrome and Mozilla Firefox. // When an user copies a comment and selects only the span, or part of it, Google chrome @@ -366,6 +354,13 @@ ep_comments.prototype.init = function(){ } }; +ep_comments.prototype.findCommentText = function($commentBox) { + var isReply = $commentBox.hasClass('sidebar-comment-reply') + if (isReply) + return $commentBox.find(".comment-text"); + else + return $commentBox.find('.compact-display-content .comment-text, .full-display-content .comment-title-wrapper .comment-text'); +} // This function is useful to collect new comments on the collaborators ep_comments.prototype.collectCommentsAfterSomeIntervalsOfTime = function() { var self = this; @@ -403,25 +398,6 @@ ep_comments.prototype.collectCommentsAfterSomeIntervalsOfTime = function() { }, 300); } -ep_comments.prototype.addCommentEditFormIfDontExist = function ($commentBox) { - var hasEditForm = $commentBox.children(".comment-edit-form").length; - if (!hasEditForm) { - // get text from comment - var commentTextValue = $commentBox.find('.comment-text').text(); - - // add a form to edit the field - var data = {}; - data.text = commentTextValue; - var content = $("#editCommentTemplate").tmpl(data); - - // localize the comment/reply edit form - commentL10n.localize(content); - - // insert form - $commentBox.children(".comment-text").after(content); - } -} - // Insert comments container on element use for linenumbers ep_comments.prototype.findContainers = function(){ var padOuter = $('iframe[name="ace_outer"]').contents(); @@ -463,14 +439,6 @@ ep_comments.prototype.collectComments = function(callback){ // If comment is not in sidebar insert it if (commentElm.length == 0) { self.insertComment(commentId, comment.data, it); - commentElm = container.find('#'+ commentId); - - $(this).on('click', function(){ - markerTop = $(this).position().top; - commentTop = commentElm.position().top; - containerTop = container.css('top'); - container.css('top', containerTop - (commentTop - markerTop)); - }); } // localize comment element commentL10n.localize(commentElm); @@ -489,54 +457,45 @@ ep_comments.prototype.collectComments = function(callback){ } commentElm.css({ 'top': commentPos }); - - // Should we show "Revert" instead of "Accept" - // Comment Replies are NOT handled here.. - if(comments[commentId]){ - var showRevert = comments[commentId].data.changeAccepted; - } - - if(showRevert){ - self.showChangeAsAccepted(commentId); - } - }); - // now if we apply a class such as mouseover to the editor it will go shitty - // so what we need to do is add CSS for the specific ID to the document... - // It's fucked up but that's how we do it.. - var padInner = this.padInner; + + // HOVER SIDEBAR COMMENT + var hideCommentTimer; this.container.on("mouseover", ".sidebar-comment", function(e){ - var commentId = e.currentTarget.id; - var inner = $('iframe[name="ace_outer"]').contents().find('iframe[name="ace_inner"]'); - inner.contents().find("head").append(""); - // on hover we should show the reply option + // highlight comment + clearTimeout(hideCommentTimer); + commentBoxes.highlightComment(e.currentTarget.id, e); + }).on("mouseout", ".sidebar-comment", function(e){ - var commentId = e.currentTarget.id; - var inner = $('iframe[name="ace_outer"]').contents().find('iframe[name="ace_inner"]'); - inner.contents().find("head").append(""); - // TODO this could potentially break ep_font_color + // do not hide directly the comment, because sometime the mouse get out accidently + hideCommentTimer = setTimeout(function() { + commentBoxes.hideComment(e.currentTarget.id); + },1000); }); + // HOVER OR CLICK THE COMMENTED TEXT IN THE EDITOR // hover event this.padInner.contents().on("mouseover", ".comment", function(e){ - var commentId = self.commentIdOf(e); - var hideEditAndRemoveCommentWindow = true; - commentBoxes.highlightComment(commentId, e, hideEditAndRemoveCommentWindow); + if (container.is(':visible')) { // not on mobile + clearTimeout(hideCommentTimer); + var commentId = self.commentIdOf(e); + commentBoxes.highlightComment(commentId, e, $(this)); + } }); // click event this.padInner.contents().on("click", ".comment", function(e){ var commentId = self.commentIdOf(e); - var hideEditAndRemoveCommentWindow = true; - commentBoxes.highlightComment(commentId, e, hideEditAndRemoveCommentWindow); + commentBoxes.highlightComment(commentId, e, $(this)); }); this.padInner.contents().on("mouseleave", ".comment", function(e){ var commentOpenedByClickOnIcon = commentIcons.isCommentOpenedByClickOnIcon(); - // only closes comment if it was not opened by a click on the icon - if (!commentOpenedByClickOnIcon) { - self.closeOpenedComment(e); + if (!commentOpenedByClickOnIcon && container.is(':visible')) { + hideCommentTimer = setTimeout(function() { + self.closeOpenedComment(e); + }, 1000); } }); @@ -550,13 +509,13 @@ ep_comments.prototype.addListenersToCloseOpenedComment = function() { var self = this; // we need to add listeners to the different iframes of the page - $(document).on("touchstart", function(e){ + $(document).on("touchstart click", function(e){ self.closeOpenedCommentIfNotOnSelectedElements(e); }); - this.padOuter.find('html').on("touchstart", function(e){ + this.padOuter.find('html').on("touchstart click", function(e){ self.closeOpenedCommentIfNotOnSelectedElements(e); }); - this.padInner.contents().find('html').on("touchstart", function(e){ + this.padInner.contents().find('html').on("touchstart click", function(e){ self.closeOpenedCommentIfNotOnSelectedElements(e); }); } @@ -574,7 +533,6 @@ ep_comments.prototype.closeOpenedCommentIfNotOnSelectedElements = function(e) { || commentBoxes.shouldNotCloseComment(e)) { // a comment box or the comment modal return; } - // All clear, can close the comment this.closeOpenedComment(e); } @@ -585,28 +543,25 @@ ep_comments.prototype.collectCommentReplies = function(callback){ var container = this.container; var commentReplies = this.commentReplies; var padComment = this.padInner.contents().find('.comment'); + $.each(this.commentReplies, function(replyId, reply){ var commentId = reply.commentId; // tell comment icon that this comment has 1+ replies commentIcons.commentHasReply(commentId); var existsAlready = $('iframe[name="ace_outer"]').contents().find('#'+replyId).length; - if(existsAlready){ - return; - } + if(existsAlready) return; reply.replyId = replyId; + reply.text = reply.text || "" + reply.date = prettyDate(reply.timestamp); reply.formattedDate = new Date(reply.timestamp).toISOString(); var content = $("#replyTemplate").tmpl(reply); // localize comment reply commentL10n.localize(content); - $('iframe[name="ace_outer"]').contents().find('#'+commentId + ' .comment-reply-input-label').before(content); - // Should we show "Revert" instead of "Accept" - // Comment Replies ARE handled here.. - if(reply.changeAccepted){ - self.showChangeAsAccepted(replyId); - } + var repliesContainer = $('iframe[name="ace_outer"]').contents().find('#'+commentId + ' .comment-replies-container'); + repliesContainer.append(content); }); }; @@ -621,12 +576,13 @@ ep_comments.prototype.commentIdOf = function(e){ ep_comments.prototype.insertContainers = function(){ var target = $('iframe[name="ace_outer"]').contents().find("#outerdocbody"); - // Add comments + // Create hover modal + target.prepend(""); + + // Add comments side bar container target.prepend('
'); - this.container = this.padOuter.find('#comments'); - // Add newComments - newComment.insertContainers(target); + this.container = this.padOuter.find('#comments'); }; // Insert a comment node @@ -636,6 +592,7 @@ ep_comments.prototype.insertComment = function(commentId, comment, index){ var commentAfterIndex = container.find('.sidebar-comment').eq(index); comment.commentId = commentId; + comment.reply = true; content = $('#commentsTemplate').tmpl(comment); commentL10n.localize(content); @@ -669,13 +626,19 @@ ep_comments.prototype.setYofComments = function(){ commentIcons.hideIcons(); $.each(inlineComments, function(){ - var y = this.offsetTop; var commentId = /(?:^| )(c-[A-Za-z0-9]*)/.exec(this.className); // classname is the ID of the comment + var commentEle = padOuter.find('#'+commentId[1]) + + var topOffset = this.offsetTop; + topOffset += parseInt(padInner.css('padding-top').split('px')[0]) + + // topOffset += ($(this).height() + $(this).outerHeight(true)) / 2 - commentEle.height(); + if(commentId) { // adjust outer comment... - var commentEle = commentBoxes.adjustTopOf(commentId[1], y); + commentBoxes.adjustTopOf(commentId[1], topOffset); // ... and adjust icons too - commentIcons.adjustTopOf(commentId[1], y); + commentIcons.adjustTopOf(commentId[1], topOffset); // mark this comment to be displayed if it was visible before we start adjusting its position if (commentIcons.shouldShow(commentEle)) commentsToBeShown.push(commentEle); @@ -708,31 +671,6 @@ ep_comments.prototype.getUniqueCommentsId = function(padInner){ return _.uniq(commentsId); } -// Make the adjustments after editor is resized (due to a window resize or -// enabling/disabling Page View) -ep_comments.prototype.editorResized = function() { - var self = this; - - commentIcons.adjustIconsForNewScreenSize(); - - // We try increasing timeouts, to make sure user gets the response as fast as we can - setTimeout(function() { - if (!self.allCommentsOnCorrectYPosition()) self.adjustCommentPositions(); - setTimeout(function() { - if (!self.allCommentsOnCorrectYPosition()) self.adjustCommentPositions(); - setTimeout(function() { - if (!self.allCommentsOnCorrectYPosition()) self.adjustCommentPositions(); - }, 1000); - }, 500); - }, 250); -} - -// Adjusts position on the screen for sidebar comments and comment icons -ep_comments.prototype.adjustCommentPositions = function(){ - commentIcons.adjustIconsForNewScreenSize(); - this.setYofComments(); -} - // Indicates if all comments are on the correct Y position, and don't need to // be adjusted ep_comments.prototype.allCommentsOnCorrectYPosition = function(){ @@ -776,7 +714,6 @@ ep_comments.prototype.localizeExistingComments = function() { // ... and update its date comment.data.date = prettyDate(comment.data.timestamp); comment.data.formattedDate = new Date(comment.data.timestamp).toISOString(); - commentElm.attr('title', comment.data.date); } }); }; @@ -854,26 +791,7 @@ ep_comments.prototype.getCommentData = function (){ // Delete a pad comment ep_comments.prototype.deleteComment = function(commentId){ - var padOuter = $('iframe[name="ace_outer"]').contents(); - var padInner = padOuter.find('iframe[name="ace_inner"]'); - var selector = "."+commentId; - var ace = this.ace; - ace.callWithAce(function(aceTop){ - var repArr = aceTop.ace_getRepFromSelector(selector, padInner); - // rep is an array of reps.. I will need to iterate over each to do something meaningful.. - $.each(repArr, function(index, rep){ - // I don't think we need this nested call - ace.callWithAce(function (ace){ - ace.ace_performSelectionChange(rep[0],rep[1],true); - ace.ace_setAttributeOnSelection('comment', 'comment-deleted'); - // Note that this is the correct way of doing it, instead of there being - // a commentId we now flag it as "comment-deleted" - }); - }); - },'deleteCommentedSelection', true); - -// }); -// }, 'getRep'); + $('iframe[name="ace_outer"]').contents().find('#' + commentId).remove(); } ep_comments.prototype.displayNewCommentForm = function() { @@ -893,17 +811,17 @@ ep_comments.prototype.displayNewCommentForm = function() { // we have nothing selected, do nothing var noTextSelected = (selectedText.length === 0); if (noTextSelected) { + $.gritter.add({text: html10n.translations["ep_comments_page.add_comment.hint"] || "Please first select the text to comment"}) return; } self.createNewCommentFormIfDontExist(rep); // Write the text to the changeFrom form - var padOuter = $('iframe[name="ace_outer"]').contents(); - padOuter.find(".comment-suggest-from").val(selectedText); + $('#newComment').find(".from-value").text(selectedText); // Display form - newComment.showNewCommentForm(); + newComment.showNewCommentPopup(); // Check if the first element selected is visible in the viewport var $firstSelectedElement = self.getFirstElementSelected(); @@ -914,15 +832,7 @@ ep_comments.prototype.displayNewCommentForm = function() { } // Adjust focus on the form - padOuter.find('.comment-content').focus(); - - // fix for iOS: when opening #newComment, we need to force focus on padOuter - // contentWindow, otherwise keyboard will be displayed but text input made by - // the user won't be added to textarea - var outerIframe = $('iframe[name="ace_outer"]').get(0); - if (outerIframe && outerIframe.contentWindow) { - outerIframe.contentWindow.focus(); - } + $('#newComment').find('.comment-content').focus(); } ep_comments.prototype.scrollViewportIfSelectedTextIsNotVisible = function($firstSelectedElement){ @@ -987,7 +897,7 @@ ep_comments.prototype.createNewCommentFormIfDontExist = function(rep) { var self = this; // If a new comment box doesn't already exist, create one - newComment.insertNewCommentFormIfDontExist(data, function(comment, index) { + newComment.insertNewCommentPopupIfDontExist(data, function(comment, index) { if(comment.changeTo){ data.comment.changeFrom = comment.changeFrom; data.comment.changeTo = comment.changeTo; @@ -1086,7 +996,7 @@ ep_comments.prototype.saveComment = function(data, rep) { comment.commentId = commentId; self.ace.callWithAce(function (ace){ - // console.log('addComment :: ', commentId); + // console.log('addComment :: ', rep, comment); ace.ace_performSelectionChange(rep.selStart, rep.selEnd, true); ace.ace_setAttributeOnSelection('comment', commentId); },'insertComment', true); @@ -1210,29 +1120,33 @@ ep_comments.prototype.commentRepliesListen = function(){ ep_comments.prototype.updateCommentBoxText = function (commentId, commentText) { var $comment = this.container.parent().find("[data-commentid='" + commentId + "']"); - $comment.children('.comment-text').text(commentText) + var textBox = this.findCommentText($comment); + textBox.text(commentText) } ep_comments.prototype.showChangeAsAccepted = function(commentId){ var self = this; // Get the comment - var comment = self.container.find("#"+commentId); - var button = comment.find("input[type='submit']").first(); // we need to get the first button otherwise the replies suggestions will be affected too - button.attr("data-l10n-id", "ep_comments_page.comments_template.revert_change.value"); - button.addClass("revert"); - commentL10n.localize(button); + var comment = this.container.parent().find("[data-commentid='" + commentId + "']"); + // Revert other comment that have already been accepted + comment.closest('.sidebar-comment') + .find('.comment-container.change-accepted').addBack('.change-accepted') + .each(function() { + $(this).removeClass('change-accepted'); + var data = {commentId: $(this).attr('data-commentid'), padId: self.padId} + self.socket.emit('revertChange', data, function (){}); + }) + + // this comment get accepted + comment.addClass('change-accepted'); } ep_comments.prototype.showChangeAsReverted = function(commentId){ var self = this; - // Get the comment - var comment = self.container.find("#"+commentId); - var button = comment.find("input[type='submit']").first(); // we need to get the first button otherwise the replies suggestions will be affected too - button.attr("data-l10n-id", "ep_comments_page.comments_template.accept_change.value"); - button.removeClass("revert"); - commentL10n.localize(button); + var comment = self.container.parent().find("[data-commentid='" + commentId + "']"); + comment.removeClass('change-accepted'); } // Push comment from collaborators @@ -1244,6 +1158,10 @@ ep_comments.prototype.pushComment = function(eventType, callback){ self.updateCommentBoxText(commentId, commentText); }) + socket.on('commentDeleted', function(commentId){ + self.deleteComment(commentId); + }); + socket.on('changeAccepted', function(commentId){ self.showChangeAsAccepted(commentId); }); @@ -1259,13 +1177,6 @@ ep_comments.prototype.pushComment = function(eventType, callback){ }); } - // On collaborator delete a comment in the current pad - else if (eventType == 'remove'){ - socket.on('pushRemoveComment', function (commentId){ - callback(commentId); - }); - } - // On reply else if (eventType == "addCommentReply"){ socket.on('pushAddCommentReply', function (replyId, reply){ @@ -1285,6 +1196,15 @@ var hooks = { if(!pad.plugins) pad.plugins = {}; var Comments = new ep_comments(context); pad.plugins.ep_comments_page = Comments; + + if (!$('#editorcontainerbox').hasClass('flex-layout')) { + $.gritter.add({ + title: "Error", + text: "Ep_comments_page: Please upgrade to etherpad 1.8.3 for this plugin to work correctly", + sticky: true, + class_name: "error" + }) + } }, aceEditEvent: function(hook, context){ diff --git a/static/js/newComment.js b/static/js/newComment.js index eecac0ff..6474b5f8 100644 --- a/static/js/newComment.js +++ b/static/js/newComment.js @@ -1,36 +1,11 @@ var $ = require('ep_etherpad-lite/static/js/rjquery').$; var commentL10n = require('ep_comments_page/static/js/commentL10n'); -// Easier access to outer pad -var padOuter; -var getPadOuter = function() { - padOuter = padOuter || $('iframe[name="ace_outer"]').contents(); - return padOuter; -} - -// Easier access to new comment container -var newCommentContainer; -var getNewCommentContainer = function() { - newCommentContainer = newCommentContainer || getPadOuter().find('#newComments'); - return newCommentContainer; -} - -// Insert a comment node -var createNewCommentForm = function(comment) { - var container = getNewCommentContainer(); - - comment.commentId = ""; - var content = $('#newCommentTemplate').tmpl(comment); - content.prependTo(container); - - return content; -}; - // Create a comment object with data filled on the given form var buildCommentFrom = function(form) { var text = form.find('.comment-content').val(); - var changeFrom = form.find('.comment-suggest-from').val(); - var changeTo = form.find('.comment-suggest-to').val() || null; + var changeFrom = form.find('.from-value').text(); + var changeTo = form.find('.to-value').val() || null; var comment = {}; comment.text = text; @@ -44,157 +19,89 @@ var buildCommentFrom = function(form) { // Callback for new comment Cancel var cancelNewComment = function(){ - hideNewCommentForm(); + hideNewCommentPopup(); } // Callback for new comment Submit -var submitNewComment = function(form, callback) { +var submitNewComment = function(callback) { var index = 0; - var text = form.find('.comment-content').val(); - var commentTextIsNotEmpty = text.length !== 0; + var form = $('#newComment'); var comment = buildCommentFrom(form); - if (commentTextIsNotEmpty) { - hideNewCommentForm(); + if (comment.text.length > 0 || comment.changeTo && comment.changeTo.length > 0) { + form.find('.comment-content, .to-value').removeClass('error'); + hideNewCommentPopup(); callback(comment, index); - } - return false; -} - -var fixFlyingToobarOnIOS = function() { - if (browser.ios) { - var shouldPlaceMenuRightOnBottom = $(".toolbar ul.menu_right").css('bottom') !== "auto"; - - getNewCommentContainer().find('input, textarea') - .on("focus", function() { - fixToolbarPosition(); - if (shouldPlaceMenuRightOnBottom) placeMenuRightOnBottom(); - }) - .on("blur", function() { - revertFixToToolbarPosition(); - if (shouldPlaceMenuRightOnBottom) revertPlacingMenuRightOnBottom(); - }); - - // When user changes orientation, we need to re-position menu_right - if (shouldPlaceMenuRightOnBottom) { - waitForResizeToFinishThenCall(500, function() { - var needToUpdateTop = $(".toolbar ul.menu_right").css("top") !== ""; - if (needToUpdateTop) placeMenuRightOnBottom(); - }); - } + } else { + if (comment.text.length == 0) form.find('.comment-content').addClass('error'); + if (comment.changeTo && comment.changeTo.length == 0) form.find('.to-value').addClass('error'); } -} - -var fixToolbarPosition = function() { - $(".toolbar ul.menu_left, .toolbar ul.menu_right").css("position", "absolute"); -} -var revertFixToToolbarPosition = function() { - $(".toolbar ul.menu_left, .toolbar ul.menu_right").css("position", ""); -} - -var placeMenuRightOnBottom = function() { - $(".toolbar ul.menu_right").css("top", $(document).outerHeight()); -} -var revertPlacingMenuRightOnBottom = function() { - $(".toolbar ul.menu_right").css("top", ""); + return false; } /* ***** Public methods: ***** */ -var localizeNewCommentForm = function() { - var newCommentForm = getNewCommentContainer().find('#newComment'); - if (newCommentForm.length !== 0) commentL10n.localize(newCommentForm); +var localizenewCommentPopup = function() { + var newCommentPopup = $('#newComment'); + if (newCommentPopup.length !== 0) commentL10n.localize(newCommentPopup); }; -// Create container to hold new comment form -var insertContainers = function(target) { - target.prepend('
'); - - // Listen for include suggested change toggle - getNewCommentContainer().on("change", '#suggestion-checkbox', function() { - if($(this).is(':checked')) { - getPadOuter().find('.suggestion').show(); - } else { - getPadOuter().find('.suggestion').hide(); - } - }); -} - // Insert new Comment Form -var insertNewCommentFormIfDontExist = function(comment, callback) { - var newCommentForm = getNewCommentContainer().find('#newComment'); - var formDoesNotExist = newCommentForm.length === 0; - if (formDoesNotExist) { - newCommentForm = createNewCommentForm(comment); - localizeNewCommentForm(); +var insertNewCommentPopupIfDontExist = function(comment, callback) { + $('#newComment').remove(); + var newCommentPopup = $('#newComment'); - // Listen to cancel - newCommentForm.find('#comment-reset').on('click', function() { - cancelNewComment(); - }); + comment.commentId = ""; + var newCommentPopup = $('#newCommentTemplate').tmpl(comment); + newCommentPopup.appendTo($('#editorcontainerbox')); - // Hack to avoid "flying" toolbars on iOS - fixFlyingToobarOnIOS(); + localizenewCommentPopup(); - } else { - // Reset form to make sure it is all clear - newCommentForm.get(0).reset(); - - // Detach current "submit" handler to be able to call the updated callback - newCommentForm.off("submit"); - } + // Listen for include suggested change toggle + $('#newComment').find('.suggestion-checkbox').change(function() { + $('#newComment').find('.suggestion').toggle($(this).is(':checked')); + }); - // Listen to comment confirmation (needs to be outside of if/else to be able to update the callback) - newCommentForm.submit(function() { - var form = $(this); - return submitNewComment(form, callback); + // Cancel btn + newCommentPopup.find('#comment-reset').on('click', function() { + cancelNewComment(); + }); + // Create btn + $('#newComment').on("submit", function(e) { + e.preventDefault(); + return submitNewComment(callback); }); - return newCommentForm; + return newCommentPopup; }; -var showNewCommentForm = function() { - getNewCommentContainer().addClass("active"); - // we need to set a timeout otherwise the animation to show #newComment won't be visible - window.setTimeout(function() { - getPadOuter().find('.suggestion').hide(); // Hides suggestion in case of a cancel - getNewCommentContainer().find('#newComment').removeClass("hidden").addClass("visible"); - }, 0); +var showNewCommentPopup = function() { + // position below comment icon + $('#newComment').css('left', $('.toolbar .addComment').offset().left) + + // Reset form to make sure it is all clear + $('#newComment').find('.suggestion-checkbox').prop('checked', false).trigger('change'); + $('#newComment').find('textarea').val(""); + $('#newComment').find('.comment-content, .to-value').removeClass('error'); + + // Show popup + $('#newComment').addClass("popup-show"); // mark selected text, so it is clear to user which text range the comment is being applied to pad.plugins.ep_comments_page.preCommentMarker.markSelectedText(); } -var hideNewCommentForm = function() { - getNewCommentContainer().find('#newComment').removeClass("visible").addClass("hidden"); +var hideNewCommentPopup = function() { + $('#newComment').removeClass("popup-show"); // force focus to be lost, so virtual keyboard is hidden on mobile devices - getNewCommentContainer().find(':focus').blur(); - - // we need to give some time for the animation of #newComment to finish - window.setTimeout(function() { - getNewCommentContainer().removeClass("active"); - }, 500); + $('#newComment').find(':focus').blur(); // unmark selected text, as now there is no text being commented pad.plugins.ep_comments_page.preCommentMarker.unmarkSelectedText(); } -// Some browsers trigger resize several times while resizing the window, so -// we need to make sure resize is done to avoid calling the callback multiple -// times. -// Based on: https://css-tricks.com/snippets/jquery/done-resizing-event/ -var waitForResizeToFinishThenCall = function(timeout, callback){ - var resizeTimer; - $(window).on("resize", function() { - clearTimeout(resizeTimer); - resizeTimer = setTimeout(callback, timeout); - }); -} - -exports.localizeNewCommentForm = localizeNewCommentForm; -exports.insertNewCommentFormIfDontExist = insertNewCommentFormIfDontExist; -exports.showNewCommentForm = showNewCommentForm; -exports.hideNewCommentForm = hideNewCommentForm; -exports.insertContainers = insertContainers; -exports.waitForResizeToFinishThenCall = waitForResizeToFinishThenCall; +exports.localizenewCommentPopup = localizenewCommentPopup; +exports.insertNewCommentPopupIfDontExist = insertNewCommentPopupIfDontExist; +exports.showNewCommentPopup = showNewCommentPopup; +exports.hideNewCommentPopup = hideNewCommentPopup; diff --git a/static/tests/frontend/specs/commentCopyPaste.js b/static/tests/frontend/specs/commentCopyPaste.js index bbbed241..5ef61697 100644 --- a/static/tests/frontend/specs/commentCopyPaste.js +++ b/static/tests/frontend/specs/commentCopyPaste.js @@ -207,7 +207,7 @@ ep_comments_page_test_helper.copyAndPaste = { } // fill reply field - var $replyField = outer$(".comment-reply-input"); + var $replyField = outer$(".comment-content"); $replyField.val(textOfReply); // submit reply diff --git a/static/tests/frontend/specs/commentDelete.js b/static/tests/frontend/specs/commentDelete.js index ad0086e5..0c1956a7 100644 --- a/static/tests/frontend/specs/commentDelete.js +++ b/static/tests/frontend/specs/commentDelete.js @@ -43,9 +43,9 @@ function createComment(callback) { // fill the comment form and submit it var $commentField = outer$("textarea.comment-content"); $commentField.val("My comment"); - var $hasSuggestion = outer$("#suggestion-checkbox"); + var $hasSuggestion = outer$(".suggestion-checkbox"); $hasSuggestion.click(); - var $suggestionField = outer$("textarea.comment-suggest-to"); + var $suggestionField = outer$("textarea.to-value"); $suggestionField.val("Change to this suggestion"); var $submittButton = outer$("input[type=submit]"); $submittButton.click(); diff --git a/static/tests/frontend/specs/commentEdit.js b/static/tests/frontend/specs/commentEdit.js index 0184f26b..05015864 100644 --- a/static/tests/frontend/specs/commentEdit.js +++ b/static/tests/frontend/specs/commentEdit.js @@ -240,7 +240,7 @@ ep_comments_page_test_helper.commentEdit = { } // fill reply field - var $replyField = outer$(".comment-reply-input"); + var $replyField = outer$(".comment-content"); $replyField.val(textOfReply); // submit reply diff --git a/static/tests/frontend/specs/commentIcons.js b/static/tests/frontend/specs/commentIcons.js index d249225e..0efa2d51 100644 --- a/static/tests/frontend/specs/commentIcons.js +++ b/static/tests/frontend/specs/commentIcons.js @@ -246,9 +246,9 @@ describe("ep_comments_page - Comment icons", function() { var $commentField = outer$("textarea.comment-content"); $commentField.val(commentText); // we don't need comment suggestion to be filled for these tests, but here's how to do it: - // var $hasSuggestion = outer$("#suggestion-checkbox"); + // var $hasSuggestion = outer$(".suggestion-checkbox"); // $hasSuggestion.click(); - // var $suggestionField = outer$("textarea.comment-suggest-to"); + // var $suggestionField = outer$("textarea.to-value"); // $suggestionField.val("Change to this suggestion"); var $submittButton = outer$("input[type=submit]"); $submittButton.click(); diff --git a/static/tests/frontend/specs/commentReply.js b/static/tests/frontend/specs/commentReply.js index 91f3ee2b..c9a972ec 100644 --- a/static/tests/frontend/specs/commentReply.js +++ b/static/tests/frontend/specs/commentReply.js @@ -38,9 +38,9 @@ describe("ep_comments_page - Comment Reply", function(){ createReply(true, function(){ var outer$ = helper.padOuter$; var $replyForm = outer$("form.comment-reply"); - var $replyField = $replyForm.find(".comment-reply-input"); + var $replyField = $replyForm.find(".comment-content"); var $replyWithSuggestionCheckbox = $replyForm.find(".reply-suggestion-checkbox"); - var $replySuggestionTextarea = $replyForm.find(".reply-comment-suggest-to"); + var $replySuggestionTextarea = $replyForm.find(".reply-to-value"); expect($replyField.text()).to.be(""); expect($replyWithSuggestionCheckbox.is(":checked")).to.be(false); expect($replySuggestionTextarea.text()).to.be(""); @@ -107,9 +107,9 @@ describe("ep_comments_page - Comment Reply", function(){ // fill the comment form and submit it var $commentField = outer$("textarea.comment-content"); $commentField.val("My comment"); - var $hasSuggestion = outer$("#suggestion-checkbox"); + var $hasSuggestion = outer$(".suggestion-checkbox"); $hasSuggestion.click(); - var $suggestionField = outer$("textarea.comment-suggest-to"); + var $suggestionField = outer$("textarea.to-value"); $suggestionField.val("Change to this suggestion"); var $submittButton = outer$("input[type=submit]"); $submittButton.click(); @@ -134,7 +134,7 @@ describe("ep_comments_page - Comment Reply", function(){ } // fill reply field - var $replyField = outer$(".comment-reply-input"); + var $replyField = outer$(".comment-content"); $replyField.val("My reply"); // fill suggestion @@ -144,7 +144,7 @@ describe("ep_comments_page - Comment Reply", function(){ $replySuggestionCheckbox.click(); // fill suggestion field - var $suggestionField = outer$("textarea.reply-comment-suggest-to"); + var $suggestionField = outer$("textarea.reply-to-value"); $suggestionField.val("My suggestion"); } diff --git a/static/tests/frontend/specs/commentSuggestion.js b/static/tests/frontend/specs/commentSuggestion.js index 57db6b20..ff8b5846 100644 --- a/static/tests/frontend/specs/commentSuggestion.js +++ b/static/tests/frontend/specs/commentSuggestion.js @@ -15,7 +15,7 @@ describe("ep_comments_page - Comment Suggestion", function(){ openCommentFormWithSuggestion(targetText); - var $suggestionFrom = outer$(".comment-suggest-from"); + var $suggestionFrom = outer$(".from-value"); expect($suggestionFrom.val()).to.be("A\n text with\n line attributes"); done(); }); @@ -37,7 +37,7 @@ describe("ep_comments_page - Comment Suggestion", function(){ .done(function() { openCommentFormWithSuggestion('New target for comment'); - var $suggestionFrom = outer$(".comment-suggest-from"); + var $suggestionFrom = outer$(".from-value"); expect($suggestionFrom.val()).to.be('New target for comment'); done(); }); @@ -64,7 +64,7 @@ function openCommentFormWithSuggestion(targetText) { $commentButton.click(); // check suggestion box - var $hasSuggestion = outer$("#suggestion-checkbox"); + var $hasSuggestion = outer$(".suggestion-checkbox"); $hasSuggestion.click(); } diff --git a/static/tests/frontend/specs/comment_l10n.js b/static/tests/frontend/specs/comment_l10n.js index f9641a94..1fb64134 100644 --- a/static/tests/frontend/specs/comment_l10n.js +++ b/static/tests/frontend/specs/comment_l10n.js @@ -22,7 +22,7 @@ describe("ep_comments_page - Comment Localization", function(){ var commentId = getCommentId(); //get the title of the comment - var $changeToLabel = outer$(".comment-changeTo-label").first(); + var $changeToLabel = outer$(".to-label").first(); expect($changeToLabel.text()).to.be("Suggested Change:"); done(); @@ -36,7 +36,7 @@ describe("ep_comments_page - Comment Localization", function(){ var commentId = getCommentId(); //get the 'Suggested Change' label - var $changeToLabel = outer$("#" + commentId + " .comment-changeTo-label").first(); + var $changeToLabel = outer$("#" + commentId + " .to-label").first(); expect($changeToLabel.text()).to.be("Alteração Sugerida:"); done(); @@ -59,7 +59,7 @@ describe("ep_comments_page - Comment Localization", function(){ changeEtherpadLanguageTo('pt-br', function(){ //get the 'Include suggested change' label - var $changeToLabel = outer$('#newComment label[for=suggestion-checkbox]').first(); + var $changeToLabel = outer$('#newComment label.label-suggestion-checkbox').first(); expect($changeToLabel.text()).to.be("Incluir alteração sugerida"); done(); @@ -89,9 +89,9 @@ describe("ep_comments_page - Comment Localization", function(){ // fill the comment form and submit it var $commentField = outer$("textarea.comment-content"); $commentField.val("My comment"); - var $hasSuggestion = outer$("#suggestion-checkbox"); + var $hasSuggestion = outer$(".suggestion-checkbox"); $hasSuggestion.click(); - var $suggestionField = outer$("textarea.comment-suggest-to"); + var $suggestionField = outer$("textarea.to-value"); $suggestionField.val("Change to this suggestion"); var $submittButton = outer$("input[type=submit]"); $submittButton.click(); diff --git a/static/tests/frontend/specs/comment_settings.js b/static/tests/frontend/specs/comment_settings.js index ab0cfdf2..803204f6 100644 --- a/static/tests/frontend/specs/comment_settings.js +++ b/static/tests/frontend/specs/comment_settings.js @@ -82,9 +82,9 @@ describe("ep_comments_page - Comment settings", function() { // fill the comment form and submit it var $commentField = outer$("textarea.comment-content"); $commentField.val("My comment"); - var $hasSuggestion = outer$("#suggestion-checkbox"); + var $hasSuggestion = outer$(".suggestion-checkbox"); $hasSuggestion.click(); - var $suggestionField = outer$("textarea.comment-suggest-to"); + var $suggestionField = outer$("textarea.to-value"); $suggestionField.val("Change to this suggestion"); var $submittButton = outer$("input[type=submit]"); $submittButton.click(); diff --git a/static/tests/frontend/specs/preCommentMark.js b/static/tests/frontend/specs/preCommentMark.js index f8b248e8..a0a1dd43 100644 --- a/static/tests/frontend/specs/preCommentMark.js +++ b/static/tests/frontend/specs/preCommentMark.js @@ -131,9 +131,9 @@ describe("ep_comments_page - Pre-comment text mark", function() { // fill the comment form and submit it var $commentField = outer$("textarea.comment-content"); $commentField.val("My comment"); - var $hasSuggestion = outer$("#suggestion-checkbox"); + var $hasSuggestion = outer$(".suggestion-checkbox"); $hasSuggestion.click(); - var $suggestionField = outer$("textarea.comment-suggest-to"); + var $suggestionField = outer$("textarea.to-value"); $suggestionField.val("Change to this suggestion"); var $submittButton = outer$("input[type=submit]"); $submittButton.click(); diff --git a/templates/commentBarButtons.ejs b/templates/commentBarButtons.ejs index bdc2067f..15d8d464 100644 --- a/templates/commentBarButtons.ejs +++ b/templates/commentBarButtons.ejs @@ -1,4 +1,6 @@
  • - + + +
  • diff --git a/templates/comments.html b/templates/comments.html index 718c3897..25d30897 100644 --- a/templates/comments.html +++ b/templates/comments.html @@ -1,116 +1,139 @@ - - - + + + + + + + + + + diff --git a/templates/styles.html b/templates/styles.html index 8d3e7815..11bb5484 100644 --- a/templates/styles.html +++ b/templates/styles.html @@ -1 +1,2 @@ + \ No newline at end of file