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