1/** 2 * WP-Dapp Hive Comment Integration 3 * Allows users to post replies to Hive directly from WordPress using Keychain. 4 */ 5jQuery(document).ready(function($) { 6 // Check if Keychain is available 7 function isKeychainAvailable() { 8 return typeof hive_keychain !== 'undefined'; 9 } 10 11 // Counter to generate unique IDs per reply form 12 let wpdappFormCounter = 0; 13 14 // Function to generate permlink 15 function generatePermlink(text) { 16 let permlink = text.toLowerCase() 17 .replace(/[^a-z0-9-]/g, '-') 18 .replace(/-+/g, '-') 19 .replace(/^-|-$/g, ''); 20 permlink = permlink.substring(0, 50); 21 return permlink + '-' + Date.now().toString(36); 22 } 23 24 function addInlineReplyButtons() { 25 if (!wpdapp_frontend.show_reply_buttons) return; 26 $('.wpdapp-comment-list > li.wpdapp-comment').each(function() { 27 const $comment = $(this); 28 const hiveKey = $comment.data('hive-key'); 29 if (!hiveKey) return; 30 const parts = String(hiveKey).split('/'); 31 const author = parts[0] || ''; 32 const permlink = parts[1] || ''; 33 const $actions = $comment.children('.wpdapp-comment-body').children('.wpdapp-comment-actions'); 34 if ($actions.length && $actions.find('.wpdapp-reply-button').length === 0) { 35 // Put Keychain reply button first, then the external link 36 $actions.prepend('<button type="button" class="wpdapp-reply-button" aria-label="' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.replyWithKeychain : 'Reply with Keychain') + '" data-author="' + author + '" data-permlink="' + permlink + '">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.replyWithKeychain : 'Reply with Keychain') + '</button>'); 37 } 38 }); 39 } 40 41 // Initial injection on page load 42 addInlineReplyButtons(); 43 44 // Note: Top-level reply button is rendered server-side in the footer to avoid duplicates 45 46 // Username persistence: prefer localStorage, fallback to sessionStorage 47 let hiveUsername = (function() { 48 try { return localStorage.getItem('wpdapp_hive_username') || sessionStorage.getItem('wpdapp_hive_username') || null; } catch(e) { return sessionStorage.getItem('wpdapp_hive_username') || null; } 49 })(); 50 51 // Open reply form (no connect step) 52 $(document).on('click', '.wpdapp-reply-button', function(e) { 53 e.preventDefault(); 54 const $button = $(this); 55 56 let $form = $button.next('.wpdapp-reply-form'); 57 if ($form.length === 0) { 58 const formId = ++wpdappFormCounter; 59 const usernameInputId = 'wpdapp-username-' + formId; 60 const connected = !!hiveUsername; 61 $form = $('<div class="wpdapp-reply-form" role="form" aria-live="polite" data-form-id="' + formId + '">' + 62 '<div class="wpdapp-conn-row">' + 63 '<span class="wpdapp-status-chip ' + (connected ? 'connected' : 'not-connected') + '">' + (connected ? (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.statusConnected : 'Connected') : (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.statusNotConnected : 'Not connected')) + '</span>' + 64 (connected 65 ? '<p class="wpdapp-connected-as">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.connectedAs : 'Connected as:') + ' <span class="wpdapp-username-display">' + hiveUsername + '</span> <button type="button" class="wpdapp-change-account">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.change : 'Change') + '</button></p>' 66 : '<label class="wpdapp-username-label" for="' + usernameInputId + '">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.enterHiveUsername : 'Enter your Hive username:') + '</label>' + 67 '<input type="text" class="wpdapp-username" id="' + usernameInputId + '" placeholder="' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.hiveUsernamePlaceholder : 'Hive username') + '">' 68 ) + 69 '</div>' + 70 '<textarea aria-label="' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.yourReply : 'Your reply') + '" placeholder="' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.yourReplyPlaceholder : 'Your reply...') + '"></textarea>' + 71 '<button type="button" class="wpdapp-submit-reply">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.submit : 'Submit') + '</button>' + 72 '<button type="button" class="wpdapp-cancel-reply">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.cancel : 'Cancel') + '</button>' + 73 '</div>'); 74 $button.after($form); 75 } 76 $form.slideDown(); 77 $form.find('textarea').focus(); 78 79 // If Keychain missing, inform inline but do not block 80 if (!isKeychainAvailable() && $form.find('.wpdapp-keychain-warning').length === 0) { 81 $form.prepend('<div class="wpdapp-form-error wpdapp-keychain-warning" role="status">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.keychainNotDetected : 'Hive Keychain not detected. Please install the extension.') + '</div>'); 82 } 83 }); 84 85 // Change account: switch to edit mode 86 $(document).on('click', '.wpdapp-change-account', function() { 87 const $form = $(this).closest('.wpdapp-reply-form'); 88 const formId = $form.data('form-id') || Date.now(); 89 const editId = 'wpdapp-username-edit-' + formId; 90 const current = $form.find('.wpdapp-username-display').text().trim() || hiveUsername || ''; 91 const label = '<label class="wpdapp-username-label" for="' + editId + '">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.enterHiveUsername : 'Enter your Hive username:') + '</label>'; 92 const input = '<input type="text" class="wpdapp-username-edit" id="' + editId + '" value="' + current + '">'; 93 const actions = '<button type="button" class="wpdapp-save-account">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.save : 'Save') + '</button>' + 94 '<button type="button" class="wpdapp-cancel-account">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.cancel : 'Cancel') + '</button>'; 95 $form.find('.wpdapp-connected-as').html(label + ' ' + input + ' ' + actions); 96 $form.find('#' + editId).focus(); 97 }); 98 99 // Save new account 100 $(document).on('click', '.wpdapp-save-account', function() { 101 const $form = $(this).closest('.wpdapp-reply-form'); 102 const newUsername = ($form.find('.wpdapp-username-edit').val() || '').trim(); 103 if (!newUsername) { 104 $form.append('<div class="wpdapp-form-error" role="status">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.pleaseEnterUsername : 'Please enter your Hive username.') + '</div>'); 105 setTimeout(() => $form.find('.wpdapp-form-error').remove(), 3000); 106 return; 107 } 108 hiveUsername = newUsername; 109 try { localStorage.setItem('wpdapp_hive_username', hiveUsername); } catch(e) {} 110 sessionStorage.setItem('wpdapp_hive_username', hiveUsername); 111 $form.find('.wpdapp-connected-as').html( 112 (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.connectedAs : 'Connected as:') + 113 ' <span class="wpdapp-username-display">' + hiveUsername + '</span> ' + 114 '<button type="button" class="wpdapp-change-account">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.change : 'Change') + '</button>' 115 ); 116 // Ensure chip shows connected 117 const $chip = $form.find('.wpdapp-status-chip'); 118 $chip.removeClass('not-connected').addClass('connected').text(wpdapp_frontend.i18n ? wpdapp_frontend.i18n.statusConnected : 'Connected'); 119 }); 120 121 // Cancel account edit 122 $(document).on('click', '.wpdapp-cancel-account', function() { 123 const $form = $(this).closest('.wpdapp-reply-form'); 124 const display = hiveUsername ? hiveUsername : ''; 125 const inner = hiveUsername 126 ? (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.connectedAs : 'Connected as:') + ' <span class="wpdapp-username-display">' + display + '</span> ' + 127 '<button type="button" class="wpdapp-change-account">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.change : 'Change') + '</button>' 128 : ''; 129 if (inner) { 130 $form.find('.wpdapp-connected-as').html(inner); 131 } else { 132 // If nothing stored, revert to input row 133 const formId = $form.data('form-id') || Date.now(); 134 const usernameInputId = 'wpdapp-username-' + formId; 135 $form.find('.wpdapp-connected-as').html('<label class="wpdapp-username-label" for="' + usernameInputId + '">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.enterHiveUsername : 'Enter your Hive username:') + '</label>' + 136 '<input type="text" class="wpdapp-username" id="' + usernameInputId + '" placeholder="' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.hiveUsernamePlaceholder : 'Hive username') + '">'); 137 const $chip = $form.find('.wpdapp-status-chip'); 138 $chip.removeClass('connected').addClass('not-connected').text(wpdapp_frontend.i18n ? wpdapp_frontend.i18n.statusNotConnected : 'Not connected'); 139 } 140 }); 141 142 // Cancel 143 $(document).on('click', '.wpdapp-cancel-reply', function() { 144 $(this).parent().slideUp(); 145 }); 146 147 // Submit 148 let isSyncInFlight = false;
149 150 $(document).on('click', '.wpdapp-submit-reply', function() { 151 const $form = $(this).parent(); 152 const content = $form.find('textarea').val().trim(); 153 154 if (!content) { 155 $form.append('<div class="wpdapp-form-error" role="status">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.pleaseEnterReply : 'Please enter a reply.') + '</div>'); 156 setTimeout(() => $form.find('.wpdapp-form-error').remove(), 3000); 157 return; 158 } 159 if (content.length < 3) { 160 $form.append('<div class="wpdapp-form-error" role="status">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.replyMinLength : 'Reply must be at least 3 characters.') + '</div>'); 161 setTimeout(() => $form.find('.wpdapp-form-error').remove(), 3000); 162 return; 163 } 164 if (content.length > 5000) { 165 $form.append('<div class="wpdapp-form-error" role="status">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.replyMaxLength : 'Reply must be at most 5000 characters.') + '</div>'); 166 setTimeout(() => $form.find('.wpdapp-form-error').remove(), 3000); 167 return; 168 } 169 170 // Determine username: stored or typed 171 let usedUsername = hiveUsername; 172 if (!usedUsername) { 173 const typed = $form.find('.wpdapp-username').val(); 174 if (typed && typed.trim().length > 0) { 175 usedUsername = typed.trim(); 176 } else { 177 $form.append('<div class="wpdapp-form-error" role="status">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.pleaseEnterUsername : 'Please enter your Hive username.') + '</div>'); 178 setTimeout(() => $form.find('.wpdapp-form-error').remove(), 3000); 179 return; 180 } 181 } 182 183 if (!isKeychainAvailable()) { 184 alert((wpdapp_frontend.i18n ? wpdapp_frontend.i18n.keychainNotDetected : 'Hive Keychain not detected. Please install the extension.')); 185 return; 186 } 187 188 const $button = $form.prev('.wpdapp-reply-button'); 189 const parentAuthor = $button.data('author'); 190 const parentPermlink = $button.data('permlink'); 191 const permlink = generatePermlink(content); 192 193 const operations = [ 194 ['comment', { 195 parent_author: parentAuthor, 196 parent_permlink: parentPermlink, 197 author: usedUsername, 198 permlink: permlink, 199 title: '', 200 body: content, 201 json_metadata: JSON.stringify({ app: 'wp-dapp/0.7.4', format: 'markdown' }) 202 }] 203 ]; 204 205 $(this).text(wpdapp_frontend.i18n ? wpdapp_frontend.i18n.posting : 'Posting...').prop('disabled', true).addClass('loading'); 206 207 hive_keychain.requestBroadcast(usedUsername, operations, 'posting', function(response) { 208 $(this).text(wpdapp_frontend.i18n ? wpdapp_frontend.i18n.submit : 'Submit').prop('disabled', false).removeClass('loading'); 209 if (response.success) { 210 // Persist username after first success 211 hiveUsername = usedUsername; 212 try { localStorage.setItem('wpdapp_hive_username', hiveUsername); } catch(e) {} 213 sessionStorage.setItem('wpdapp_hive_username', hiveUsername); 214 // Update status chip/UI 215 $form.find('.wpdapp-username, .wpdapp-username-label').remove(); 216 const $chip = $form.find('.wpdapp-status-chip'); 217 $chip.removeClass('not-connected').addClass('connected').text(wpdapp_frontend.i18n ? wpdapp_frontend.i18n.statusConnected : 'Connected'); 218 if ($form.find('.wpdapp-connected-as').length === 0) { 219 $chip.after('<p class="wpdapp-connected-as">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.connectedAs : 'Connected as:') + ' ' + hiveUsername + '</p>'); 220 } else { 221 $form.find('.wpdapp-connected-as').text((wpdapp_frontend.i18n ? wpdapp_frontend.i18n.connectedAs : 'Connected as:') + ' ' + hiveUsername); 222 } 223 224 $form.append('<div class="wpdapp-form-success" role="status">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.postedSyncing : 'Reply posted successfully! Syncing...') + '</div>'); 225 if (isSyncInFlight) return; 226 isSyncInFlight = true; 227 $.ajax({ 228 url: wpdapp_frontend.ajax_url, 229 type: 'POST', 230 data: { action: 'wpdapp_sync_comments', nonce: wpdapp_frontend.nonce, post_id: wpdapp_frontend.post_id }, 231 success: function(syncResponse) { 232 if (syncResponse.success) { 233 $.ajax({ 234 url: wpdapp_frontend.ajax_url, 235 type: 'POST', 236 data: { action: 'wpdapp_render_hive_c
236omments', nonce: wpdapp_frontend.nonce, post_id: wpdapp_frontend.post_id }, 237 success: function(renderResponse) { 238 if (renderResponse.success && renderResponse.data && renderResponse.data.html) { 239 var $footer = $('.wpdapp-hive-comments-footer').first(); 240 if ($footer.length) { $footer.remove(); } 241 $('.wpdapp-hive-comments').first().replaceWith(renderResponse.data.html); 242 $form.find('.wpdapp-form-success').text(wpdapp_frontend.i18n ? wpdapp_frontend.i18n.replyPostedSynced : 'Reply posted and synced!'); 243 } else { 244 $form.append('<div class="wpdapp-form-error" role="status">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.syncedRefreshFailed : 'Synced, but failed to refresh comments.') + '</div>'); 245 } 246 }, 247 error: function() { 248 $form.append('<div class="wpdapp-form-error" role="status">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.syncedRefreshError : 'Synced, but refresh failed.') + '</div>'); 249 } 250 }); 251 } else { 252 $form.append('<div class="wpdapp-form-error" role="status">' + (wpdapp_frontend.i18n ? wpdapp_frontend.i18n.syncFailedPrefix : 'Posted to Hive, but sync failed:') + ' ' + (syncResponse.data || 'Unknown error') + '</div>'); 253 } 254 }, 255 complete: function() { 256 isSyncInFlight = false; 257 setTimeout(() => $form.find('.wpdapp-form-success, .wpdapp-form-error').remove(), 5000); 258 $form.slideUp(); 259 $form.find('textarea').val(''); 260 } 261 }); 262 } else { 263 $form.append('<div class="wpdapp-form-error">Error: ' + response.message + '</div>'); 264 setTimeout(() => $form.find('.wpdapp-form-error').remove(), 5000); 265 } 266 }.bind(this)); 267 }); 268 269 // Handle sync button 270 $(document).on('click', '.wpdapp-sync-button', function(e) { 271 e.preventDefault(); 272 const $button = $(this); 273 if (isSyncInFlight) return; 274 isSyncInFlight = true; 275 const isForce = $button.data('force') ? 1 : 0; 276 $button.text(wpdapp_frontend.i18n ? wpdapp_frontend.i18n.syncing : (isForce ? 'Importing...' : 'Syncing...')).prop('disabled', true).addClass('loading'); 277 $.ajax({ 278 url: wpdapp_frontend.ajax_url, 279 type: 'POST', 280 data: { 281 action: 'wpdapp_sync_comments', 282 nonce: wpdapp_frontend.nonce, 283 post_id: wpdapp_frontend.post_id, 284 force: isForce 285 }, 286 success: function(syncResponse) { 287 if (syncResponse.success) { 288 // Fetch fresh rendered HTML and replace the block 289 $.ajax({ 290 url: wpdapp_frontend.ajax_url, 291 type: 'POST', 292 data: { 293 action: 'wpdapp_render_hive_comments', 294 nonce: wpdapp_frontend.nonce, 295 post_id: wpdapp_frontend.post_id 296 }, 297 success: function(renderResponse) { 298 if (renderResponse.success && renderResponse.data && renderResponse.data.html) { 299 var $footer = $('.wpdapp-hive-comments-footer').first(); 300 if ($footer.length) { 301 $footer.remove(); 302 } 303 $('.wpdapp-hive-comments').first().replaceWith(renderResponse.data.html); 304 // Re-inject inline reply buttons after DOM refresh 305 addInlineReplyButtons(); 306 } 307 } 308 }); 309 } 310 }, 311 complete: function() { 312 isSyncInFlight = false;
313 $button.text(wpdapp_frontend.i18n ? wpdapp_frontend.i18n.syncHiveComments : 'Sync (copy) Hive replies into WP comments').prop('disabled', false).removeClass('loading'); 314 } 315 }); 316 }); 317});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.