Sync your WordPress posts to standard.site records on your PDS
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

show records from pds

+536
+267
assets/js/records.js
··· 1 + (function ($) { 2 + "use strict"; 3 + 4 + function formatDate(isoString) { 5 + if (!isoString) return "\u2014"; 6 + var d = new Date(isoString); 7 + return d.toLocaleDateString(undefined, { 8 + year: "numeric", 9 + month: "short", 10 + day: "numeric", 11 + hour: "2-digit", 12 + minute: "2-digit", 13 + }); 14 + } 15 + 16 + function esc(str) { 17 + return $("<span>").text(str || "").html(); 18 + } 19 + 20 + function fieldRow(label, value) { 21 + return "<tr><th>" + esc(label) + "</th><td>" + value + "</td></tr>"; 22 + } 23 + 24 + function renderPublication(record) { 25 + var val = record.value; 26 + var html = '<table class="widefat wireservice-record-table">'; 27 + html += "<tbody>"; 28 + html += fieldRow("AT-URI", "<code>" + esc(record.uri) + "</code>"); 29 + html += fieldRow("CID", "<code>" + esc(record.cid) + "</code>"); 30 + html += fieldRow("Name", esc(val.name)); 31 + html += fieldRow( 32 + "URL", 33 + '<a href="' + 34 + esc(val.url) + 35 + '" target="_blank">' + 36 + esc(val.url) + 37 + "</a>" 38 + ); 39 + 40 + if (val.description) { 41 + html += fieldRow("Description", esc(val.description)); 42 + } 43 + 44 + if (val.icon) { 45 + html += fieldRow( 46 + "Icon", 47 + "<code>blob</code> (" + 48 + esc(val.icon.mimeType || "unknown type") + 49 + ")" 50 + ); 51 + } 52 + 53 + if (val.basicTheme) { 54 + var theme = val.basicTheme; 55 + var colors = ["background", "foreground", "accent", "accentForeground"]; 56 + for (var i = 0; i < colors.length; i++) { 57 + var c = theme[colors[i]]; 58 + if (c) { 59 + var rgb = "rgb(" + c.r + ", " + c.g + ", " + c.b + ")"; 60 + html += fieldRow( 61 + "Theme: " + colors[i], 62 + '<span class="wireservice-color-swatch" style="background:' + 63 + rgb + 64 + ';"></span> ' + 65 + rgb 66 + ); 67 + } 68 + } 69 + } 70 + 71 + if (val.preferences) { 72 + html += fieldRow( 73 + "Show in Discover", 74 + val.preferences.showInDiscover ? "Yes" : "No" 75 + ); 76 + } 77 + 78 + html += "</tbody></table>"; 79 + return html; 80 + } 81 + 82 + function renderDocumentCard(record) { 83 + var val = record.value; 84 + var html = '<div class="wireservice-document-card">'; 85 + html += '<div class="wireservice-document-card-header">'; 86 + html += "<h3>" + esc(val.title) + "</h3>"; 87 + html += "</div>"; 88 + html += '<div class="wireservice-document-card-body">'; 89 + html += '<table class="widefat wireservice-record-table">'; 90 + html += "<tbody>"; 91 + html += fieldRow("AT-URI", "<code>" + esc(record.uri) + "</code>"); 92 + 93 + if (val.path) { 94 + html += fieldRow("Path", esc(val.path)); 95 + } 96 + 97 + html += fieldRow("Published", formatDate(val.publishedAt)); 98 + 99 + if (val.updatedAt) { 100 + html += fieldRow("Updated", formatDate(val.updatedAt)); 101 + } 102 + 103 + if (val.description) { 104 + var desc = 105 + val.description.length > 200 106 + ? val.description.substring(0, 200) + "..." 107 + : val.description; 108 + html += fieldRow("Description", esc(desc)); 109 + } 110 + 111 + if (val.tags && val.tags.length > 0) { 112 + var tags = val.tags 113 + .map(function (t) { 114 + return esc(t); 115 + }) 116 + .join(", "); 117 + html += fieldRow("Tags", tags); 118 + } 119 + 120 + if (val.coverImage) { 121 + html += fieldRow( 122 + "Cover Image", 123 + "<code>blob</code> (" + 124 + esc(val.coverImage.mimeType || "unknown type") + 125 + ")" 126 + ); 127 + } 128 + 129 + if (val.textContent) { 130 + html += fieldRow( 131 + "Text Content", 132 + esc(val.textContent.substring(0, 100)) + 133 + (val.textContent.length > 100 ? "..." : "") 134 + ); 135 + } 136 + 137 + html += "</tbody></table>"; 138 + html += "</div></div>"; 139 + return html; 140 + } 141 + 142 + function loadPublication() { 143 + var $loading = $("#wireservice-publication-loading"); 144 + var $error = $("#wireservice-publication-error"); 145 + var $data = $("#wireservice-publication-data"); 146 + 147 + $.post(wireserviceRecords.ajaxUrl, { 148 + action: "wireservice_get_publication_record", 149 + nonce: wireserviceRecords.nonce, 150 + }) 151 + .done(function (response) { 152 + $loading.hide(); 153 + if (!response.success) { 154 + $error 155 + .html( 156 + "<p class='wireservice-error'>" + esc(response.data) + "</p>" 157 + ) 158 + .show(); 159 + return; 160 + } 161 + $data.html(renderPublication(response.data)).show(); 162 + }) 163 + .fail(function () { 164 + $loading.hide(); 165 + $error 166 + .html( 167 + "<p class='wireservice-error'>Failed to fetch publication record.</p>" 168 + ) 169 + .show(); 170 + }); 171 + } 172 + 173 + var documentCursor = null; 174 + var totalLoaded = 0; 175 + 176 + function loadDocuments(cursor) { 177 + var $loading = $("#wireservice-documents-loading"); 178 + var $error = $("#wireservice-documents-error"); 179 + var $list = $("#wireservice-documents-list"); 180 + var $pagination = $("#wireservice-documents-pagination"); 181 + var $loadMore = $("#wireservice-documents-load-more"); 182 + 183 + if (!cursor) { 184 + $loading.show(); 185 + } 186 + $loadMore.prop("disabled", true); 187 + 188 + var postData = { 189 + action: "wireservice_list_document_records", 190 + nonce: wireserviceRecords.nonce, 191 + }; 192 + 193 + if (cursor) { 194 + postData.cursor = cursor; 195 + } 196 + 197 + $.post(wireserviceRecords.ajaxUrl, postData) 198 + .done(function (response) { 199 + $loading.hide(); 200 + if (!response.success) { 201 + $error 202 + .html( 203 + "<p class='wireservice-error'>" + esc(response.data) + "</p>" 204 + ) 205 + .show(); 206 + return; 207 + } 208 + 209 + var records = response.data.records || []; 210 + documentCursor = response.data.cursor || null; 211 + totalLoaded += records.length; 212 + 213 + if (records.length === 0 && totalLoaded === 0) { 214 + $list.html("<p>No document records found on this PDS.</p>").show(); 215 + return; 216 + } 217 + 218 + var html = ""; 219 + for (var i = 0; i < records.length; i++) { 220 + html += renderDocumentCard(records[i]); 221 + } 222 + 223 + $list.append(html).show(); 224 + 225 + var $count = $("#wireservice-documents-count"); 226 + if ($count.length === 0) { 227 + $list.before( 228 + '<p id="wireservice-documents-count">' + 229 + totalLoaded + 230 + " records loaded</p>" 231 + ); 232 + } else { 233 + $count.text(totalLoaded + " records loaded"); 234 + } 235 + 236 + if (documentCursor) { 237 + $pagination.show(); 238 + $loadMore.prop("disabled", false); 239 + } else { 240 + $pagination.hide(); 241 + } 242 + }) 243 + .fail(function () { 244 + $loading.hide(); 245 + $error 246 + .html( 247 + "<p class='wireservice-error'>Failed to fetch document records.</p>" 248 + ) 249 + .show(); 250 + }); 251 + } 252 + 253 + $(document).ready(function () { 254 + if ($("#wireservice-records-publication").length === 0) { 255 + return; 256 + } 257 + 258 + loadPublication(); 259 + loadDocuments(null); 260 + 261 + $("#wireservice-documents-load-more").on("click", function () { 262 + if (documentCursor) { 263 + loadDocuments(documentCursor); 264 + } 265 + }); 266 + }); 267 + })(jQuery);
+54
includes/API.php
··· 425 425 } 426 426 427 427 /** 428 + * Get a single record from the PDS. 429 + * 430 + * @param string $collection The collection NSID. 431 + * @param string $rkey The record key. 432 + * @return array|\WP_Error 433 + */ 434 + public function get_record(string $collection, string $rkey) 435 + { 436 + $did = $this->get_did(); 437 + 438 + if (is_wp_error($did)) { 439 + return $did; 440 + } 441 + 442 + return $this->pds_get("/xrpc/com.atproto.repo.getRecord", [ 443 + "repo" => $did, 444 + "collection" => $collection, 445 + "rkey" => $rkey, 446 + ]); 447 + } 448 + 449 + /** 450 + * List records in a collection from the PDS. 451 + * 452 + * @param string $collection The collection NSID. 453 + * @param int $limit Max records to return (default 50, max 100). 454 + * @param string|null $cursor Pagination cursor. 455 + * @return array|\WP_Error 456 + */ 457 + public function list_records( 458 + string $collection, 459 + int $limit = 50, 460 + ?string $cursor = null, 461 + ) { 462 + $did = $this->get_did(); 463 + 464 + if (is_wp_error($did)) { 465 + return $did; 466 + } 467 + 468 + $params = [ 469 + "repo" => $did, 470 + "collection" => $collection, 471 + "limit" => $limit, 472 + ]; 473 + 474 + if ($cursor !== null) { 475 + $params["cursor"] = $cursor; 476 + } 477 + 478 + return $this->pds_get("/xrpc/com.atproto.repo.listRecords", $params); 479 + } 480 + 481 + /** 428 482 * Upload a blob to the PDS. 429 483 * 430 484 * @param string $file_path The local file path.
+96
includes/Admin.php
··· 50 50 $this, 51 51 "handle_backfill_batch", 52 52 ]); 53 + add_action("wp_ajax_wireservice_get_publication_record", [ 54 + $this, 55 + "handle_get_publication_record", 56 + ]); 57 + add_action("wp_ajax_wireservice_list_document_records", [ 58 + $this, 59 + "handle_list_document_records", 60 + ]); 53 61 add_filter("plugin_action_links_wireservice/wireservice.php", [ 54 62 $this, 55 63 "add_settings_link", ··· 83 91 "ajaxUrl" => admin_url("admin-ajax.php"), 84 92 "nonce" => wp_create_nonce("wireservice_backfill"), 85 93 ]); 94 + 95 + $active_tab = isset($_GET["tab"]) ? sanitize_key($_GET["tab"]) : "settings"; 96 + 97 + if ($active_tab === "records") { 98 + wp_enqueue_script( 99 + "wireservice-records", 100 + WIRESERVICE_PLUGIN_URL . "assets/js/records.js", 101 + ["jquery"], 102 + WIRESERVICE_VERSION, 103 + true, 104 + ); 105 + 106 + wp_localize_script("wireservice-records", "wireserviceRecords", [ 107 + "ajaxUrl" => admin_url("admin-ajax.php"), 108 + "nonce" => wp_create_nonce("wireservice_records"), 109 + ]); 110 + } 86 111 } 87 112 88 113 /** ··· 793 818 admin_url("options-general.php?page=wireservice&settings-updated=true"), 794 819 ); 795 820 exit(); 821 + } 822 + 823 + /** 824 + * Handle AJAX request to fetch the publication record from the PDS. 825 + * 826 + * @return void 827 + */ 828 + public function handle_get_publication_record(): void 829 + { 830 + if (!current_user_can("manage_options")) { 831 + wp_send_json_error("Unauthorized.", 403); 832 + } 833 + 834 + check_ajax_referer("wireservice_records", "nonce"); 835 + 836 + $pub_uri = $this->publication->get_at_uri(); 837 + 838 + if (empty($pub_uri)) { 839 + wp_send_json_error("No publication record exists."); 840 + } 841 + 842 + $rkey = AtUri::get_rkey($pub_uri); 843 + $result = $this->api->get_record("site.standard.publication", $rkey); 844 + 845 + if (is_wp_error($result)) { 846 + wp_send_json_error($result->get_error_message()); 847 + } 848 + 849 + wp_send_json_success($result); 850 + } 851 + 852 + /** 853 + * Handle AJAX request to list document records from the PDS. 854 + * 855 + * @return void 856 + */ 857 + public function handle_list_document_records(): void 858 + { 859 + if (!current_user_can("manage_options")) { 860 + wp_send_json_error("Unauthorized.", 403); 861 + } 862 + 863 + check_ajax_referer("wireservice_records", "nonce"); 864 + 865 + $pub_uri = $this->publication->get_at_uri(); 866 + $pub_data = $this->publication->get_publication_data(); 867 + $pub_url = rtrim($pub_data["url"] ?? "", "/"); 868 + 869 + $cursor = isset($_POST["cursor"]) 870 + ? sanitize_text_field(wp_unslash($_POST["cursor"])) 871 + : null; 872 + 873 + $result = $this->api->list_records( 874 + "site.standard.document", 875 + 50, 876 + $cursor ?: null, 877 + ); 878 + 879 + if (is_wp_error($result)) { 880 + wp_send_json_error($result->get_error_message()); 881 + } 882 + 883 + $result["records"] = array_values(array_filter( 884 + $result["records"] ?? [], 885 + function (array $record) use ($pub_uri, $pub_url): bool { 886 + $site = $record["value"]["site"] ?? ""; 887 + return $site === $pub_uri || rtrim($site, "/") === $pub_url; 888 + }, 889 + )); 890 + 891 + wp_send_json_success($result); 796 892 } 797 893 }
+48
templates/records-page.php
··· 1 + <?php 2 + /** 3 + * Records browser template for Wireservice. 4 + * 5 + * @package Wireservice 6 + */ 7 + 8 + defined('ABSPATH') || exit; 9 + ?> 10 + 11 + <div class="wireservice-records"> 12 + <div id="wireservice-records-publication"> 13 + <h2><?php esc_html_e("Publication Record", "wireservice"); ?></h2> 14 + <p class="description"> 15 + <?php esc_html_e( 16 + "The site.standard.publication record stored on your PDS.", 17 + "wireservice", 18 + ); ?> 19 + </p> 20 + <div id="wireservice-publication-loading"> 21 + <p><?php esc_html_e("Loading publication record...", "wireservice"); ?></p> 22 + </div> 23 + <div id="wireservice-publication-error" style="display: none;"></div> 24 + <div id="wireservice-publication-data" style="display: none;"></div> 25 + </div> 26 + 27 + <hr> 28 + 29 + <div id="wireservice-records-documents"> 30 + <h2><?php esc_html_e("Document Records", "wireservice"); ?></h2> 31 + <p class="description"> 32 + <?php esc_html_e( 33 + "All site.standard.document records stored on your PDS.", 34 + "wireservice", 35 + ); ?> 36 + </p> 37 + <div id="wireservice-documents-loading"> 38 + <p><?php esc_html_e("Loading document records...", "wireservice"); ?></p> 39 + </div> 40 + <div id="wireservice-documents-error" style="display: none;"></div> 41 + <div id="wireservice-documents-list" style="display: none;"></div> 42 + <div id="wireservice-documents-pagination" style="display: none;"> 43 + <button type="button" class="button" id="wireservice-documents-load-more"> 44 + <?php esc_html_e("Load More", "wireservice"); ?> 45 + </button> 46 + </div> 47 + </div> 48 + </div>
+71
templates/settings-page.php
··· 42 42 <div class="wrap"> 43 43 <h1><?php echo esc_html(get_admin_page_title()); ?></h1> 44 44 45 + <?php $active_tab = isset($_GET["tab"]) ? sanitize_key($_GET["tab"]) : "settings"; ?> 46 + <nav class="nav-tab-wrapper"> 47 + <a href="<?php echo esc_url(admin_url("options-general.php?page=wireservice&tab=settings")); ?>" 48 + class="nav-tab <?php echo $active_tab === "settings" ? "nav-tab-active" : ""; ?>"> 49 + <?php esc_html_e("Settings", "wireservice"); ?> 50 + </a> 51 + <?php if ($is_connected && $pub_uri): ?> 52 + <a href="<?php echo esc_url(admin_url("options-general.php?page=wireservice&tab=records")); ?>" 53 + class="nav-tab <?php echo $active_tab === "records" ? "nav-tab-active" : ""; ?>"> 54 + <?php esc_html_e("Records", "wireservice"); ?> 55 + </a> 56 + <?php endif; ?> 57 + </nav> 58 + 59 + <?php if ($active_tab === "settings"): ?> 45 60 <div class="wireservice-settings"> 46 61 <h2><?php esc_html_e("AT Protocol Connection", "wireservice"); ?></h2> 47 62 ··· 426 441 </button> 427 442 </form> 428 443 </div> 444 + <?php elseif ($active_tab === "records"): ?> 445 + <?php include WIRESERVICE_PLUGIN_DIR . "templates/records-page.php"; ?> 446 + <?php endif; ?> 429 447 </div> 430 448 <style> 431 449 .wireservice-settings { ··· 514 532 cursor: pointer; 515 533 color: #d63638; 516 534 font-weight: 500; 535 + } 536 + .wireservice-records { 537 + max-width: 800px; 538 + } 539 + .wireservice-record-table { 540 + border-collapse: collapse; 541 + } 542 + .wireservice-record-table th { 543 + width: 160px; 544 + text-align: left; 545 + padding: 8px 12px; 546 + vertical-align: top; 547 + font-weight: 600; 548 + white-space: nowrap; 549 + } 550 + .wireservice-record-table td { 551 + padding: 8px 12px; 552 + word-break: break-all; 553 + } 554 + .wireservice-record-table code { 555 + font-size: 12px; 556 + background: #f0f0f1; 557 + padding: 2px 6px; 558 + border-radius: 3px; 559 + } 560 + .wireservice-document-card { 561 + background: #fff; 562 + border: 1px solid #c3c4c7; 563 + border-radius: 4px; 564 + margin-bottom: 16px; 565 + } 566 + .wireservice-document-card-header { 567 + padding: 12px 16px; 568 + border-bottom: 1px solid #f0f0f1; 569 + } 570 + .wireservice-document-card-header h3 { 571 + margin: 0; 572 + font-size: 14px; 573 + } 574 + .wireservice-document-card-body { 575 + padding: 0; 576 + } 577 + .wireservice-document-card-body .wireservice-record-table { 578 + border: none; 579 + } 580 + .wireservice-color-swatch { 581 + display: inline-block; 582 + width: 16px; 583 + height: 16px; 584 + border-radius: 3px; 585 + border: 1px solid #ddd; 586 + vertical-align: middle; 587 + margin-right: 4px; 517 588 } 518 589 </style>