From fadfc43aa18dcbc9ece59614a63c9804f91fb7ad Mon Sep 17 00:00:00 2001 From: Alexey Skobkin Date: Wed, 14 Jan 2015 13:34:04 +0400 Subject: [PATCH 01/10] Comment DOM generation moved to separate function. Some bugs fixed. Some optimizations done. --- chrome_point_plus/js/point-plus.js | 215 ++++++++++++++++------------- 1 file changed, 118 insertions(+), 97 deletions(-) diff --git a/chrome_point_plus/js/point-plus.js b/chrome_point_plus/js/point-plus.js index d0be6df..2464831 100644 --- a/chrome_point_plus/js/point-plus.js +++ b/chrome_point_plus/js/point-plus.js @@ -432,110 +432,44 @@ $(document).ready(function() { // Check we are in specified post if (wsMessage.post_id != postId) { - console.log('The comment is for #%s but current page is for #%s', wsMessage.post_id, postId); + console.log('The comment is not for this post'); console.groupEnd(); break; } - - var $anchor = $('').attr('name', wsMessage.comment_id); - - // Initializing comment element - var $commentTemplate = $('
').attr({ - 'class': 'post', - 'data-id': postId, - 'data-comment-id': wsMessage.comment_id, - 'data-to-comment-id': (wsMessage.to_comment_id != null) ? wsMessage.to_comment_id : '' - }); - - // @todo: Вынести в отдельную функцию - // Loading HTML template - $commentTemplate.load(chrome.extension.getURL('includes/comment.html'), function() { - // Load complete - console.info('comment.html loaded'); - - // Date and time of comment - var date = new Date(); - - // @todo: унести наверх - // Data for template - var userLink = '//' + wsMessage.author + '.point.im/'; - var postAuthorLink = $('#top-post .info a').attr('href'); - var postLink = postAuthorLink + wsMessage.post_id; - var userAvatar = '//point.im/avatar/' + wsMessage.author; - var commentLink = '//point.im/' + wsMessage.post_id + '#' + wsMessage.comment_id; - var csRfToken = $('.reply-form input[name="csrf_token"').val(); - - // Filling template - console.info('Changing data in the comment element'); - // Date and time - $commentTemplate.find('.info .created') - .append($('').html(((date.getDate().toString.length < 2) ? ('0' + date.getDate().toString()) : (date.getDate().toString())) + ' ' + months[date.getMonth()])) - // Crutchy fix - .append($('
')) - ///Crutchy fix - .append($('').html(date.getHours() + ':' + ((date.getMinutes().toString().length < 2) ? ('0' + date.getMinutes().toString()) : (date.getMinutes().toString())))); - // Comment text - $commentTemplate.find('.text').append($('

').html(escapeHtml(wsMessage.text))); - // Author - $commentTemplate.find('.author a.user').attr('href', userLink).html(wsMessage.author); - // Avatar and link - $commentTemplate.find('.info a').attr('href', userLink).children('img.avatar').attr('src', userAvatar + '/24'); - // Post and comment ID's link - $commentTemplate.find('.clearfix .post-id a').attr('href', commentLink).html('#' + wsMessage.post_id + '/' + wsMessage.comment_id) - // Adding answer label - .after((wsMessage.to_comment_id !== null) ? (' в ответ на /' + wsMessage.to_comment_id + '') : ('')); - // Setting action labels and other attributes - $commentTemplate.find('.action-labels .reply-label').attr('for', 'reply-' + wsMessage.post_id + '_' + wsMessage.comment_id); - $commentTemplate.find('.action-labels .more-label').attr('for', 'action-' + wsMessage.post_id + '_' + wsMessage.comment_id); - $commentTemplate.find('.post-content input[name="action-radio"]').attr('id', 'action-' + wsMessage.post_id + '_' + wsMessage.comment_id); - // Bookmark link - $commentTemplate.find('.action-buttons a.bookmark').attr('href', postLink + '/b?comment_id=' + wsMessage.comment_id + '&csrf_token=' + csRfToken); - // Reply form - $commentTemplate.find('.post-content input.reply-radio').attr('id', 'reply-' + wsMessage.post_id + '_' + wsMessage.comment_id); - $commentTemplate.find('.post-content form.reply-form').attr('action', '/' + wsMessage.post_id); - $commentTemplate.find('.post-content form.reply-form textarea[name="text"]').html('@' + wsMessage.author + ', '); - $commentTemplate.find('.post-content form.reply-form input[name="comment_id"]').val(wsMessage.comment_id); - $commentTemplate.find('.post-content form.reply-form input[name="csrf_token"]').val(csRfToken); - ///Filling template - + + // Generating comment from websocket message + create_comment_elements({ + id: wsMessage.comment_id, + toId: wsMessage.to_comment_id, + postId: wsMessage.post_id, + author: wsMessage.author, + text: wsMessage.text, + fadeOut: options.is('option_ws_comments_color_fadeout') + }, function($comment) { // It's time to DOM console.info('Inserting comment'); + + // Search for parent comment + $parentComment = $('.post[data-comment-id="' + wsMessage.to_comment_id + '"]'); + console.log('Parent comment: %O', $parentComment); + // If list mode or not addressed to other comment - if ((treeSwitch == '?tree=0') || (wsMessage.to_comment_id == null)) { - // List mode - $('.content-wrap #comments #post-reply').before($commentTemplate.hide().fadeIn(2000)); + if ($('#comments #tree-switch a').eq(0).hasClass('active') || (wsMessage.to_comment_id === null) || (!$parentComment.length)) { + // Adding to the end of the list + $('.content-wrap #comments #post-reply').before($comment); } else { - // Tree mode - // Search parent comment - $parentComment = $('.post[data-comment-id="' + wsMessage.to_comment_id + '"]'); - if ($parentComment.length > 0) { - console.log('Parent comment: %O', $parentComment); - // Check for children - $parentCommentChildren = $parentComment.next('.comments'); - // If child comment already exist - if ($parentCommentChildren.length > 0) { - console.log('Child comments found. Appending...'); - $parentCommentChildren.append($commentTemplate.hide().fadeIn(2000)); - } else { - console.log('No child comments found. Creating...'); - $parentComment.after($('

').addClass('comments').append($commentTemplate.hide().fadeIn(2000))); - } + // Check for children + $parentCommentChildren = $parentComment.next('.comments'); + // If child comment already exist + if ($parentCommentChildren.length > 0) { + console.log('Child comments found. Appending...'); + $parentCommentChildren.append($comment); } else { - console.log('Parent comment not found'); - // FIXME: Double code - $('.content-wrap #comments #post-reply').before($commentTemplate.hide().fadeIn(2000)); + console.log('No child comments found. Creating...'); + $parentComment.after($('
').addClass('comments').append($comment)); } } - // Adding anchor - $commentTemplate.before($anchor); - - // Fading out highlight if needed - if (options.is('option_ws_comments_color_fadeout')) { - console.log('Fading out the highlight'); - $commentTemplate.children('.pp-highlight').fadeOut(20000); - } - // Desktop notifications if (options.is('option_ws_comments_notifications')) { console.log('Showing desktop notification'); @@ -551,11 +485,12 @@ $(document).ready(function() { console.groupEnd(); }); + break; // Posts case 'post': - console.group('ws-post #%s', wsMessage.post_id); + console.groupCollapsed('ws-post #%s', wsMessage.post_id); console.debug(wsMessage); @@ -564,7 +499,7 @@ $(document).ready(function() { // Recommendation case 'ok': - console.group('ws-recommendation #%s/%s', wsMessage.post_id, wsMessage.comment_id); + console.groupCollapsed('ws-recommendation #%s/%s', wsMessage.post_id, wsMessage.comment_id); console.debug(wsMessage); @@ -572,7 +507,7 @@ $(document).ready(function() { break; default: - console.group('ws-other'); + console.groupCollapsed('ws-other'); console.log(wsMessage); @@ -585,7 +520,7 @@ $(document).ready(function() { } } catch (e) { - console.log('WebSocket exception:') + console.log('WebSocket exception:'); console.log(e); console.log(evt.data); } @@ -662,6 +597,92 @@ var months = [ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; +/** + * Creating new comment elements for dynamic injection into the DOM + * + * @param {object} commentData Comment data + * @param {string|number} commentData.id ID of the created comment + * @param {string|number} commentData.toId ID of the comment replying to + * @param {string} commentData.postId ID of the post + * @param {string} commentData.author Author of the comment + * @param {string} commentData.text Text of the comment + * @param {boolean} commentData.fadeOut Is fadeout enabled or not + * @param {function} onCommentCreated Callback which is called when comment is ready + * + */ +function create_comment_elements(commentData, onCommentCreated) { + var $anchor = $('').attr('name', commentData.id); + + // Initializing comment element + var $commentTemplate = $('
').attr({ + 'class': 'post', + 'data-id': commentData.postId, + 'data-comment-id': commentData.id, + 'data-to-comment-id': commentData.id || '' + }); + + // Loading HTML template + $commentTemplate.load(chrome.extension.getURL('includes/comment.html'), function() { + // Load complete + console.info('comment.html loaded'); + + // Date and time of comment + var date = new Date(); + + // Data for template + var userLink = '//' + commentData.author + '.point.im/'; + var postAuthorLink = $('#top-post .info a').attr('href'); + var postLink = postAuthorLink + commentData.postId; + var userAvatar = '//point.im/avatar/' + commentData.author; + var commentLink = '//point.im/' + commentData.postId + '#' + commentData.id; + var csRfToken = $('.reply-form:first input[name="csrf_token"]').val(); + + // Filling template + // Date and time + $commentTemplate.find('.info .created') + .append($('').html(((date.getDate().toString.length < 2) ? ('0' + date.getDate().toString()) : (date.getDate().toString())) + ' ' + months[date.getMonth()])) + // Crutchy fix + .append($('
')) + ///Crutchy fix + .append($('').html(date.getHours() + ':' + ((date.getMinutes().toString().length < 2) ? ('0' + date.getMinutes().toString()) : (date.getMinutes().toString())))); + // Comment text + $commentTemplate.find('.text').append($('

').text(commentData.text)); + // Author + $commentTemplate.find('.author a.user').attr('href', userLink).text(commentData.author); + // Avatar and link + $commentTemplate.find('.info a').attr('href', userLink).children('img.avatar').attr('src', userAvatar + '/24'); + // Post and comment ID's link + $commentTemplate.find('.clearfix .post-id a').attr('href', commentLink).html('#' + commentData.postId + '/' + commentData.id) + // Adding answer label + .after((commentData.toId !== null) ? (' в ответ на /' + commentData.toId + '') : ('')); + // Setting action labels and other attributes + $commentTemplate.find('.action-labels .reply-label').attr('for', 'reply-' + commentData.postId + '_' + commentData.id); + $commentTemplate.find('.action-labels .more-label').attr('for', 'action-' + commentData.postId + '_' + commentData.id); + $commentTemplate.find('.post-content input[name="action-radio"]').attr('id', 'action-' + commentData.postId + '_' + commentData.id); + // Bookmark link + $commentTemplate.find('.action-buttons a.bookmark').attr('href', postLink + '/b?comment_id=' + commentData.id + '&csrf_token=' + csRfToken); + // Reply form + $commentTemplate.find('.post-content input.reply-radio').attr('id', 'reply-' + commentData.postId + '_' + commentData.id); + $commentTemplate.find('.post-content form.reply-form').attr('action', '/' + commentData.postId); + $commentTemplate.find('.post-content form.reply-form textarea[name="text"]').html('@' + commentData.author + ', '); + $commentTemplate.find('.post-content form.reply-form input[name="comment_id"]').val(commentData.id); + $commentTemplate.find('.post-content form.reply-form input[name="csrf_token"]').val(csRfToken); + ///Filling template + + // Fading out highlight if needed + if (commentData.fadeOut) { + console.log('Fading out the highlight'); + $commentTemplate.children('.pp-highlight').fadeOut(20000); + } + + // Hiding and fading in + $commentTemplate.hide().fadeIn(2000); + + // Triggering callback + onCommentCreated([$anchor, $commentTemplate]); + }); +} + // Картинки с бурятников var booru_picture_count = 0; function load_all_booru_images() { From 26b5da4e935d3c1f33f26e11ad9a8fffa89d0b81 Mon Sep 17 00:00:00 2001 From: Alexey Skobkin Date: Wed, 14 Jan 2015 14:48:49 +0400 Subject: [PATCH 02/10] Fixes. --- chrome_point_plus/js/point-plus.js | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/chrome_point_plus/js/point-plus.js b/chrome_point_plus/js/point-plus.js index 2464831..959a292 100644 --- a/chrome_point_plus/js/point-plus.js +++ b/chrome_point_plus/js/point-plus.js @@ -450,8 +450,8 @@ $(document).ready(function() { console.info('Inserting comment'); // Search for parent comment - $parentComment = $('.post[data-comment-id="' + wsMessage.to_comment_id + '"]'); - console.log('Parent comment: %O', $parentComment); + $parentComment = (wsMessage.to_comment_id) ? ($('.post[data-comment-id="' + wsMessage.to_comment_id + '"]')) : []; + console.log('Parent comment: %O', $parentComment || null); // If list mode or not addressed to other comment if ($('#comments #tree-switch a').eq(0).hasClass('active') || (wsMessage.to_comment_id === null) || (!$parentComment.length)) { @@ -476,10 +476,10 @@ $(document).ready(function() { chrome.runtime.sendMessage({ type: 'showNotification', notificationId: 'comment_' + wsMessage.post_id + '#' + wsMessage.comment_id, - avatarUrl: getProtocol() + userAvatar + '/80', - title: '@' + wsMessage.author + ' #' + wsMessage.post_id + '(/' + wsMessage.comment_id + ')', + avatarUrl: getProtocol() + '//point.im/avatar/' + wsMessage.author + '/80', + title: '@' + wsMessage.author + ' #' + wsMessage.post_id + '/' + wsMessage.comment_id + '', text: wsMessage.text - }); + }, function(response) {}); } console.groupEnd(); @@ -631,10 +631,6 @@ function create_comment_elements(commentData, onCommentCreated) { // Data for template var userLink = '//' + commentData.author + '.point.im/'; - var postAuthorLink = $('#top-post .info a').attr('href'); - var postLink = postAuthorLink + commentData.postId; - var userAvatar = '//point.im/avatar/' + commentData.author; - var commentLink = '//point.im/' + commentData.postId + '#' + commentData.id; var csRfToken = $('.reply-form:first input[name="csrf_token"]').val(); // Filling template @@ -650,9 +646,9 @@ function create_comment_elements(commentData, onCommentCreated) { // Author $commentTemplate.find('.author a.user').attr('href', userLink).text(commentData.author); // Avatar and link - $commentTemplate.find('.info a').attr('href', userLink).children('img.avatar').attr('src', userAvatar + '/24'); + $commentTemplate.find('.info a').attr('href', userLink).children('img.avatar').attr('src', '//point.im/avatar/' + commentData.author + '/24'); // Post and comment ID's link - $commentTemplate.find('.clearfix .post-id a').attr('href', commentLink).html('#' + commentData.postId + '/' + commentData.id) + $commentTemplate.find('.clearfix .post-id a').attr('href', '//point.im/' + commentData.postId + '#' + commentData.id).text('#' + commentData.postId + '/' + commentData.id) // Adding answer label .after((commentData.toId !== null) ? (' в ответ на /' + commentData.toId + '') : ('')); // Setting action labels and other attributes @@ -660,7 +656,7 @@ function create_comment_elements(commentData, onCommentCreated) { $commentTemplate.find('.action-labels .more-label').attr('for', 'action-' + commentData.postId + '_' + commentData.id); $commentTemplate.find('.post-content input[name="action-radio"]').attr('id', 'action-' + commentData.postId + '_' + commentData.id); // Bookmark link - $commentTemplate.find('.action-buttons a.bookmark').attr('href', postLink + '/b?comment_id=' + commentData.id + '&csrf_token=' + csRfToken); + $commentTemplate.find('.action-buttons a.bookmark').attr('href', $('#top-post .info a').attr('href') + commentData.postId + '/b?comment_id=' + commentData.id + '&csrf_token=' + csRfToken); // Reply form $commentTemplate.find('.post-content input.reply-radio').attr('id', 'reply-' + commentData.postId + '_' + commentData.id); $commentTemplate.find('.post-content form.reply-form').attr('action', '/' + commentData.postId); @@ -672,14 +668,14 @@ function create_comment_elements(commentData, onCommentCreated) { // Fading out highlight if needed if (commentData.fadeOut) { console.log('Fading out the highlight'); - $commentTemplate.children('.pp-highlight').fadeOut(20000); + $commentTemplate.children('.pp-highlight').delay(250).fadeOut(20000); } - // Hiding and fading in - $commentTemplate.hide().fadeIn(2000); + // Hiding + $commentTemplate.hide().delay(250).fadeIn(2000); // Triggering callback - onCommentCreated([$anchor, $commentTemplate]); + onCommentCreated($anchor.add($commentTemplate)); }); } From dcb1dcf5297166080bfca0c27db9d5d44b0729dd Mon Sep 17 00:00:00 2001 From: Alexey Skobkin Date: Wed, 14 Jan 2015 15:42:20 +0400 Subject: [PATCH 03/10] Notification click bugfix. --- chrome_point_plus/js/background.js | 41 +++++++++++++----------------- chrome_point_plus/js/point-plus.js | 20 +++++++++------ 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/chrome_point_plus/js/background.js b/chrome_point_plus/js/background.js index 2dd44c6..11ff391 100644 --- a/chrome_point_plus/js/background.js +++ b/chrome_point_plus/js/background.js @@ -8,6 +8,23 @@ chrome.storage.sync.get('options_version', function(data) { } }); +// Adding notification click event listener +chrome.notifications.onClicked.addListener(function(notificationId) { + // Detecting notification type + if (notificationId.indexOf('comment_') === 0) { + tab_url = 'https://point.im/' + notificationId.replace(/comment_/g, ''); + } else if (notificationId.indexOf('post_') === 0) { + tab_url = 'https://point.im/' + notificationId.replace(/post_/g, ''); + } + console.log('Notification %s clicked! Opening new tab: %s', notificationId, tab_url); + + if (tab_url !== undefined) { + chrome.tabs.create({ + url: tab_url + }); + } +}); + // Crutches and bikes /** * Inject several JS files @@ -79,30 +96,6 @@ chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { return true; break; - case 'listenNotificationClicks': - // Adding notification click event listener - chrome.notifications.onClicked.addListener(function(notificationId) { - // Detecting notification type - if (notificationId.indexOf('comment_') === 0) { - tab_url = message.protocol + '//' + 'point.im/' + notificationId.replace(/comment_/g, ''); - } else if (notificationId.indexOf('post_') === 0) { - tab_url = message.protocol + '//' + 'point.im/' + notificationId.replace(/post_/g, ''); - } - console.log('Notification %s clicked! Opening new tab: %s', notificationId, tab_url); - - if (tab_url !== undefined) { - chrome.tabs.create({ - url: tab_url - }); - } - }); - - sendResponse(true); - - // Fuck You, Chrome API documentation! - return true; - break; - /** * @deprecated since 1.19.1 */ diff --git a/chrome_point_plus/js/point-plus.js b/chrome_point_plus/js/point-plus.js index 959a292..1142347 100644 --- a/chrome_point_plus/js/point-plus.js +++ b/chrome_point_plus/js/point-plus.js @@ -381,13 +381,6 @@ $(document).ready(function() { ws = new WebSocket(((location.protocol == 'https:') ? 'wss' : 'ws') + '://point.im/ws'); console.log('WebSocket created: %O', ws); - // @todo: унести в опцию - // Adding event listener for notification click - chrome.runtime.sendMessage({ - type: 'listenNotificationClicks', - protocol: getProtocol() - }); - // Detecting post id if presented var postId = $('#top-post').attr('data-id'); console.debug('Current post id detected as #%s', postId); @@ -477,7 +470,7 @@ $(document).ready(function() { type: 'showNotification', notificationId: 'comment_' + wsMessage.post_id + '#' + wsMessage.comment_id, avatarUrl: getProtocol() + '//point.im/avatar/' + wsMessage.author + '/80', - title: '@' + wsMessage.author + ' #' + wsMessage.post_id + '/' + wsMessage.comment_id + '', + title: '@' + wsMessage.author + ' #' + wsMessage.post_id + '/' + wsMessage.comment_id, text: wsMessage.text }, function(response) {}); } @@ -493,6 +486,17 @@ $(document).ready(function() { console.groupCollapsed('ws-post #%s', wsMessage.post_id); console.debug(wsMessage); + + if (true /*options.is('option_ws_posts_notifications')*/) { + console.log('Showing desktop notification'); + chrome.runtime.sendMessage({ + type: 'showNotification', + notificationId: 'post_' + wsMessage.post_id, + avatarUrl: getProtocol() + '//point.im/avatar/' + wsMessage.author + '/80', + title: 'Post by @' + wsMessage.author + ' #' + wsMessage.post_id, + text: wsMessage.text + }, function(response) {}); + } console.groupEnd(); break; From c5ba450afefb548eaf947a5cf0b04be12cd0d14b Mon Sep 17 00:00:00 2001 From: Alexey Skobkin Date: Wed, 14 Jan 2015 18:41:34 +0400 Subject: [PATCH 04/10] Ajax comments test implementation. Some bugs included. --- chrome_point_plus/js/point-plus.js | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/chrome_point_plus/js/point-plus.js b/chrome_point_plus/js/point-plus.js index 1142347..8b45bdd 100644 --- a/chrome_point_plus/js/point-plus.js +++ b/chrome_point_plus/js/point-plus.js @@ -543,6 +543,81 @@ $(document).ready(function() { file: 'css/modules/at_before_username.css' }); } + + // @todo implement option + if (true || options.is('option_ajax_comments')) { + // Removing old bindings + // Dirty hack for page context + $('#comments').replaceWith($('#comments').clone()); + + // Binding new + $('#comments').on('keypress.pp', '.reply-form textarea', function (evt) { + if ((evt.keyCode === 10 || evt.keyCode === 13) && (evt.ctrlKey || evt.metaKey)) { + evt.stopPropagation(); + evt.preventDefault(); + + var $post = $(this).parents('.post:first'); + var csRf = $(this).siblings('input[name="csrf_token"]').val(); + console.log(csRf); + + $.ajax({ + type: 'POST', + url: '/api/post/' + $post.data('id'), + data: { + text: $(this).val(), + comment_id: $post.data('comment-id') + }, + success: function (data, textStatus) { + console.log('data %O', data); + console.log('status %O', textStatus); + /*{ + comment_id: 34, + id: 'ovrwcv' + };*/ + + if (textStatus == 'success') { + // Hiding form + $('#reply-' + $post.data('id') + '_' + $post.data('comment-id')).prop('checked', false); + + create_comment_elements({ + id: data.comment_id, + toId: $post.data('comment-id') || null, + postId: $post.data('id'), + author: $('#name h1').text(), + text: $(this).val(), + fadeOut: false + }, function($comment) { + // If list mode or not addressed to other comment + if ($('#comments #tree-switch a').eq(0).hasClass('active') || ($post.data('comment-id') === undefined)) { + // Adding to the end of the list + $('.content-wrap #comments #post-reply').before($comment); + } else { + // Check for children + $parentCommentChildren = $post.next('.comments'); + // If child comment already exist + if ($parentCommentChildren.length) { + console.log('Child comments found. Appending...'); + $parentCommentChildren.append($comment); + } else { + console.log('No child comments found. Creating...'); + $post.after($('

').addClass('comments').append($comment).css('margin-left', '0px')); + } + } + }); + + // Cleaning textarea + $(this).val(''); + + } + }.bind(this), + beforeSend: function (xhr) { + xhr.setRequestHeader('X-CSRF', csRf); + } + }); + } + }); + + } // Hightlight post with new comments if (options.is('option_other_hightlight_post_comments')) { From 56bd3117ecb8844f6cf9c1011b01f18a7e6fe6f0 Mon Sep 17 00:00:00 2001 From: Alexey Skobkin Date: Thu, 15 Jan 2015 13:05:37 +0400 Subject: [PATCH 05/10] Error messages. --- chrome_point_plus/_locales/en/messages.json | 5 +++++ chrome_point_plus/_locales/ru/messages.json | 5 +++++ chrome_point_plus/js/point-plus.js | 23 ++++++++++----------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/chrome_point_plus/_locales/en/messages.json b/chrome_point_plus/_locales/en/messages.json index 8ba0f9a..c8d0c1d 100644 --- a/chrome_point_plus/_locales/en/messages.json +++ b/chrome_point_plus/_locales/en/messages.json @@ -177,5 +177,10 @@ "options_feedback_text": { "message": "

If you find an error do not hesitate to send me a bug report<\/a>.<\/p>

Also you can make a donation in the following ways:<\/p>