From 7aea1e82636413f6a016a351923dcbf202c289e4 Mon Sep 17 00:00:00 2001 From: Matt Wiebe Date: Wed, 30 Aug 2023 14:14:57 -0500 Subject: [PATCH] Add "Follow Me" block (#395) The Follow Me block helps site visitors to follow you in the fediverse --------- Co-authored-by: Matthias Pfefferle --- README.md | 17 +-- build/follow-me/block.json | 41 ++++++++ build/follow-me/index.asset.php | 1 + build/follow-me/index.js | 1 + build/follow-me/style-index.css | 1 + build/follow-me/view.asset.php | 1 + build/follow-me/view.js | 1 + build/followers/index.asset.php | 2 +- build/followers/index.js | 6 +- build/followers/view.asset.php | 2 +- build/followers/view.js | 4 +- composer.json | 3 + includes/class-blocks.php | 34 +++++- includes/class-webfinger.php | 4 +- includes/model/class-user.php | 7 ++ readme.txt | 17 +-- src/follow-me/block.json | 38 +++++++ src/follow-me/button-style.js | 75 ++++++++++++++ src/follow-me/edit.js | 26 +++++ src/follow-me/follow-me.js | 178 ++++++++++++++++++++++++++++++++ src/follow-me/index.js | 5 + src/follow-me/style.scss | 57 ++++++++++ src/follow-me/view.js | 16 +++ src/followers/edit.js | 26 +---- src/followers/followers.js | 2 +- src/shared/use-user-options.js | 24 +++++ 26 files changed, 535 insertions(+), 54 deletions(-) create mode 100644 build/follow-me/block.json create mode 100644 build/follow-me/index.asset.php create mode 100644 build/follow-me/index.js create mode 100644 build/follow-me/style-index.css create mode 100644 build/follow-me/view.asset.php create mode 100644 build/follow-me/view.js create mode 100644 src/follow-me/block.json create mode 100644 src/follow-me/button-style.js create mode 100644 src/follow-me/edit.js create mode 100644 src/follow-me/follow-me.js create mode 100644 src/follow-me/index.js create mode 100644 src/follow-me/style.scss create mode 100644 src/follow-me/view.js create mode 100644 src/shared/use-user-options.js diff --git a/README.md b/README.md index 1d7890c..a283b8b 100644 --- a/README.md +++ b/README.md @@ -108,17 +108,18 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu ### 1.0.0 ### * Add: blog-wide Account (catchall, like `example.com@example.com`) -* Add: Signature Verification: https://docs.joinmastodon.org/spec/security/ . -* Add: a Followers Block. +* Add: a Follow Me block (help visitors to follow your Profile) +* Add: Signature Verification: https://docs.joinmastodon.org/spec/security/ +* Add: a Followers Block (show off your Followers) * Add: Simple caching * Add: Collection endpoints for Featured Tags and Featured Posts -* Update: Complete rewrite of the Follower-System based on Custom Post Types. +* Update: Complete rewrite of the Follower-System based on Custom Post Types * Update: Improved linter (PHPCS) -* Compatibility: Add a new conditional, `\Activitypub\is_activitypub_request()`, to allow third-party plugins to detect ActivityPub requests. -* Compatibility: Add hooks to allow modifying images returned in ActivityPub requests. -* Compatibility: Indicate that the plugin is compatible and has been tested with the latest version of WordPress, 6.3. -* Compatibility: Avoid PHP notice on sites using PHP 8.2. -* Fixed: Load the plugin later in the WordPress code lifecycle to avoid errors in some requests. +* Compatibility: Add a new conditional, `\Activitypub\is_activitypub_request()`, to allow third-party plugins to detect ActivityPub requests +* Compatibility: Add hooks to allow modifying images returned in ActivityPub requests +* Compatibility: Indicate that the plugin is compatible and has been tested with the latest version of WordPress, 6.3 +* Compatibility: Avoid PHP notice on sites using PHP 8.2 +* Fixed: Load the plugin later in the WordPress code lifecycle to avoid errors in some requests * Fixed: Updating posts * Fixed: Hashtag now support CamelCase and UTF-8 diff --git a/build/follow-me/block.json b/build/follow-me/block.json new file mode 100644 index 0000000..b9bc81e --- /dev/null +++ b/build/follow-me/block.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "name": "activitypub/follow-me", + "apiVersion": 3, + "version": "1.0.0", + "title": "Follow me on the Fediverse", + "category": "widgets", + "description": "Display your Fediverse profile so that visitors can follow you.", + "textdomain": "activitypub", + "icon": "groups", + "supports": { + "html": false, + "color": { + "gradients": true, + "link": true, + "__experimentalDefaultControls": { + "background": true, + "text": true, + "link": true + } + }, + "__experimentalBorder": { + "radius": true, + "width": true, + "color": true, + "style": true + } + }, + "attributes": { + "selectedUser": { + "type": "string", + "default": "site" + } + }, + "editorScript": "file:./index.js", + "viewScript": "file:./view.js", + "style": [ + "file:./style-index.css", + "wp-components" + ] +} \ No newline at end of file diff --git a/build/follow-me/index.asset.php b/build/follow-me/index.asset.php new file mode 100644 index 0000000..d8eef3e --- /dev/null +++ b/build/follow-me/index.asset.php @@ -0,0 +1 @@ + array('wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '9d4af3528585afd49437'); diff --git a/build/follow-me/index.js b/build/follow-me/index.js new file mode 100644 index 0000000..2b7f6d1 --- /dev/null +++ b/build/follow-me/index.js @@ -0,0 +1 @@ +(()=>{"use strict";var e,t={843:(e,t,n)=>{const r=window.wp.blocks,o=window.wp.element,l=window.wp.primitives,a=(0,o.createElement)(l.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,o.createElement)(l.Path,{d:"M15.5 9.5a1 1 0 100-2 1 1 0 000 2zm0 1.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zm-2.25 6v-2a2.75 2.75 0 00-2.75-2.75h-4A2.75 2.75 0 003.75 15v2h1.5v-2c0-.69.56-1.25 1.25-1.25h4c.69 0 1.25.56 1.25 1.25v2h1.5zm7-2v2h-1.5v-2c0-.69-.56-1.25-1.25-1.25H15v-1.5h2.5A2.75 2.75 0 0120.25 15zM9.5 8.5a1 1 0 11-2 0 1 1 0 012 0zm1.5 0a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z",fillRule:"evenodd"}));function c(){return c=Object.assign?Object.assign.bind():function(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:null;return n?`${e}${arguments.length>3&&void 0!==arguments[3]?arguments[3]:""} { ${t}: ${n}; }\n`:""}function g(e,t,n,r){return E(e,"background-color",t)+E(e,"color",n)+E(e,"background-color",r,":hover")+E(e,"background-color",r,":focus")}function k(e){let{selector:t,style:n,backgroundColor:r}=e;const l=function(e,t,n){const r=`${e} .components-button`,o=("string"==typeof(l=n)?h(l):l?.color?.background||null)||t?.color?.background;var l;return g(r,_(t?.elements?.link?.color?.text),o,_(t?.elements?.link?.[":hover"]?.color?.text))}(t,n,r);return(0,o.createElement)("style",null,l)}const{namespace:O}=window._activityPubOptions,x={avatar:"",resource:"@well@hello.dolly",name:(0,s.__)("Hello Dolly Fan Account","activitypub"),url:"#"};function C(e){if(!e)return x;const t={...x,...e};return t.avatar=t?.icon?.url,t}function S(e){let{profile:t,popupStyles:n,userId:r}=e;const{avatar:l,name:a,resource:c}=t;return(0,o.createElement)("div",{className:"activitypub-profile"},(0,o.createElement)("img",{className:"activitypub-profile__avatar",src:l}),(0,o.createElement)("div",{className:"activitypub-profile__content"},(0,o.createElement)("div",{className:"activitypub-profile__name"},a),(0,o.createElement)("div",{className:"activitypub-profile__handle"},c)),(0,o.createElement)(N,{profile:t,popupStyles:n,userId:r}))}function N(e){let{profile:t,popupStyles:n,userId:r}=e;const[l,a]=(0,o.useState)(!1),c=(0,s.sprintf)((0,s.__)("Follow %s","activitypub"),t?.name);return(0,o.createElement)(o.Fragment,null,(0,o.createElement)(u.Button,{className:"activitypub-profile__follow",onClick:()=>a(!0)},(0,s.__)("Follow","activitypub")),l&&(0,o.createElement)(u.Modal,{className:"activitypub-profile__confirm",onRequestClose:()=>a(!1),title:c},(0,o.createElement)(P,{profile:t,userId:r}),(0,o.createElement)("style",null,n)))}function $(e){try{return new URL(e),!0}catch(e){return!1}}function P(e){let{profile:t,userId:n}=e;const{resource:r}=t,l=(0,s.__)("Follow","activitypub"),a=(0,s.__)("Loading...","activitypub"),c=(0,s.__)("Opening...","activitypub"),i=(0,s.__)("Error","activitypub"),p=(0,s.__)("Invalid","activitypub"),[m,v]=(0,o.useState)(l),[h,_]=(0,o.useState)(f),E=(0,b.useCopyToClipboard)(r,(()=>{_(w),setTimeout((()=>_(f)),1e3)})),[g,k]=(0,o.useState)(""),x=(0,o.useCallback)((()=>{let e;if(!$(g)&&!function(e){const t=e.replace(/^@/,"").split("@");return 2===t.length&&$(`https://${t[1]}`)}(g))return v(p),e=setTimeout((()=>v(l)),2e3),()=>clearTimeout(e);const t=`/${O}/users/${n}/remote-follow?resource=${g}`;v(a),d()({path:t}).then((e=>{let{url:t}=e;v(c),setTimeout((()=>{window.open(t,"_blank"),v(l)}),200)})).catch((()=>{v(i),setTimeout((()=>v(l)),2e3)}))}),[g]);return(0,o.createElement)("div",{className:"activitypub-follow-me__dialog"},(0,o.createElement)("div",{className:"apmfd__section"},(0,o.createElement)("h4",null,(0,s.__)("My Profile","activitypub")),(0,o.createElement)("div",{className:"apfmd-description"},(0,s.__)("Copy and paste my profile into the search field of your favorite fediverse app or server.","activitypub")),(0,o.createElement)("div",{className:"apfmd__button-group"},(0,o.createElement)("input",{type:"text",value:r,readOnly:!0}),(0,o.createElement)(u.Button,{ref:E},(0,o.createElement)(y,{icon:h}),(0,s.__)("Copy","activitypub")))),(0,o.createElement)("div",{className:"apmfd__section"},(0,o.createElement)("h4",null,(0,s.__)("Your Profile","activitypub")),(0,o.createElement)("div",{className:"apfmd-description"},(0,o.createInterpolateElement)((0,s.__)("Or, if you know your own profile, we can start things that way! (eg https://example.com/yourusername or yourusername@example.com)","activitypub"),{code:(0,o.createElement)("code",null)})),(0,o.createElement)("div",{className:"apfmd__button-group"},(0,o.createElement)("input",{type:"text",value:g,onKeyDown:e=>{"Enter"===e?.code&&x()},onChange:e=>k(e.target.value)}),(0,o.createElement)(u.Button,{onClick:x},m))))}function z(e){let{selectedUser:t,style:n,backgroundColor:r,id:l,useId:a=!1}=e;const[c,i]=(0,o.useState)(C()),s="site"===t?0:t,u=function(e){return g(".apfmd__button-group .components-button",_(e?.elements?.link?.color?.text)||"#111","#fff",_(e?.elements?.link?.[":hover"]?.color?.text)||"#333")}(n),p=a?{id:l}:{};function m(e){i(C(e))}return(0,o.useEffect)((()=>{(function(e){const t={headers:{Accept:"application/activity+json"},path:`/${O}/users/${e}`};return d()(t)})(s).then(m)}),[s]),(0,o.createElement)("div",p,(0,o.createElement)(k,{selector:`#${l}`,style:n,backgroundColor:r}),(0,o.createElement)(S,{profile:c,userId:s,popupStyles:u}))}(0,r.registerBlockType)("activitypub/follow-me",{edit:function(e){let{attributes:t,setAttributes:n}=e;const r=(0,i.useBlockProps)(),l=function(){const e=m?.users?(0,p.useSelect)((e=>e("core").getUsers({who:"authors"}))):[];return(0,o.useMemo)((()=>{if(!e)return[];const t=m?.site?[{label:(0,s.__)("Whole Site","activitypub"),value:"site"}]:[];return e.reduce(((e,t)=>(e.push({label:t.name,value:t.id}),e)),t)}),[e])}();return(0,o.createElement)("div",r,(0,o.createElement)(i.InspectorControls,{key:"setting"},(0,o.createElement)(u.PanelBody,{title:(0,s.__)("Followers Options","activitypub")},(0,o.createElement)(u.SelectControl,{label:(0,s.__)("Select User","activitypub"),value:t.selectedUser,options:l,onChange:e=>n({selectedUser:e})}))),(0,o.createElement)(z,c({},t,{id:r.id})))},save:()=>null,icon:a})}},n={};function r(e){var o=n[e];if(void 0!==o)return o.exports;var l=n[e]={exports:{}};return t[e](l,l.exports,r),l.exports}r.m=t,e=[],r.O=(t,n,o,l)=>{if(!n){var a=1/0;for(u=0;u=l)&&Object.keys(r.O).every((e=>r.O[e](n[i])))?n.splice(i--,1):(c=!1,l0&&e[u-1][2]>l;u--)e[u]=e[u-1];e[u]=[n,o,l]},r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={127:0,740:0};r.O.j=t=>0===e[t];var t=(t,n)=>{var o,l,[a,c,i]=n,s=0;if(a.some((t=>0!==e[t]))){for(o in c)r.o(c,o)&&(r.m[o]=c[o]);if(i)var u=i(r)}for(t&&t(n);sr(843)));o=r.O(o)})(); \ No newline at end of file diff --git a/build/follow-me/style-index.css b/build/follow-me/style-index.css new file mode 100644 index 0000000..4091e2c --- /dev/null +++ b/build/follow-me/style-index.css @@ -0,0 +1 @@ +.activitypub-follow-me-block-wrapper .activitypub-profile,.editor-styles-wrapper .activitypub-profile{align-items:self-start;display:flex;padding:1rem}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__avatar,.editor-styles-wrapper .activitypub-profile .activitypub-profile__avatar{height:75px;margin-right:1rem;width:75px}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__name,.editor-styles-wrapper .activitypub-profile .activitypub-profile__name{font-size:var(--wp--preset--font-size--large);line-height:1;margin:0}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__follow,.editor-styles-wrapper .activitypub-profile .activitypub-profile__follow{align-self:center;background-color:var(--wp--preset--color--black);color:var(--wp--preset--color--white);margin-left:auto}.activitypub-follow-me__dialog{max-width:30em}.activitypub-follow-me__dialog h4{line-height:1;margin:0}.activitypub-follow-me__dialog .apmfd__section{margin-bottom:2em}.activitypub-follow-me__dialog .apfmd-description{font-size:var(--wp--preset--font-size--normal,.75rem);margin:.33em 0 1em}.activitypub-follow-me__dialog .apfmd__button-group{display:flex;justify-content:flex-end}.activitypub-follow-me__dialog .apfmd__button-group svg{height:21px;margin-right:.5em;width:21px}.activitypub-follow-me__dialog .apfmd__button-group input{flex:1;padding-left:1em;padding-right:1em} diff --git a/build/follow-me/view.asset.php b/build/follow-me/view.asset.php new file mode 100644 index 0000000..56862e5 --- /dev/null +++ b/build/follow-me/view.asset.php @@ -0,0 +1 @@ + array('wp-api-fetch', 'wp-components', 'wp-compose', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '06a922cf2e58c94b6431'); diff --git a/build/follow-me/view.js b/build/follow-me/view.js new file mode 100644 index 0000000..e55d91b --- /dev/null +++ b/build/follow-me/view.js @@ -0,0 +1 @@ +(()=>{"use strict";var e,t={810:(e,t,r)=>{function n(){return n=Object.assign?Object.assign.bind():function(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:null;return r?`${e}${arguments.length>3&&void 0!==arguments[3]?arguments[3]:""} { ${t}: ${r}; }\n`:""}function _(e,t,r,n){return w(e,"background-color",t)+w(e,"color",r)+w(e,"background-color",n,":hover")+w(e,"background-color",n,":focus")}function h(e){let{selector:t,style:r,backgroundColor:n}=e;const l=function(e,t,r){const n=`${e} .components-button`,o=("string"==typeof(l=r)?y(l):l?.color?.background||null)||t?.color?.background;var l;return _(n,b(t?.elements?.link?.color?.text),o,b(t?.elements?.link?.[":hover"]?.color?.text))}(t,r,n);return(0,o.createElement)("style",null,l)}const{namespace:E}=window._activityPubOptions,g={avatar:"",resource:"@well@hello.dolly",name:(0,s.__)("Hello Dolly Fan Account","activitypub"),url:"#"};function k(e){if(!e)return g;const t={...g,...e};return t.avatar=t?.icon?.url,t}function O(e){let{profile:t,popupStyles:r,userId:n}=e;const{avatar:l,name:a,resource:c}=t;return(0,o.createElement)("div",{className:"activitypub-profile"},(0,o.createElement)("img",{className:"activitypub-profile__avatar",src:l}),(0,o.createElement)("div",{className:"activitypub-profile__content"},(0,o.createElement)("div",{className:"activitypub-profile__name"},a),(0,o.createElement)("div",{className:"activitypub-profile__handle"},c)),(0,o.createElement)(x,{profile:t,popupStyles:r,userId:n}))}function x(e){let{profile:t,popupStyles:r,userId:n}=e;const[l,a]=(0,o.useState)(!1),c=(0,s.sprintf)((0,s.__)("Follow %s","activitypub"),t?.name);return(0,o.createElement)(o.Fragment,null,(0,o.createElement)(u.Button,{className:"activitypub-profile__follow",onClick:()=>a(!0)},(0,s.__)("Follow","activitypub")),l&&(0,o.createElement)(u.Modal,{className:"activitypub-profile__confirm",onRequestClose:()=>a(!1),title:c},(0,o.createElement)(N,{profile:t,userId:n}),(0,o.createElement)("style",null,r)))}function C(e){try{return new URL(e),!0}catch(e){return!1}}function N(e){let{profile:t,userId:r}=e;const{resource:n}=t,l=(0,s.__)("Follow","activitypub"),a=(0,s.__)("Loading...","activitypub"),c=(0,s.__)("Opening...","activitypub"),p=(0,s.__)("Error","activitypub"),y=(0,s.__)("Invalid","activitypub"),[b,w]=(0,o.useState)(l),[_,h]=(0,o.useState)(m),g=(0,f.useCopyToClipboard)(n,(()=>{h(d),setTimeout((()=>h(m)),1e3)})),[k,O]=(0,o.useState)(""),x=(0,o.useCallback)((()=>{let e;if(!C(k)&&!function(e){const t=e.replace(/^@/,"").split("@");return 2===t.length&&C(`https://${t[1]}`)}(k))return w(y),e=setTimeout((()=>w(l)),2e3),()=>clearTimeout(e);const t=`/${E}/users/${r}/remote-follow?resource=${k}`;w(a),i()({path:t}).then((e=>{let{url:t}=e;w(c),setTimeout((()=>{window.open(t,"_blank"),w(l)}),200)})).catch((()=>{w(p),setTimeout((()=>w(l)),2e3)}))}),[k]);return(0,o.createElement)("div",{className:"activitypub-follow-me__dialog"},(0,o.createElement)("div",{className:"apmfd__section"},(0,o.createElement)("h4",null,(0,s.__)("My Profile","activitypub")),(0,o.createElement)("div",{className:"apfmd-description"},(0,s.__)("Copy and paste my profile into the search field of your favorite fediverse app or server.","activitypub")),(0,o.createElement)("div",{className:"apfmd__button-group"},(0,o.createElement)("input",{type:"text",value:n,readOnly:!0}),(0,o.createElement)(u.Button,{ref:g},(0,o.createElement)(v,{icon:_}),(0,s.__)("Copy","activitypub")))),(0,o.createElement)("div",{className:"apmfd__section"},(0,o.createElement)("h4",null,(0,s.__)("Your Profile","activitypub")),(0,o.createElement)("div",{className:"apfmd-description"},(0,o.createInterpolateElement)((0,s.__)("Or, if you know your own profile, we can start things that way! (eg https://example.com/yourusername or yourusername@example.com)","activitypub"),{code:(0,o.createElement)("code",null)})),(0,o.createElement)("div",{className:"apfmd__button-group"},(0,o.createElement)("input",{type:"text",value:k,onKeyDown:e=>{"Enter"===e?.code&&x()},onChange:e=>O(e.target.value)}),(0,o.createElement)(u.Button,{onClick:x},b))))}function S(e){let{selectedUser:t,style:r,backgroundColor:n,id:l,useId:a=!1}=e;const[c,u]=(0,o.useState)(k()),s="site"===t?0:t,p=function(e){return _(".apfmd__button-group .components-button",b(e?.elements?.link?.color?.text)||"#111","#fff",b(e?.elements?.link?.[":hover"]?.color?.text)||"#333")}(r),m=a?{id:l}:{};function d(e){u(k(e))}return(0,o.useEffect)((()=>{(function(e){const t={headers:{Accept:"application/activity+json"},path:`/${E}/users/${e}`};return i()(t)})(s).then(d)}),[s]),(0,o.createElement)("div",m,(0,o.createElement)(h,{selector:`#${l}`,style:r,backgroundColor:n}),(0,o.createElement)(O,{profile:c,userId:s,popupStyles:p}))}let $=1;a()((()=>{[].forEach.call(document.querySelectorAll(".activitypub-follow-me-block-wrapper"),(e=>{const t=JSON.parse(e.dataset.attrs);(0,o.render)((0,o.createElement)(S,n({},t,{id:"activitypub-follow-me-block-"+$++,useId:!0})),e)}))}))}},r={};function n(e){var o=r[e];if(void 0!==o)return o.exports;var l=r[e]={exports:{}};return t[e](l,l.exports,n),l.exports}n.m=t,e=[],n.O=(t,r,o,l)=>{if(!r){var a=1/0;for(s=0;s=l)&&Object.keys(n.O).every((e=>n.O[e](r[i])))?r.splice(i--,1):(c=!1,l0&&e[s-1][2]>l;s--)e[s]=e[s-1];e[s]=[r,o,l]},n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={529:0,740:0};n.O.j=t=>0===e[t];var t=(t,r)=>{var o,l,[a,c,i]=r,u=0;if(a.some((t=>0!==e[t]))){for(o in c)n.o(c,o)&&(n.m[o]=c[o]);if(i)var s=i(n)}for(t&&t(r);un(810)));o=n.O(o)})(); \ No newline at end of file diff --git a/build/followers/index.asset.php b/build/followers/index.asset.php index 06c1d9d..93f6c2a 100644 --- a/build/followers/index.asset.php +++ b/build/followers/index.asset.php @@ -1 +1 @@ - array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => '284dffd27ea0242085be'); + array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => 'a7adf2628fd43600eef6'); diff --git a/build/followers/index.js b/build/followers/index.js index df9590b..a9269ce 100644 --- a/build/followers/index.js +++ b/build/followers/index.js @@ -1,3 +1,3 @@ -(()=>{var e={184:(e,t)=>{var a;!function(){"use strict";var n={}.hasOwnProperty;function l(){for(var e=[],t=0;t{var t=e&&e.__esModule?()=>e.default:()=>e;return a.d(t,{a:t}),t},a.d=(e,t)=>{for(var n in t)a.o(t,n)&&!a.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},a.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";const e=window.wp.blocks,t=window.wp.element,n=window.wp.primitives,l=(0,t.createElement)(n.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,t.createElement)(n.Path,{d:"M15.5 9.5a1 1 0 100-2 1 1 0 000 2zm0 1.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zm-2.25 6v-2a2.75 2.75 0 00-2.75-2.75h-4A2.75 2.75 0 003.75 15v2h1.5v-2c0-.69.56-1.25 1.25-1.25h4c.69 0 1.25.56 1.25 1.25v2h1.5zm7-2v2h-1.5v-2c0-.69-.56-1.25-1.25-1.25H15v-1.5h2.5A2.75 2.75 0 0120.25 15zM9.5 8.5a1 1 0 11-2 0 1 1 0 012 0zm1.5 0a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z",fillRule:"evenodd"}));function r(){return r=Object.assign?Object.assign.bind():function(e){for(var t=1;t{e.preventDefault(),!a&&r(l)}},n)}const g={outlined:"outlined",minimal:"minimal"};function f(e){let{compact:a,nextLabel:n,page:l,pageClick:r,perPage:o,prevLabel:i,total:c,variant:s=g.outlined}=e;const p=((e,t)=>{let a=[1,e-2,e-1,e,e+1,e+2,t];a.sort(((e,t)=>e-t)),a=a.filter(((e,a,n)=>e>=1&&e<=t&&n.lastIndexOf(e)===a));for(let e=a.length-2;e>=0;e--)a[e]===a[e+1]&&a.splice(e+1,1);return a})(l,Math.ceil(c/o)),u=w()("alignwide wp-block-query-pagination is-content-justification-space-between is-layout-flex wp-block-query-pagination-is-layout-flex",`is-${s}`,{"is-compact":a});return(0,t.createElement)("nav",{className:u},i&&(0,t.createElement)(d,{key:"prev",page:l-1,pageClick:r,active:1===l,"aria-label":i,className:"wp-block-query-pagination-previous block-editor-block-list__block"},i),!a&&(0,t.createElement)("div",{className:"block-editor-block-list__block wp-block wp-block-query-pagination-numbers"},p.map((e=>(0,t.createElement)(d,{key:e,page:e,pageClick:r,active:e===l,className:"page-numbers"},e)))),n&&(0,t.createElement)(d,{key:"next",page:l+1,pageClick:r,active:l===Math.ceil(c/o),"aria-label":n,className:"wp-block-query-pagination-next block-editor-block-list__block"},n))}const{namespace:y}=window._activityPubOptions;function h(e){let{selectedUser:a,per_page:n,order:l,title:o,page:i,setPage:c,className:u="",followLinks:b=!0}=e;const w="site"===a?0:a,[d,g]=(0,p.useState)([]),[h,_]=(0,p.useState)(0),[E,x]=(0,p.useState)(0),[C,S]=function(){const[e,t]=(0,p.useState)(1);return[e,t]}(),O=i||C,N=c||S,P=(0,t.createInterpolateElement)(/* translators: arrow for previous followers link */ -(0,s.__)(" Less","activitypub"),{span:(0,t.createElement)("span",{class:"wp-block-query-pagination-previous-arrow is-arrow-arrow","aria-hidden":"true"})}),L=(0,t.createInterpolateElement)(/* translators: arrow for next followers link */ -(0,s.__)("More ","activitypub"),{span:(0,t.createElement)("span",{class:"wp-block-query-pagination-next-arrow is-arrow-arrow","aria-hidden":"true"})});return(0,p.useEffect)((()=>{const e=function(e,t,a,n){const l=`/${y}/users/${e}/followers`,r={per_page:t,order:a,page:n,context:"full"};return(0,m.addQueryArgs)(l,r)}(w,n,l,O);v()({path:e}).then((e=>{_(Math.ceil(e.totalItems/n)),x(e.totalItems),g(e.orderedItems)})).catch((e=>console.error(e)))}),[w,n,l,O]),(0,t.createElement)("div",{className:"activitypub-follower-block "+u},(0,t.createElement)("h3",null,o),(0,t.createElement)("ul",null,d&&d.map((e=>(0,t.createElement)("li",{key:e.url},(0,t.createElement)(k,r({},e,{followLinks:b})))))),h>1&&(0,t.createElement)(f,{page:O,perPage:n,total:E,pageClick:N,nextLabel:L,prevLabel:P,compact:"is-style-compact"===u}))}function k(e){let{name:a,icon:n,url:l,preferredUsername:i,followLinks:c=!0}=e;const s=`@${i}`,p={};return c||(p.onClick=e=>e.preventDefault()),(0,t.createElement)(o.ExternalLink,r({className:"activitypub-link",href:l,title:s},p),(0,t.createElement)("img",{width:"40",height:"40",src:n.url,class:"avatar activitypub-avatar"}),(0,t.createElement)("span",{class:"activitypub-actor"},(0,t.createElement)("strong",{className:"activitypub-name"},a),(0,t.createElement)("span",{class:"sep"},"/"),(0,t.createElement)("span",{class:"activitypub-handle"},s)))}const _=window._activityPubOptions?.enabled;(0,e.registerBlockType)("activitypub/followers",{edit:function(e){let{attributes:a,setAttributes:n}=e;const{order:l,per_page:p,selectedUser:u,title:v}=a,m=(0,c.useBlockProps)(),[b,w]=(0,t.useState)(1),d=[{label:(0,s.__)("New to old","activitypub"),value:"desc"},{label:(0,s.__)("Old to new","activitypub"),value:"asc"}],g=function(){const e=_?.users?(0,i.useSelect)((e=>e("core").getUsers({who:"authors"}))):[];return(0,t.useMemo)((()=>{if(!e)return[];const t=_?.site?[{label:(0,s.__)("Whole Site","activitypub"),value:"site"}]:[];return e.reduce(((e,t)=>(e.push({label:t.name,value:t.id}),e)),t)}),[e])}(),f=e=>t=>{w(1),n({[e]:t})};return(0,t.createElement)("div",m,(0,t.createElement)(c.InspectorControls,{key:"setting"},(0,t.createElement)(o.PanelBody,{title:(0,s.__)("Followers Options","activitypub")},(0,t.createElement)(o.TextControl,{label:(0,s.__)("Title","activitypub"),help:(0,s.__)("Title to display above the list of followers. Blank for none.","activitypub"),value:v,onChange:e=>n({title:e})}),(0,t.createElement)(o.SelectControl,{label:(0,s.__)("Select User","activitypub"),value:u,options:g,onChange:f("selectedUser")}),(0,t.createElement)(o.SelectControl,{label:(0,s.__)("Sort","activitypub"),value:l,options:d,onChange:f("order")}),(0,t.createElement)(o.RangeControl,{label:(0,s.__)("Number of Followers","activitypub"),value:p,onChange:f("per_page"),min:1,max:10}))),(0,t.createElement)(h,r({},a,{page:b,setPage:w,followLinks:!1})))},save:()=>null,icon:l})})()})(); \ No newline at end of file +(()=>{var e={184:(e,t)=>{var a;!function(){"use strict";var n={}.hasOwnProperty;function l(){for(var e=[],t=0;t{var t=e&&e.__esModule?()=>e.default:()=>e;return a.d(t,{a:t}),t},a.d=(e,t)=>{for(var n in t)a.o(t,n)&&!a.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},a.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";const e=window.wp.blocks,t=window.wp.element,n=window.wp.primitives,l=(0,t.createElement)(n.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,t.createElement)(n.Path,{d:"M15.5 9.5a1 1 0 100-2 1 1 0 000 2zm0 1.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zm-2.25 6v-2a2.75 2.75 0 00-2.75-2.75h-4A2.75 2.75 0 003.75 15v2h1.5v-2c0-.69.56-1.25 1.25-1.25h4c.69 0 1.25.56 1.25 1.25v2h1.5zm7-2v2h-1.5v-2c0-.69-.56-1.25-1.25-1.25H15v-1.5h2.5A2.75 2.75 0 0120.25 15zM9.5 8.5a1 1 0 11-2 0 1 1 0 012 0zm1.5 0a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z",fillRule:"evenodd"}));function r(){return r=Object.assign?Object.assign.bind():function(e){for(var t=1;t{e.preventDefault(),!a&&r(l)}},n)}const d={outlined:"outlined",minimal:"minimal"};function g(e){let{compact:a,nextLabel:n,page:l,pageClick:r,perPage:o,prevLabel:i,total:c,variant:s=d.outlined}=e;const p=((e,t)=>{let a=[1,e-2,e-1,e,e+1,e+2,t];a.sort(((e,t)=>e-t)),a=a.filter(((e,a,n)=>e>=1&&e<=t&&n.lastIndexOf(e)===a));for(let e=a.length-2;e>=0;e--)a[e]===a[e+1]&&a.splice(e+1,1);return a})(l,Math.ceil(c/o)),u=b()("alignwide wp-block-query-pagination is-content-justification-space-between is-layout-flex wp-block-query-pagination-is-layout-flex",`is-${s}`,{"is-compact":a});return(0,t.createElement)("nav",{className:u},i&&(0,t.createElement)(w,{key:"prev",page:l-1,pageClick:r,active:1===l,"aria-label":i,className:"wp-block-query-pagination-previous block-editor-block-list__block"},i),!a&&(0,t.createElement)("div",{className:"block-editor-block-list__block wp-block wp-block-query-pagination-numbers"},p.map((e=>(0,t.createElement)(w,{key:e,page:e,pageClick:r,active:e===l,className:"page-numbers"},e)))),n&&(0,t.createElement)(w,{key:"next",page:l+1,pageClick:r,active:l===Math.ceil(c/o),"aria-label":n,className:"wp-block-query-pagination-next block-editor-block-list__block"},n))}const{namespace:f}=window._activityPubOptions;function y(e){let{selectedUser:a,per_page:n,order:l,title:o,page:i,setPage:p,className:m="",followLinks:b=!0}=e;const w="site"===a?0:a,[d,y]=(0,s.useState)([]),[k,_]=(0,s.useState)(0),[E,x]=(0,s.useState)(0),[C,S]=function(){const[e,t]=(0,s.useState)(1);return[e,t]}(),O=i||C,N=p||S,P=(0,t.createInterpolateElement)(/* translators: arrow for previous followers link */ +(0,c.__)(" Less","activitypub"),{span:(0,t.createElement)("span",{class:"wp-block-query-pagination-previous-arrow is-arrow-arrow","aria-hidden":"true"})}),L=(0,t.createInterpolateElement)(/* translators: arrow for next followers link */ +(0,c.__)("More ","activitypub"),{span:(0,t.createElement)("span",{class:"wp-block-query-pagination-next-arrow is-arrow-arrow","aria-hidden":"true"})});return(0,s.useEffect)((()=>{const e=function(e,t,a,n){const l=`/${f}/users/${e}/followers`,r={per_page:t,order:a,page:n,context:"full"};return(0,v.addQueryArgs)(l,r)}(w,n,l,O);u()({path:e}).then((e=>{_(Math.ceil(e.totalItems/n)),x(e.totalItems),y(e.orderedItems)})).catch((()=>{}))}),[w,n,l,O]),(0,t.createElement)("div",{className:"activitypub-follower-block "+m},(0,t.createElement)("h3",null,o),(0,t.createElement)("ul",null,d&&d.map((e=>(0,t.createElement)("li",{key:e.url},(0,t.createElement)(h,r({},e,{followLinks:b})))))),k>1&&(0,t.createElement)(g,{page:O,perPage:n,total:E,pageClick:N,nextLabel:L,prevLabel:P,compact:"is-style-compact"===m}))}function h(e){let{name:a,icon:n,url:l,preferredUsername:i,followLinks:c=!0}=e;const s=`@${i}`,p={};return c||(p.onClick=e=>e.preventDefault()),(0,t.createElement)(o.ExternalLink,r({className:"activitypub-link",href:l,title:s},p),(0,t.createElement)("img",{width:"40",height:"40",src:n.url,class:"avatar activitypub-avatar"}),(0,t.createElement)("span",{class:"activitypub-actor"},(0,t.createElement)("strong",{className:"activitypub-name"},a),(0,t.createElement)("span",{class:"sep"},"/"),(0,t.createElement)("span",{class:"activitypub-handle"},s)))}const k=window.wp.data,_=window._activityPubOptions?.enabled;(0,e.registerBlockType)("activitypub/followers",{edit:function(e){let{attributes:a,setAttributes:n}=e;const{order:l,per_page:s,selectedUser:p,title:u}=a,v=(0,i.useBlockProps)(),[m,b]=(0,t.useState)(1),w=[{label:(0,c.__)("New to old","activitypub"),value:"desc"},{label:(0,c.__)("Old to new","activitypub"),value:"asc"}],d=function(){const e=_?.users?(0,k.useSelect)((e=>e("core").getUsers({who:"authors"}))):[];return(0,t.useMemo)((()=>{if(!e)return[];const t=_?.site?[{label:(0,c.__)("Whole Site","activitypub"),value:"site"}]:[];return e.reduce(((e,t)=>(e.push({label:t.name,value:t.id}),e)),t)}),[e])}(),g=e=>t=>{b(1),n({[e]:t})};return(0,t.createElement)("div",v,(0,t.createElement)(i.InspectorControls,{key:"setting"},(0,t.createElement)(o.PanelBody,{title:(0,c.__)("Followers Options","activitypub")},(0,t.createElement)(o.TextControl,{label:(0,c.__)("Title","activitypub"),help:(0,c.__)("Title to display above the list of followers. Blank for none.","activitypub"),value:u,onChange:e=>n({title:e})}),(0,t.createElement)(o.SelectControl,{label:(0,c.__)("Select User","activitypub"),value:p,options:d,onChange:g("selectedUser")}),(0,t.createElement)(o.SelectControl,{label:(0,c.__)("Sort","activitypub"),value:l,options:w,onChange:g("order")}),(0,t.createElement)(o.RangeControl,{label:(0,c.__)("Number of Followers","activitypub"),value:s,onChange:g("per_page"),min:1,max:10}))),(0,t.createElement)(y,r({},a,{page:m,setPage:b,followLinks:!1})))},save:()=>null,icon:l})})()})(); \ No newline at end of file diff --git a/build/followers/view.asset.php b/build/followers/view.asset.php index 7f57812..32ae6d5 100644 --- a/build/followers/view.asset.php +++ b/build/followers/view.asset.php @@ -1 +1 @@ - array('react', 'wp-api-fetch', 'wp-components', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => 'd645fd4aa610b479e8f4'); + array('react', 'wp-api-fetch', 'wp-components', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '6384e801c2802d2fecee'); diff --git a/build/followers/view.js b/build/followers/view.js index f28d93f..29367a8 100644 --- a/build/followers/view.js +++ b/build/followers/view.js @@ -1,3 +1,3 @@ -(()=>{var e,t={189:(e,t,a)=>{"use strict";const r=window.wp.element;function n(){return n=Object.assign?Object.assign.bind():function(e){for(var t=1;t{e.preventDefault(),!t&&l(n)}},a)}const v={outlined:"outlined",minimal:"minimal"};function b(e){let{compact:t,nextLabel:a,page:n,pageClick:l,perPage:o,prevLabel:i,total:c,variant:s=v.outlined}=e;const p=((e,t)=>{let a=[1,e-2,e-1,e,e+1,e+2,t];a.sort(((e,t)=>e-t)),a=a.filter(((e,a,r)=>e>=1&&e<=t&&r.lastIndexOf(e)===a));for(let e=a.length-2;e>=0;e--)a[e]===a[e+1]&&a.splice(e+1,1);return a})(n,Math.ceil(c/o)),b=u()("alignwide wp-block-query-pagination is-content-justification-space-between is-layout-flex wp-block-query-pagination-is-layout-flex",`is-${s}`,{"is-compact":t});return(0,r.createElement)("nav",{className:b},i&&(0,r.createElement)(m,{key:"prev",page:n-1,pageClick:l,active:1===n,"aria-label":i,className:"wp-block-query-pagination-previous block-editor-block-list__block"},i),!t&&(0,r.createElement)("div",{className:"block-editor-block-list__block wp-block wp-block-query-pagination-numbers"},p.map((e=>(0,r.createElement)(m,{key:e,page:e,pageClick:l,active:e===n,className:"page-numbers"},e)))),a&&(0,r.createElement)(m,{key:"next",page:n+1,pageClick:l,active:n===Math.ceil(c/o),"aria-label":a,className:"wp-block-query-pagination-next block-editor-block-list__block"},a))}const f=window.wp.components,{namespace:d}=window._activityPubOptions;function w(e){let{selectedUser:t,per_page:a,order:o,title:p,page:u,setPage:m,className:v="",followLinks:f=!0}=e;const w="site"===t?0:t,[y,k]=(0,l.useState)([]),[h,E]=(0,l.useState)(0),[O,x]=(0,l.useState)(0),[_,N]=function(){const[e,t]=(0,l.useState)(1);return[e,t]}(),j=u||_,S=m||N,C=(0,r.createInterpolateElement)(/* translators: arrow for previous followers link */ +(()=>{var e,t={189:(e,t,a)=>{"use strict";const r=window.wp.element;function n(){return n=Object.assign?Object.assign.bind():function(e){for(var t=1;t{e.preventDefault(),!t&&l(n)}},a)}const v={outlined:"outlined",minimal:"minimal"};function b(e){let{compact:t,nextLabel:a,page:n,pageClick:l,perPage:i,prevLabel:o,total:c,variant:s=v.outlined}=e;const p=((e,t)=>{let a=[1,e-2,e-1,e,e+1,e+2,t];a.sort(((e,t)=>e-t)),a=a.filter(((e,a,r)=>e>=1&&e<=t&&r.lastIndexOf(e)===a));for(let e=a.length-2;e>=0;e--)a[e]===a[e+1]&&a.splice(e+1,1);return a})(n,Math.ceil(c/i)),b=u()("alignwide wp-block-query-pagination is-content-justification-space-between is-layout-flex wp-block-query-pagination-is-layout-flex",`is-${s}`,{"is-compact":t});return(0,r.createElement)("nav",{className:b},o&&(0,r.createElement)(m,{key:"prev",page:n-1,pageClick:l,active:1===n,"aria-label":o,className:"wp-block-query-pagination-previous block-editor-block-list__block"},o),!t&&(0,r.createElement)("div",{className:"block-editor-block-list__block wp-block wp-block-query-pagination-numbers"},p.map((e=>(0,r.createElement)(m,{key:e,page:e,pageClick:l,active:e===n,className:"page-numbers"},e)))),a&&(0,r.createElement)(m,{key:"next",page:n+1,pageClick:l,active:n===Math.ceil(c/i),"aria-label":a,className:"wp-block-query-pagination-next block-editor-block-list__block"},a))}const f=window.wp.components,{namespace:d}=window._activityPubOptions;function w(e){let{selectedUser:t,per_page:a,order:i,title:p,page:u,setPage:m,className:v="",followLinks:f=!0}=e;const w="site"===t?0:t,[y,k]=(0,l.useState)([]),[h,E]=(0,l.useState)(0),[O,x]=(0,l.useState)(0),[_,N]=function(){const[e,t]=(0,l.useState)(1);return[e,t]}(),j=u||_,S=m||N,C=(0,r.createInterpolateElement)(/* translators: arrow for previous followers link */ (0,s.__)(" Less","activitypub"),{span:(0,r.createElement)("span",{class:"wp-block-query-pagination-previous-arrow is-arrow-arrow","aria-hidden":"true"})}),L=(0,r.createInterpolateElement)(/* translators: arrow for next followers link */ -(0,s.__)("More ","activitypub"),{span:(0,r.createElement)("span",{class:"wp-block-query-pagination-next-arrow is-arrow-arrow","aria-hidden":"true"})});return(0,l.useEffect)((()=>{const e=function(e,t,a,r){const n=`/${d}/users/${e}/followers`,l={per_page:t,order:a,page:r,context:"full"};return(0,c.addQueryArgs)(n,l)}(w,a,o,j);i()({path:e}).then((e=>{E(Math.ceil(e.totalItems/a)),x(e.totalItems),k(e.orderedItems)})).catch((e=>console.error(e)))}),[w,a,o,j]),(0,r.createElement)("div",{className:"activitypub-follower-block "+v},(0,r.createElement)("h3",null,p),(0,r.createElement)("ul",null,y&&y.map((e=>(0,r.createElement)("li",{key:e.url},(0,r.createElement)(g,n({},e,{followLinks:f})))))),h>1&&(0,r.createElement)(b,{page:j,perPage:a,total:O,pageClick:S,nextLabel:L,prevLabel:C,compact:"is-style-compact"===v}))}function g(e){let{name:t,icon:a,url:l,preferredUsername:o,followLinks:i=!0}=e;const c=`@${o}`,s={};return i||(s.onClick=e=>e.preventDefault()),(0,r.createElement)(f.ExternalLink,n({className:"activitypub-link",href:l,title:c},s),(0,r.createElement)("img",{width:"40",height:"40",src:a.url,class:"avatar activitypub-avatar"}),(0,r.createElement)("span",{class:"activitypub-actor"},(0,r.createElement)("strong",{className:"activitypub-name"},t),(0,r.createElement)("span",{class:"sep"},"/"),(0,r.createElement)("span",{class:"activitypub-handle"},c)))}const y=window.wp.domReady;a.n(y)()((()=>{[].forEach.call(document.querySelectorAll(".activitypub-follower-block"),(e=>{const t=JSON.parse(e.dataset.attrs);(0,r.render)((0,r.createElement)(w,t),e)}))}))},184:(e,t)=>{var a;!function(){"use strict";var r={}.hasOwnProperty;function n(){for(var e=[],t=0;t{if(!a){var o=1/0;for(p=0;p=l)&&Object.keys(r.O).every((e=>r.O[e](a[c])))?a.splice(c--,1):(i=!1,l0&&e[p-1][2]>l;p--)e[p]=e[p-1];e[p]=[a,n,l]},r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var a in t)r.o(t,a)&&!r.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={638:0,962:0};r.O.j=t=>0===e[t];var t=(t,a)=>{var n,l,[o,i,c]=a,s=0;if(o.some((t=>0!==e[t]))){for(n in i)r.o(i,n)&&(r.m[n]=i[n]);if(c)var p=c(r)}for(t&&t(a);sr(189)));n=r.O(n)})(); \ No newline at end of file +(0,s.__)("More ","activitypub"),{span:(0,r.createElement)("span",{class:"wp-block-query-pagination-next-arrow is-arrow-arrow","aria-hidden":"true"})});return(0,l.useEffect)((()=>{const e=function(e,t,a,r){const n=`/${d}/users/${e}/followers`,l={per_page:t,order:a,page:r,context:"full"};return(0,c.addQueryArgs)(n,l)}(w,a,i,j);o()({path:e}).then((e=>{E(Math.ceil(e.totalItems/a)),x(e.totalItems),k(e.orderedItems)})).catch((()=>{}))}),[w,a,i,j]),(0,r.createElement)("div",{className:"activitypub-follower-block "+v},(0,r.createElement)("h3",null,p),(0,r.createElement)("ul",null,y&&y.map((e=>(0,r.createElement)("li",{key:e.url},(0,r.createElement)(g,n({},e,{followLinks:f})))))),h>1&&(0,r.createElement)(b,{page:j,perPage:a,total:O,pageClick:S,nextLabel:L,prevLabel:C,compact:"is-style-compact"===v}))}function g(e){let{name:t,icon:a,url:l,preferredUsername:i,followLinks:o=!0}=e;const c=`@${i}`,s={};return o||(s.onClick=e=>e.preventDefault()),(0,r.createElement)(f.ExternalLink,n({className:"activitypub-link",href:l,title:c},s),(0,r.createElement)("img",{width:"40",height:"40",src:a.url,class:"avatar activitypub-avatar"}),(0,r.createElement)("span",{class:"activitypub-actor"},(0,r.createElement)("strong",{className:"activitypub-name"},t),(0,r.createElement)("span",{class:"sep"},"/"),(0,r.createElement)("span",{class:"activitypub-handle"},c)))}const y=window.wp.domReady;a.n(y)()((()=>{[].forEach.call(document.querySelectorAll(".activitypub-follower-block"),(e=>{const t=JSON.parse(e.dataset.attrs);(0,r.render)((0,r.createElement)(w,t),e)}))}))},184:(e,t)=>{var a;!function(){"use strict";var r={}.hasOwnProperty;function n(){for(var e=[],t=0;t{if(!a){var i=1/0;for(p=0;p=l)&&Object.keys(r.O).every((e=>r.O[e](a[c])))?a.splice(c--,1):(o=!1,l0&&e[p-1][2]>l;p--)e[p]=e[p-1];e[p]=[a,n,l]},r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var a in t)r.o(t,a)&&!r.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={638:0,962:0};r.O.j=t=>0===e[t];var t=(t,a)=>{var n,l,[i,o,c]=a,s=0;if(i.some((t=>0!==e[t]))){for(n in o)r.o(o,n)&&(r.m[n]=o[n]);if(c)var p=c(r)}for(t&&t(a);sr(189)));n=r.O(n)})(); \ No newline at end of file diff --git a/composer.json b/composer.json index d34d110..054226f 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,9 @@ ], "lint": [ "vendor/bin/phpcs -n -q" + ], + "lint:fix": [ + "vendor/bin/phpcbf" ] } } diff --git a/includes/class-blocks.php b/includes/class-blocks.php index a59c4df..fca634c 100644 --- a/includes/class-blocks.php +++ b/includes/class-blocks.php @@ -13,7 +13,9 @@ class Blocks { } public static function add_data() { - $handle = is_admin() ? 'activitypub-followers-editor-script' : 'activitypub-followers-view-script'; + $context = is_admin() ? 'editor' : 'view'; + $followers_handle = 'activitypub-followers-' . $context . '-script'; + $follow_me_handle = 'activitypub-follow-me-' . $context . '-script'; $data = array( 'namespace' => ACTIVITYPUB_REST_NAMESPACE, 'enabled' => array( @@ -22,7 +24,8 @@ class Blocks { ), ); $js = sprintf( 'var _activityPubOptions = %s;', wp_json_encode( $data ) ); - \wp_add_inline_script( $handle, $js, 'before' ); + \wp_add_inline_script( $followers_handle, $js, 'before' ); + \wp_add_inline_script( $follow_me_handle, $js, 'before' ); } public static function register_blocks() { @@ -32,6 +35,12 @@ class Blocks { 'render_callback' => array( self::class, 'render_follower_block' ), ) ); + \register_block_type_from_metadata( + ACTIVITYPUB_PLUGIN_DIR . '/build/follow-me', + array( + 'render_callback' => array( self::class, 'render_follow_me_block' ), + ) + ); } private static function get_user_id( $user_string ) { @@ -42,7 +51,24 @@ class Blocks { return 0; } - public static function render_follower_block( $attrs, $content, $block ) { + /** + * Render the follow me block. + * @param array $attrs The block attributes. + * @return string The HTML to render. + */ + public static function render_follow_me_block( $attrs ) { + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'aria-label' => __( 'Follow me on the Fediverse', 'activitypub' ), + 'class' => 'activitypub-follow-me-block-wrapper', + 'data-attrs' => wp_json_encode( $attrs ), + ) + ); + // todo: render more than an empty div? + return '
'; + } + + public static function render_follower_block( $attrs ) { $followee_user_id = self::get_user_id( $attrs['selectedUser'] ); $per_page = absint( $attrs['per_page'] ); $followers = Followers::get_followers( $followee_user_id, $per_page ); @@ -55,7 +81,7 @@ class Blocks { ) ); - $html = '
'; + $html = '
'; if ( $title ) { $html .= '

' . $title . '

'; } diff --git a/includes/class-webfinger.php b/includes/class-webfinger.php index c077f12..c041290 100644 --- a/includes/class-webfinger.php +++ b/includes/class-webfinger.php @@ -26,7 +26,7 @@ class Webfinger { } $user = Users::get_by_id( $user_id ); - if ( ! $user ) { + if ( ! $user || is_wp_error( $user ) ) { return ''; } @@ -198,6 +198,6 @@ class Webfinger { } } - return new WP_Error( 'webfinger_remote_follow_endpoint_invalid', null, $data ); + return new WP_Error( 'webfinger_remote_follow_endpoint_invalid', $data, array( 'status' => 417 ) ); } } diff --git a/includes/model/class-user.php b/includes/model/class-user.php index 12fbb67..f1c657d 100644 --- a/includes/model/class-user.php +++ b/includes/model/class-user.php @@ -36,6 +36,13 @@ class User extends Actor { */ protected $featured; + /** + * The Webfinger-style identifier. + * + * @var string + */ + protected $resource; + /** * The User-Type * diff --git a/readme.txt b/readme.txt index 76ad06c..898485b 100644 --- a/readme.txt +++ b/readme.txt @@ -108,17 +108,18 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu = 1.0.0 = * Add: blog-wide Account (catchall, like `example.com@example.com`) -* Add: Signature Verification: https://docs.joinmastodon.org/spec/security/ . -* Add: a Followers Block. +* Add: a Follow Me block (help visitors to follow your Profile) +* Add: Signature Verification: https://docs.joinmastodon.org/spec/security/ +* Add: a Followers Block (show off your Followers) * Add: Simple caching * Add: Collection endpoints for Featured Tags and Featured Posts -* Update: Complete rewrite of the Follower-System based on Custom Post Types. +* Update: Complete rewrite of the Follower-System based on Custom Post Types * Update: Improved linter (PHPCS) -* Compatibility: Add a new conditional, `\Activitypub\is_activitypub_request()`, to allow third-party plugins to detect ActivityPub requests. -* Compatibility: Add hooks to allow modifying images returned in ActivityPub requests. -* Compatibility: Indicate that the plugin is compatible and has been tested with the latest version of WordPress, 6.3. -* Compatibility: Avoid PHP notice on sites using PHP 8.2. -* Fixed: Load the plugin later in the WordPress code lifecycle to avoid errors in some requests. +* Compatibility: Add a new conditional, `\Activitypub\is_activitypub_request()`, to allow third-party plugins to detect ActivityPub requests +* Compatibility: Add hooks to allow modifying images returned in ActivityPub requests +* Compatibility: Indicate that the plugin is compatible and has been tested with the latest version of WordPress, 6.3 +* Compatibility: Avoid PHP notice on sites using PHP 8.2 +* Fixed: Load the plugin later in the WordPress code lifecycle to avoid errors in some requests * Fixed: Updating posts * Fixed: Hashtag now support CamelCase and UTF-8 diff --git a/src/follow-me/block.json b/src/follow-me/block.json new file mode 100644 index 0000000..a728f13 --- /dev/null +++ b/src/follow-me/block.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "name": "activitypub/follow-me", + "apiVersion": 3, + "version": "1.0.0", + "title": "Follow me on the Fediverse", + "category": "widgets", + "description": "Display your Fediverse profile so that visitors can follow you.", + "textdomain": "activitypub", + "icon": "groups", + "supports": { + "html": false, + "color": { + "gradients": true, + "link": true, + "__experimentalDefaultControls": { + "background": true, + "text": true, + "link": true + } + }, + "__experimentalBorder": { + "radius": true, + "width": true, + "color": true, + "style": true + } + }, + "attributes": { + "selectedUser": { + "type": "string", + "default": "site" + } + }, + "editorScript": "file:./index.js", + "viewScript": "file:./view.js", + "style": ["file:./style-index.css", "wp-components"] +} \ No newline at end of file diff --git a/src/follow-me/button-style.js b/src/follow-me/button-style.js new file mode 100644 index 0000000..0c33ca2 --- /dev/null +++ b/src/follow-me/button-style.js @@ -0,0 +1,75 @@ +function presetVarColorCss( color ) { + return `var(--wp--preset--color--${ color })`; +} + +function getBackgroundColor( color ) { + // if color is a string, it's a var like this. + if ( typeof color === 'string' ) { + return presetVarColorCss( color ); + } + + return color?.color?.background || null; +} + +function getLinkColor( text ) { + if ( typeof text !== 'string' ) { + return null; + } + // if it starts with a hash, leave it be + if ( text.match( /^#/ ) ) { + // we don't handle the alpha channel if present. + return text.substring( 0, 7 ); + } + // var:preset|color|luminous-vivid-amber + // var(--wp--preset--color--luminous-vivid-amber) + // we will receive the top format, we need to output the bottom format + const [ , , color ] = text.split( '|' ); + return presetVarColorCss( color ); +} + +function generateSelector( selector, prop, value = null, pseudo = '' ) { + if ( ! value ) { + return ''; + } + return `${ selector }${ pseudo } { ${ prop }: ${ value }; }\n`; +} + +function getStyles( selector, button, text, hover ) { + return generateSelector( selector, 'background-color', button ) + + generateSelector( selector, 'color', text ) + + generateSelector( selector, 'background-color', hover, ':hover' ) + + generateSelector( selector, 'background-color', hover, ':focus' ); +} + +function getBlockStyles( base, style, backgroundColor ) { + const selector = `${ base } .components-button`; + // we grab the background color if set as a good color for our button text + const buttonTextColor = getBackgroundColor( backgroundColor ) + // bg might be in this form. + || style?.color?.background; + // we misuse the link color for the button background + const buttonColor = getLinkColor( style?.elements?.link?.color?.text ); + // hover! + const buttonHoverColor = getLinkColor( style?.elements?.link?.[':hover']?.color?.text ); + + return getStyles( selector, buttonColor, buttonTextColor, buttonHoverColor ); +} + +export function getPopupStyles( style ) { + // we don't acept backgroundColor because the popup is always white (right?) + const buttonColor = getLinkColor( style?.elements?.link?.color?.text ) + || '#111'; + const buttonTextColor = '#fff'; + const buttonHoverColor = getLinkColor( style?.elements?.link?.[':hover']?.color?.text ) + || '#333'; + const selector = '.apfmd__button-group .components-button'; + + return getStyles( selector, buttonColor, buttonTextColor, buttonHoverColor ); +} + +export function ButtonStyle( { selector, style, backgroundColor } ) { + const css = getBlockStyles( selector, style, backgroundColor ); + return ( + + ); +} \ No newline at end of file diff --git a/src/follow-me/edit.js b/src/follow-me/edit.js new file mode 100644 index 0000000..b15279a --- /dev/null +++ b/src/follow-me/edit.js @@ -0,0 +1,26 @@ +import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; +import { SelectControl, PanelBody } from '@wordpress/components'; +import { useUserOptions } from '../shared/use-user-options'; +import FollowMe from './follow-me'; + +export default function Edit( { attributes, setAttributes } ) { + const blockProps = useBlockProps(); + const usersOptions = useUserOptions(); + + return ( +
+ + + setAttributes( { selectedUser: value } ) } + /> + + + +
+ ); +} diff --git a/src/follow-me/follow-me.js b/src/follow-me/follow-me.js new file mode 100644 index 0000000..8b7a505 --- /dev/null +++ b/src/follow-me/follow-me.js @@ -0,0 +1,178 @@ + +import apiFetch from '@wordpress/api-fetch'; +import { useCallback, useEffect, useState, createInterpolateElement } from '@wordpress/element'; +import { Button, Modal } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { copy, check, Icon } from '@wordpress/icons'; +import { useCopyToClipboard } from '@wordpress/compose'; +import { ButtonStyle, getPopupStyles } from './button-style'; +import './style.scss'; +const { namespace } = window._activityPubOptions; + +const DEFAULT_PROFILE_DATA = { + avatar: '', + resource: '@well@hello.dolly', + name: __( 'Hello Dolly Fan Account', 'activitypub' ), + url: '#', +}; + +function getNormalizedProfile( profile ) { + if ( ! profile ) { + return DEFAULT_PROFILE_DATA; + } + const data = { ...DEFAULT_PROFILE_DATA, ...profile }; + data.avatar = data?.icon?.url; + return data; +} + +function fetchProfile( userId ) { + const fetchOptions = { + headers: { Accept: 'application/activity+json' }, + path: `/${ namespace }/users/${ userId }`, + }; + return apiFetch( fetchOptions ); +} + +function Profile( { profile, popupStyles, userId } ) { + const { avatar, name, resource } = profile; + return ( +
+ +
+
{ name }
+
{ resource }
+
+ +
+ ); +} + +function Follow( { profile, popupStyles, userId } ) { + const [ isOpen, setIsOpen ] = useState( false ); + const title = sprintf( __( 'Follow %s', 'activitypub' ), profile?.name ); + + return ( + <> + + { isOpen && ( + setIsOpen( false ) } + title={ title } + > + + + + ) } + + ); +} + +function isUrl( string ) { + try { + new URL( string ); + return true; + } catch ( _ ) { + return false; + } +} + +function isHandle( string ) { + // remove leading @, there should still be an @ in there + const parts = string.replace( /^@/, '' ).split( '@' ); + return parts.length === 2 && isUrl( `https://${ parts[ 1 ] }` ); +} + +function Dialog( { profile, userId } ) { + const { resource } = profile; + const followText = __( 'Follow', 'activitypub' ); + const loadingText = __( 'Loading...', 'activitypub' ); + const openingText = __( 'Opening...', 'activitypub' ); + const errorText = __( 'Error', 'activitypub' ); + const invalidText = __( 'Invalid', 'activitypub' ); + const [ buttonText, setButtonText ] = useState( followText ); + const [ buttonIcon, setButtonIcon ] = useState( copy ); + const ref = useCopyToClipboard( resource, () => { + setButtonIcon( check ); + setTimeout( () => setButtonIcon( copy ), 1000 ); + } ); + const [ remoteProfile, setRemoteProfile ] = useState( '' ); + const retrieveAndFollow = useCallback( () => { + let timeout; + if ( ! ( isUrl( remoteProfile ) || isHandle( remoteProfile ) ) ) { + setButtonText( invalidText ); + timeout = setTimeout( () => setButtonText( followText ), 2000 ); + return () => clearTimeout( timeout ); + } + const path = `/${ namespace }/users/${userId}/remote-follow?resource=${ remoteProfile }`; + setButtonText( loadingText ); + apiFetch( { path } ).then( ( { url } ) => { + setButtonText( openingText ); + setTimeout( () => { + window.open( url, '_blank' ); + setButtonText( followText ); + }, 200 ); + } ).catch( () => { + setButtonText( errorText ); + setTimeout( () => setButtonText( followText ), 2000 ); + } ); + }, [ remoteProfile ] ); + + return ( +
+
+

{ __( 'My Profile', 'activitypub' ) }

+
+ { __( 'Copy and paste my profile into the search field of your favorite fediverse app or server.', 'activitypub' ) } +
+
+ + +
+
+
+

{ __( 'Your Profile', 'activitypub' ) }

+
+ { createInterpolateElement( + __( 'Or, if you know your own profile, we can start things that way! (eg https://example.com/yourusername or yourusername@example.com)', 'activitypub' ), + { code: } + ) } +
+
+ { event?.code === 'Enter' && retrieveAndFollow() } } + onChange={ e => setRemoteProfile( e.target.value ) } + /> + +
+
+
+ ); +} + +export default function FollowMe( { selectedUser, style, backgroundColor, id, useId = false } ) { + const [ profile, setProfile ] = useState( getNormalizedProfile() ); + const userId = selectedUser === 'site' ? 0 : selectedUser; + const popupStyles = getPopupStyles( style ); + const wrapperProps = useId ? { id } : {}; + function setProfileData( profile ) { + setProfile( getNormalizedProfile( profile ) ); + } + useEffect( () => { + fetchProfile( userId ).then( setProfileData ); + }, [ userId ] ); + + return( +
+ + +
+ ) +} \ No newline at end of file diff --git a/src/follow-me/index.js b/src/follow-me/index.js new file mode 100644 index 0000000..550af60 --- /dev/null +++ b/src/follow-me/index.js @@ -0,0 +1,5 @@ +import { registerBlockType } from '@wordpress/blocks'; +import { people } from '@wordpress/icons'; +import edit from './edit'; +const save = () => null; +registerBlockType( 'activitypub/follow-me', { edit, save, icon: people } ); \ No newline at end of file diff --git a/src/follow-me/style.scss b/src/follow-me/style.scss new file mode 100644 index 0000000..9a29f4b --- /dev/null +++ b/src/follow-me/style.scss @@ -0,0 +1,57 @@ +.editor-styles-wrapper, .activitypub-follow-me-block-wrapper { + .activitypub-profile { + display: flex; + align-items: self-start; + padding: 1rem; + + .activitypub-profile__avatar { + height: 75px; + width: 75px; + margin-right: 1rem; + } + .activitypub-profile__name { + margin: 0; + line-height: 1; + font-size: var( --wp--preset--font-size--large ); + } + .activitypub-profile__follow { + margin-left: auto; + align-self: center; + background-color: var(--wp--preset--color--black); + color: var(--wp--preset--color--white); + } + } +} + +.activitypub-follow-me__dialog { + max-width: 30em; + h4 { + line-height: 1; + margin: 0; + } + .apmfd__section { + margin-bottom: 2em; + } + .apfmd-description { + font-size: var( --wp--preset--font-size--normal, .75rem ); + margin: 0.33em 0 1em; + } + .apfmd__button-group { + display: flex; + justify-content: flex-end; + + svg { + margin-right: .5em; + height: 21px; + width: 21px; + } + + input { + flex: 1; + padding: { + left: 1em; + right: 1em; + } + } + } +} diff --git a/src/follow-me/view.js b/src/follow-me/view.js new file mode 100644 index 0000000..418e45c --- /dev/null +++ b/src/follow-me/view.js @@ -0,0 +1,16 @@ +import { render } from '@wordpress/element'; +import domReady from '@wordpress/dom-ready'; +import FollowMe from './follow-me'; + +let id = 1; +function getUniqueId() { + return `activitypub-follow-me-block-${ id++ }`; +} + +domReady( () => { + // iterate over a nodelist + [].forEach.call( document.querySelectorAll( '.activitypub-follow-me-block-wrapper' ), ( element ) => { + const attrs = JSON.parse( element.dataset.attrs ); + render( , element ); + } ); +} ); \ No newline at end of file diff --git a/src/followers/edit.js b/src/followers/edit.js index 80156bb..1b72d71 100644 --- a/src/followers/edit.js +++ b/src/followers/edit.js @@ -1,31 +1,9 @@ import { SelectControl, RangeControl, PanelBody, TextControl } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; -import { useMemo, useState } from '@wordpress/element'; +import { useState } from '@wordpress/element'; import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { Followers } from './followers'; - -const enabled = window._activityPubOptions?.enabled; - -function useUserOptions() { - const users = enabled?.users ? useSelect( ( select ) => select( 'core' ).getUsers( { who: 'authors' } ) ) : []; - return useMemo( () => { - if ( ! users ) { - return []; - } - const withBlogUser = enabled?.site ? [ { - label: __( 'Whole Site', 'activitypub' ), - value: 'site' - } ] : []; - return users.reduce( ( acc, user ) => { - acc.push({ - label: user.name, - value: user.id - } ); - return acc; - }, withBlogUser ); - }, [ users ] ); -} +import { useUserOptions } from '../shared/use-user-options'; export default function Edit( { attributes, setAttributes } ) { const { order, per_page, selectedUser, title } = attributes; diff --git a/src/followers/followers.js b/src/followers/followers.js index 445537b..bc555b8 100644 --- a/src/followers/followers.js +++ b/src/followers/followers.js @@ -64,7 +64,7 @@ export function Followers( { setTotal( data.totalItems ); setFollowers( data.orderedItems ); } ) - .catch( ( error ) => console.error( error ) ); + .catch( () => {} ); }, [ userId, per_page, order, page ] ); return (
diff --git a/src/shared/use-user-options.js b/src/shared/use-user-options.js new file mode 100644 index 0000000..c43723b --- /dev/null +++ b/src/shared/use-user-options.js @@ -0,0 +1,24 @@ +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; +const enabled = window._activityPubOptions?.enabled; + +export function useUserOptions() { + const users = enabled?.users ? useSelect( ( select ) => select( 'core' ).getUsers( { who: 'authors' } ) ) : []; + return useMemo( () => { + if ( ! users ) { + return []; + } + const withBlogUser = enabled?.site ? [ { + label: __( 'Whole Site', 'activitypub' ), + value: 'site' + } ] : []; + return users.reduce( ( acc, user ) => { + acc.push({ + label: user.name, + value: user.id + } ); + return acc; + }, withBlogUser ); + }, [ users ] ); +} \ No newline at end of file