diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aed5d63..21ef38e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.6.0] - 2024-04-22 + +### Added +* Blocks for Co-Authors #997 + +### Fixed +* Improve alignment in author-selection component #990 +* Fix admin notice positions #1002 +* UI: Add capability check to Add New button #1003 +* i18n: set script translations and refresh language files #1007 + +### Maintenance +* Docs: Consolidate and refresh README.md #992 +* Refresh package.json #993 +* NPM: Add version-bump-prompt #994 +* Fix/general code tidy #995 +* Composer: Use 4-space tab indentation #999 +* Improve tests structure #1000 +* Extract iterator class from template-tags.php #1005 +* Add supported_post_types() method #1006 +* Tests improvements #1008 +* Bump postcss from 8.4.19 to 8.4.31 #1009 +* Create co-authors-plus-da_DK.po #1013 +* Increase minimum supported PHP version to 7.4 #954 +* Increase minimum supported WordPress version to 5.7 #955 + ## [3.5.15] - 2023-08-28 ### Fixed @@ -462,6 +488,7 @@ Props to the many people who helped make this release possible: [catchmyfame](ht **1.1.0 (Apr. 14, 2009)** * Initial beta release. +[3.6.0]: https://github.com/automattic/co-authors-plus/compare/3.5.15...3.6.0 [3.5.15]: https://github.com/automattic/co-authors-plus/compare/3.5.14...3.5.15 [3.5.14]: https://github.com/automattic/co-authors-plus/compare/3.5.13...3.5.14 [3.5.13]: https://github.com/automattic/co-authors-plus/compare/3.5.12...3.5.13 diff --git a/README.md b/README.md index 4593ab97..dccda58c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Co-Authors Plus -Stable tag: 3.5.15 +Stable tag: 3.6.0 Requires at least: 4.1 -Tested up to: 6.3 +Tested up to: 6.5 Requires PHP: 5.6 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -68,3 +68,87 @@ Yes! Guest authors can be disabled entirely through a filter. Having the followi ## Change Log [View the change log](https://github.com/Automattic/Co-Authors-Plus/blob/master/CHANGELOG.md). + +## Blocks + +### Co-Authors + +Use this block to create a repeating template that displays the co-authors of a post. By default it contains the Co-Author Name block, but you can add any other block you want to the template. If you choose another Co-Author block like avatar, biography or image it will automatically be supplied the author `context` that it needs. This works similarly to creating a Post Template in a Query Loop block. + +The Co-Authors Block supports two layouts: + +#### Inline Layout + +Use the inline layout to display co-authors in a list on a single wrapping line. + +You can control the characters displayed before, between and after co-authors in the list using the block settings, or change the defaults using the following server-side filters: + +``` +coauthors_default_before +coauthors_default_between +coauthors_default_between_last +coauthors_default_after +``` + +#### Block Layout + +Use the block layout to display co-authors in a vertical stack. While using the block layout you can use block spacing settings to control the vertical space between co-authors. + +Then you can create your own layout using blocks like group, row or stack and it will be applied to each co-author, similar to applying a layout to each post in a query loop. + +### Co-Author Name + +This block displays a co-author's `Display Name` and optionally turns it into a link to their author archive. + +Using the block's advanced settings you can select which HTML element is used to output the name. This is useful in contexts such as an author archive where you might want their name to be a heading. + +### Co-Author Avatar + +Like the post author avatar, or comment author avatar, this block displays a small scale square image of a co-author and utilizes the Gravatar default avatars as configured in your site's discussion options. + +To customize the available sizes, use the [rest_avatar_sizes](https://developer.wordpress.org/reference/hooks/rest_avatar_sizes/) filter. + +### Co-Author Biography + +This block outputs the biographical information for a co-author based on either their user or guest author data. + +The content is wrapped in paragraph elements using `wpautop` and is escaped using `wp_kses_post`. + +### Co-Author Featured Image + +This block requires the use of Guest Authors. Because guest author avatars are uploaded to the WordPress media library, there are more options for displaying these images. + +This block utilizes the image sizes configured in your theme and your site's media settings to present a guest author's avatar at a larger scale or higher resolution. It does not support Gravatars. + +## Block Context + +### Post, Page, Query Loop + +By default, all blocks receive the post context. The job of the Co-Authors Block is to use this context to find the relevant authors and provide context to its inner blocks. + +### Author Archive + +If you want to display data about the author on their own archive, use the individual co-author blocks directly without wrapping them in the Co-Authors Block. During requests for an author archive the correct context is derived from the `author_name` query variable and provided to all blocks that declare their use of the context `co-authors-plus/author`. + +### Extending + +If you make a custom block and want to use the author context, add `co-authors-plus/author` to the `usesContext` property in your block.json file. + +Example: +```json +{ + "usesContext": ["co-authors-plus/author"] +} +``` + +## Block Example Data + +When working with Full Site Editing, or in the post editor before the authors are loaded, example data is used. The example data provided with the co-author blocks resembles a response to the `/coauthors/v1/coauthors/:user-nicename` REST API endpoint. + +### Extending + +If you have written a plugin that modifies the REST API response, you can similarly modify the example data either on the server-side using the filter `coauthors_blocks_store_data` or the client-side using the filter `co-authors-plus.author-placeholder`. + +## Block Non-support + +To declare a lack of support for Co-Author Plus blocks on your site, use the filter `coauthors_plus_support_blocks` to return `false`. diff --git a/build/blocks-store/index.asset.php b/build/blocks-store/index.asset.php new file mode 100644 index 00000000..b14de6c9 --- /dev/null +++ b/build/blocks-store/index.asset.php @@ -0,0 +1 @@ + array('wp-data', 'wp-hooks'), 'version' => '82f1cda3f1d3fbb0136c'); diff --git a/build/blocks-store/index.js b/build/blocks-store/index.js new file mode 100644 index 00000000..f51ca210 --- /dev/null +++ b/build/blocks-store/index.js @@ -0,0 +1 @@ +!function(){"use strict";var o=window.wp.data,e=window.wp.hooks;(0,o.register)((0,o.createReduxStore)("co-authors-plus/blocks",{reducer:function(){let o=arguments.length>0&&void 0!==arguments[0]?arguments[0]:window.coAuthorsBlocks;return o},selectors:{getAuthorPlaceholder:o=>(0,e.applyFilters)("co-authors-plus.author-placeholder",o.authorPlaceholder)}}))}(); \ No newline at end of file diff --git a/build/blocks/block-coauthor-avatar/block.json b/build/blocks/block-coauthor-avatar/block.json new file mode 100644 index 00000000..0692c654 --- /dev/null +++ b/build/blocks/block-coauthor-avatar/block.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "co-authors-plus/avatar", + "version": "1.0.0", + "title": "Co-Author Avatar", + "category": "theme", + "description": "Displays a small scale version of a co-author's avatar. Utilizes fallbacks from Gravatar so everyone has an avatar.", + "keywords": [ + "coauthors" + ], + "supports": { + "html": false, + "__experimentalBorder": { + "color": true, + "radius": true, + "width": true, + "__experimentalSelector": "img, .block-editor-media-placeholder", + "__experimentalSkipSerialization": true, + "__experimentalDefaultControls": { + "color": false, + "radius": false, + "width": false + } + }, + "spacing": { + "margin": true, + "padding": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false + } + } + }, + "usesContext": [ + "co-authors-plus/author", + "co-authors-plus/layout" + ], + "attributes": { + "size": { + "type": "number", + "default": 24 + }, + "isLink": { + "type": "boolean", + "default": false + }, + "rel": { + "type": "string" + }, + "verticalAlign": { + "type": "string" + }, + "align": { + "type": "string" + } + }, + "textdomain": "co-authors-plus", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} \ No newline at end of file diff --git a/build/blocks/block-coauthor-avatar/index.asset.php b/build/blocks/block-coauthor-avatar/index.asset.php new file mode 100644 index 00000000..b60246a1 --- /dev/null +++ b/build/blocks/block-coauthor-avatar/index.asset.php @@ -0,0 +1 @@ + array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'b6b841ac8f86f7358245'); diff --git a/build/blocks/block-coauthor-avatar/index.js b/build/blocks/block-coauthor-avatar/index.js new file mode 100644 index 00000000..dcf076b9 --- /dev/null +++ b/build/blocks/block-coauthor-avatar/index.js @@ -0,0 +1 @@ +!function(){var e,t={601:function(e,t,l){"use strict";var o=window.wp.blocks,n=window.wp.element,r=window.wp.primitives,a=(0,n.createElement)(r.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,n.createElement)(r.Path,{fillRule:"evenodd",d:"M7.25 16.437a6.5 6.5 0 1 1 9.5 0V16A2.75 2.75 0 0 0 14 13.25h-4A2.75 2.75 0 0 0 7.25 16v.437Zm1.5 1.193a6.47 6.47 0 0 0 3.25.87 6.47 6.47 0 0 0 3.25-.87V16c0-.69-.56-1.25-1.25-1.25h-4c-.69 0-1.25.56-1.25 1.25v1.63ZM4 12a8 8 0 1 1 16 0 8 8 0 0 1-16 0Zm10-2a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z",clipRule:"evenodd"})),i=window.wp.i18n,u=window.wp.blockEditor,s=window.wp.components,c=window.wp.data,h=l(184),p=l.n(h);function v(e){let{dimensions:t,style:l,className:o}=e;const r=(0,n.useMemo)((()=>function(e){let{width:t,height:l}=e;return`data:image/svg+xml;charset=UTF-8,${encodeURIComponent(`\n\t\t\t\n\t\t\t\n\t\t`.replace(/[\t\n\r]/gim,"").replace(/\s\s+/g," ")).replace(/\(/g,"%28").replace(/\)/g,"%29")}`}(t)),[t]);return(0,n.createElement)("img",{alt:(0,i.__)("Placeholder image"),className:o,src:r,style:l,width:t.width,height:t.height})}var d=JSON.parse('{"u2":"co-authors-plus/avatar"}');(0,o.registerBlockType)(d.u2,{edit:function(e){var t;let{context:l,attributes:o,setAttributes:r}=e;const{isLink:a,rel:h,size:d,verticalAlign:g,align:f}=o,m=(0,c.useSelect)((e=>e("co-authors-plus/blocks").getAuthorPlaceholder()),[]),_=l["co-authors-plus/author"]||m,w=l["co-authors-plus/layout"]||"",{avatar_urls:b}=_;if(!b||0===b.length)return null;const y=Object.keys(b).map((e=>({value:e,label:`${e} x ${e}`}))),k=(0,u.__experimentalUseBorderProps)(o),x=null!==(t=b[d])&&void 0!==t?t:"";return(0,n.createElement)(n.Fragment,null,"default"!==w?(0,n.createElement)(u.BlockControls,null,(0,n.createElement)(u.BlockAlignmentToolbar,{value:f,onChange:e=>{r({align:e})},controls:["none","left","center","right"]})):null,(0,n.createElement)("div",(0,u.useBlockProps)({className:p()({[`align${f}`]:"default"!==w&&f&&"none"!==f})}),""===x?(0,n.createElement)(v,{className:k.className,dimensions:{width:d,height:d},style:{height:d,width:d,minWidth:"auto",minHeight:"auto",padding:0,verticalAlign:g,...k.style}}):(0,n.createElement)("img",{style:{...k.style,verticalAlign:g},width:d,height:d,src:`${b[d]}`})),(0,n.createElement)(u.InspectorControls,null,(0,n.createElement)(s.PanelBody,{title:(0,i.__)("Avatar Settings","co-authors-plus")},(0,n.createElement)(s.SelectControl,{label:(0,i.__)("Avatar size","co-authors-plus"),value:d,options:y,onChange:e=>{r({size:Number(e)})}}),(0,n.createElement)(s.ToggleControl,{label:(0,i.__)("Make avatar a link to author archive.","co-authors-plus"),onChange:()=>r({isLink:!a}),checked:a}),a&&(0,n.createElement)(s.TextControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Link rel","co-authors-plus"),value:h,onChange:e=>r({rel:e})})),"default"===w?(0,n.createElement)(s.PanelBody,{initialOpen:!1,title:(0,i.__)("Co-Authors Layout","co-authors-plus")},(0,n.createElement)(s.SelectControl,{label:(0,i.__)("Vertical align","co-authors-plus"),value:g,options:[{value:"",label:(0,i.__)("Default","co-authors-plus")},{value:"baseline",label:(0,i.__)("Baseline","co-authors-plus")},{value:"bottom",label:(0,i.__)("Bottom","co-authors-plus")},{value:"middle",label:(0,i.__)("Middle","co-authors-plus")},{value:"sub",label:(0,i.__)("Sub","co-authors-plus")},{value:"super",label:(0,i.__)("Super","co-authors-plus")},{value:"text-bottom",label:(0,i.__)("Text Bottom","co-authors-plus")},{value:"text-top",label:(0,i.__)("Text Top","co-authors-plus")},{value:"top",label:(0,i.__)("Top","co-authors-plus")}],onChange:e=>{r({verticalAlign:""===e?void 0:e})},help:(0,i.__)("Vertical alignment defaults to bottom in the block layout and middle in the inline layout.","co-authors-plus")})):null))},icon:a})},184:function(e,t){var l;!function(){"use strict";var o={}.hasOwnProperty;function n(){for(var e=[],t=0;t=r)&&Object.keys(o.O).every((function(e){return o.O[e](l[u])}))?l.splice(u--,1):(i=!1,r0&&e[c-1][2]>r;c--)e[c]=e[c-1];e[c]=[l,n,r]},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,{a:t}),t},o.d=function(e,t){for(var l in t)o.o(t,l)&&!o.o(e,l)&&Object.defineProperty(e,l,{enumerable:!0,get:t[l]})},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e={893:0,30:0};o.O.j=function(t){return 0===e[t]};var t=function(t,l){var n,r,a=l[0],i=l[1],u=l[2],s=0;if(a.some((function(t){return 0!==e[t]}))){for(n in i)o.o(i,n)&&(o.m[n]=i[n]);if(u)var c=u(o)}for(t&&t(l);s array('wp-block-editor', 'wp-blocks', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'd61297f0bd62592218f0'); diff --git a/build/blocks/block-coauthor-description/index.css b/build/blocks/block-coauthor-description/index.css new file mode 100644 index 00000000..ae02f029 --- /dev/null +++ b/build/blocks/block-coauthor-description/index.css @@ -0,0 +1 @@ +.wp-block-co-authors-plus-description a{pointer-events:none} diff --git a/build/blocks/block-coauthor-description/index.js b/build/blocks/block-coauthor-description/index.js new file mode 100644 index 00000000..452cc935 --- /dev/null +++ b/build/blocks/block-coauthor-description/index.js @@ -0,0 +1 @@ +!function(){var t={184:function(t,e){var n;!function(){"use strict";var r={}.hasOwnProperty;function o(){for(var t=[],e=0;et("co-authors-plus/blocks").getAuthorPlaceholder()),[]),p=n["co-authors-plus/author"]||c,{description:f}=p;return(0,e.createElement)(e.Fragment,null,(0,e.createElement)(a.BlockControls,null,(0,e.createElement)(a.AlignmentControl,{value:u,onChange:t=>{o({textAlign:t})}})),(0,e.createElement)("div",i({},(0,a.useBlockProps)({className:s()({[`has-text-align-${u}`]:u,"is-layout-flow":!0})}),{dangerouslySetInnerHTML:{__html:f.rendered}})))},icon:o})}()}(); \ No newline at end of file diff --git a/build/blocks/block-coauthor-image/block.json b/build/blocks/block-coauthor-image/block.json new file mode 100644 index 00000000..86847728 --- /dev/null +++ b/build/blocks/block-coauthor-image/block.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "co-authors-plus/image", + "version": "1.0.0", + "title": "Co-Author Featured Image", + "category": "theme", + "description": "Uses your theme's image sizes to display a scalable avatar for a co-author with a guest author profile. Does not fallback to Gravatar images.", + "keywords": [ + "coauthors" + ], + "supports": { + "__experimentalBorder": { + "color": true, + "radius": true, + "width": true, + "__experimentalSelector": "img, .block-editor-media-placeholder", + "__experimentalSkipSerialization": true, + "__experimentalDefaultControls": { + "color": false, + "radius": false, + "width": false + } + }, + "spacing": { + "margin": true, + "padding": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false + } + } + }, + "usesContext": [ + "co-authors-plus/author", + "co-authors-plus/layout" + ], + "attributes": { + "isLink": { + "type": "boolean", + "default": false + }, + "rel": { + "type": "string" + }, + "aspectRatio": { + "type": "string" + }, + "width": { + "type": "string" + }, + "height": { + "type": "string" + }, + "scale": { + "type": "string", + "default": "cover" + }, + "sizeSlug": { + "type": "string" + }, + "verticalAlign": { + "type": "string" + }, + "align": { + "type": "string" + } + }, + "textdomain": "co-authors-plus", + "editorScript": "file:./index.js", + "editorStyle": "file:./index.css", + "style": "file:./style-index.css" +} \ No newline at end of file diff --git a/build/blocks/block-coauthor-image/index.asset.php b/build/blocks/block-coauthor-image/index.asset.php new file mode 100644 index 00000000..699f315d --- /dev/null +++ b/build/blocks/block-coauthor-image/index.asset.php @@ -0,0 +1 @@ + array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '97710ad46fb953ce4975'); diff --git a/build/blocks/block-coauthor-image/index.js b/build/blocks/block-coauthor-image/index.js new file mode 100644 index 00000000..294955e5 --- /dev/null +++ b/build/blocks/block-coauthor-image/index.js @@ -0,0 +1 @@ +!function(){var e,t={62:function(e,t,l){"use strict";var o=window.wp.blocks,n=window.wp.element,a=window.wp.primitives,i=(0,n.createElement)(a.SVG,{viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},(0,n.createElement)(a.Path,{d:"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM5 4.5h14c.3 0 .5.2.5.5v8.4l-3-2.9c-.3-.3-.8-.3-1 0L11.9 14 9 12c-.3-.2-.6-.2-.8 0l-3.6 2.6V5c-.1-.3.1-.5.4-.5zm14 15H5c-.3 0-.5-.2-.5-.5v-2.4l4.1-3 3 1.9c.3.2.7.2.9-.1L16 12l3.5 3.4V19c0 .3-.2.5-.5.5z"})),r=window.wp.i18n,s=window.wp.blockEditor,u=window.wp.components,c=window.wp.data,h=window.wp.coreData;const p=(0,n.createElement)(n.Fragment,null,(0,n.createElement)(u.__experimentalToggleGroupControlOption,{value:"cover",label:(0,r._x)("Cover","Scale option for Image dimension control")}),(0,n.createElement)(u.__experimentalToggleGroupControlOption,{value:"contain",label:(0,r._x)("Contain","Scale option for Image dimension control")}),(0,n.createElement)(u.__experimentalToggleGroupControlOption,{value:"fill",label:(0,r._x)("Fill","Scale option for Image dimension control")})),g="cover",d={cover:(0,r.__)("Image is scaled and cropped to fill the entire space without being distorted."),contain:(0,r.__)("Image is scaled to fill the space without clipping nor distorting."),fill:(0,r.__)("Image will be stretched and distorted to completely fill the space.")};var m=e=>{let{clientId:t,attributes:{aspectRatio:l,width:o,height:a,scale:i,sizeSlug:c},setAttributes:h,imageSizeOptions:m=[]}=e;const _=(0,u.__experimentalUseCustomUnits)({availableUnits:(0,s.useSetting)("spacing.units")||["px","%","vw","em","rem"]}),v=(e,t)=>{const l=parseFloat(t);isNaN(l)&&t||h({[e]:l<0?"0":t})},f=(0,r._x)("Scale","Image scaling options"),w=a||l&&"auto"!==l;return(0,n.createElement)(s.InspectorControls,{group:"dimensions"},(0,n.createElement)(u.__experimentalToolsPanelItem,{hasValue:()=>!!l,label:(0,r.__)("Aspect ratio"),onDeselect:()=>h({aspectRatio:void 0}),resetAllFilter:()=>({aspectRatio:void 0}),isShownByDefault:!0,panelId:t},(0,n.createElement)(u.SelectControl,{__nextHasNoMarginBottom:!0,label:(0,r.__)("Aspect ratio"),value:l,options:[{label:(0,r.__)("Original"),value:"auto"},{label:(0,r.__)("Square"),value:"1"},{label:(0,r.__)("16:9"),value:"16/9"},{label:(0,r.__)("4:3"),value:"4/3"},{label:(0,r.__)("3:2"),value:"3/2"},{label:(0,r.__)("9:16"),value:"9/16"},{label:(0,r.__)("3:4"),value:"3/4"},{label:(0,r.__)("2:3"),value:"2/3"}],onChange:e=>h({aspectRatio:e})})),(0,n.createElement)(u.__experimentalToolsPanelItem,{className:"single-column",hasValue:()=>!!a,label:(0,r.__)("Height"),onDeselect:()=>h({height:void 0}),resetAllFilter:()=>({height:void 0}),isShownByDefault:!0,panelId:t},(0,n.createElement)(u.__experimentalUnitControl,{label:(0,r.__)("Height"),labelPosition:"top",value:a||"",min:0,onChange:e=>v("height",e),units:_})),(0,n.createElement)(u.__experimentalToolsPanelItem,{className:"single-column",hasValue:()=>!!o,label:(0,r.__)("Width"),onDeselect:()=>h({width:void 0}),resetAllFilter:()=>({width:void 0}),isShownByDefault:!0,panelId:t},(0,n.createElement)(u.__experimentalUnitControl,{label:(0,r.__)("Width"),labelPosition:"top",value:o||"",min:0,onChange:e=>v("width",e),units:_})),w&&(0,n.createElement)(u.__experimentalToolsPanelItem,{hasValue:()=>!!i&&i!==g,label:f,onDeselect:()=>h({scale:g}),resetAllFilter:()=>({scale:g}),isShownByDefault:!0,panelId:t},(0,n.createElement)(u.__experimentalToggleGroupControl,{__nextHasNoMarginBottom:!0,label:f,value:i,help:d[i],onChange:e=>h({scale:e}),isBlock:!0},p)),!!m.length&&(0,n.createElement)(u.__experimentalToolsPanelItem,{hasValue:()=>!!c,label:(0,r.__)("Resolution"),onDeselect:()=>h({sizeSlug:void 0}),resetAllFilter:()=>({sizeSlug:void 0}),isShownByDefault:!1,panelId:t},(0,n.createElement)(u.SelectControl,{__nextHasNoMarginBottom:!0,label:(0,r.__)("Resolution"),value:c||"thumbnail",options:m,onChange:e=>h({sizeSlug:e}),help:(0,r.__)("Select the size of the source image.")})))};function _(e){let{dimensions:t,style:l,className:o}=e;const a=(0,n.useMemo)((()=>function(e){let{width:t,height:l}=e;return`data:image/svg+xml;charset=UTF-8,${encodeURIComponent(`\n\t\t\t\n\t\t\t\n\t\t`.replace(/[\t\n\r]/gim,"").replace(/\s\s+/g," ")).replace(/\(/g,"%28").replace(/\)/g,"%29")}`}(t)),[t]);return(0,n.createElement)("img",{alt:(0,r.__)("Placeholder image"),className:o,src:a,style:l,width:t.width,height:t.height})}function v(e,t){var l,o;return null==e||null===(l=e.media_details)||void 0===l||null===(o=l.sizes[t])||void 0===o?void 0:o.source_url}var f=l(184),w=l.n(f),b=JSON.parse('{"u2":"co-authors-plus/image"}');(0,o.registerBlockType)(b.u2,{edit:function(e){let{attributes:t,setAttributes:l,context:o,clientId:a}=e;const{aspectRatio:i,height:p,isLink:g,rel:d,scale:f,sizeSlug:b,verticalAlign:x,width:S,align:y}=t,E=(0,c.useSelect)((e=>e("co-authors-plus/blocks").getAuthorPlaceholder()),[]),C=o["co-authors-plus/author"]||E,k=o["co-authors-plus/layout"]||"",I=(0,c.useSelect)((e=>0!==C.featured_media&&e(h.store).getMedia(C.featured_media,{context:"view"})),[C.featured_media]),{imageSizes:O,imageDimensions:B}=(0,c.useSelect)((e=>e(s.store).getSettings()),[]),A=O.map((e=>{let{name:t,slug:l}=e;return{value:l,label:t}})),T=function(e,t,l){if(e&&"full"===l)return l;const o=function(e,t){if(!e)return Object.keys(t);const l=Object.keys(e.media_details.sizes),o=Object.keys(t);return Array.from(new Set([...l.filter((e=>o.includes(e)))]))}(e,t);return l&&o.includes(l)?l:o[0]}(I,B,b),N=function(e,t,l){if(!e)return{};const o=e.media_details.sizes[l];if("full"===l)return{width:o.width,height:o.height};const n=t[l];if(!0===n.crop||n.width===n.height)return{width:n.width,height:n.height};const a=o.width/o.height;return n.width>n.height?{width:n.width,height:n.width/a}:{width:n.height*a,height:n.height}}(I,B,T),P=I?{}:function(e,t){const l=e[t];return!0===l.crop||l.width===l.height?{width:l.width,height:l.height}:l.width>l.height?{width:l.width,height:l.width}:{width:l.height,height:l.height}}(B,T),z=(0,s.__experimentalUseBorderProps)(t),D=0!==C.id&&!1===I;return(0,n.createElement)(n.Fragment,null,(0,n.createElement)(m,{clientId:a,attributes:t,setAttributes:l,imageSizeOptions:A}),""===k?(0,n.createElement)(s.BlockControls,null,(0,n.createElement)(s.BlockAlignmentToolbar,{value:y,onChange:e=>{l({align:e})},controls:["none","left","center","right","wide","full"]})):null,D?null:(0,n.createElement)("figure",(0,s.useBlockProps)({className:w()({[`align${y}`]:!k&&y&&"none"!==y})}),I?(0,n.createElement)("img",{alt:(0,r.__)("Author featured image","co-authors-plus"),className:z.className,src:v(I,T),style:{width:!S&&p?"auto":S,height:!p&&S?"auto":p,aspectRatio:i,objectFit:f,verticalAlign:x,...z.style},width:N.width,height:N.height}):(0,n.createElement)(_,{className:z.className,dimensions:P,style:{width:!S&&p?"auto":S,height:!p&&S?"auto":p,aspectRatio:i,objectFit:f,verticalAlign:x,...z.style}})),(0,n.createElement)(s.InspectorControls,null,(0,n.createElement)(u.PanelBody,{title:(0,r.__)("Image Settings","co-authors-plus")},(0,n.createElement)(u.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,r.__)("Make featured image a link to author archive.","co-authors-plus"),onChange:()=>l({isLink:!g}),checked:g}),g&&(0,n.createElement)(u.TextControl,{__nextHasNoMarginBottom:!0,label:(0,r.__)("Link rel","co-authors-plus"),value:d,onChange:e=>l({rel:e})})),"default"===k?(0,n.createElement)(u.PanelBody,{initialOpen:!1,title:(0,r.__)("Co-Authors Layout","co-authors-plus")},(0,n.createElement)(u.SelectControl,{label:(0,r.__)("Vertical align","co-authors-plus"),value:x,options:[{value:"",label:(0,r.__)("Default","co-authors-plus")},{value:"baseline",label:(0,r.__)("Baseline","co-authors-plus")},{value:"bottom",label:(0,r.__)("Bottom","co-authors-plus")},{value:"middle",label:(0,r.__)("Middle","co-authors-plus")},{value:"sub",label:(0,r.__)("Sub","co-authors-plus")},{value:"super",label:(0,r.__)("Super","co-authors-plus")},{value:"text-bottom",label:(0,r.__)("Text Bottom","co-authors-plus")},{value:"text-top",label:(0,r.__)("Text Top","co-authors-plus")},{value:"top",label:(0,r.__)("Top","co-authors-plus")}],onChange:e=>{l({verticalAlign:""===e?void 0:e})},help:(0,r.__)("Vertical alignment defaults to bottom in the block layout and middle in the inline layout.","co-authors-plus")})):null))},icon:i})},184:function(e,t){var l;!function(){"use strict";var o={}.hasOwnProperty;function n(){for(var e=[],t=0;t=a)&&Object.keys(o.O).every((function(e){return o.O[e](l[s])}))?l.splice(s--,1):(r=!1,a0&&e[c-1][2]>a;c--)e[c]=e[c-1];e[c]=[l,n,a]},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,{a:t}),t},o.d=function(e,t){for(var l in t)o.o(t,l)&&!o.o(e,l)&&Object.defineProperty(e,l,{enumerable:!0,get:t[l]})},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e={461:0,286:0};o.O.j=function(t){return 0===e[t]};var t=function(t,l){var n,a,i=l[0],r=l[1],s=l[2],u=0;if(i.some((function(t){return 0!==e[t]}))){for(n in r)o.o(r,n)&&(o.m[n]=r[n]);if(s)var c=s(o)}for(t&&t(l);u array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '3a57c4d58ba62fabefff'); diff --git a/build/blocks/block-coauthor-name/index.js b/build/blocks/block-coauthor-name/index.js new file mode 100644 index 00000000..3e07a0e1 --- /dev/null +++ b/build/blocks/block-coauthor-name/index.js @@ -0,0 +1 @@ +!function(){var e={184:function(e,t){var n;!function(){"use strict";var o={}.hasOwnProperty;function l(){for(var e=[],t=0;te("co-authors-plus/blocks").getAuthorPlaceholder()),[]),g=n["co-authors-plus/author"]||m,{link:d,display_name:f}=g,w=h;return(0,t.createElement)(t.Fragment,null,(0,t.createElement)(r.BlockControls,null,(0,t.createElement)(r.AlignmentControl,{value:v,onChange:e=>{l({textAlign:e})}})),(0,t.createElement)(w,(0,r.useBlockProps)({className:c()({[`has-text-align-${v}`]:v})}),s?(0,t.createElement)("a",{href:d,rel:p,onClick:e=>e.preventDefault()},f):f),(0,t.createElement)(r.InspectorControls,null,(0,t.createElement)(a.PanelBody,{title:(0,u.__)("Settings","co-authors-plus")},(0,t.createElement)(a.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,u.__)("Make co-author name a link","co-authors-plus"),onChange:()=>l({isLink:!s}),checked:s}),s&&(0,t.createElement)(t.Fragment,null,(0,t.createElement)(a.TextControl,{__nextHasNoMarginBottom:!0,label:(0,u.__)("Link rel","co-authors-plus"),value:p,onChange:e=>l({rel:e})})))),(0,t.createElement)(r.InspectorControls,{group:"advanced"},(0,t.createElement)(a.SelectControl,{__nextHasNoMarginBottom:!0,label:(0,u.__)("HTML element","co-authors-plus"),options:[{label:(0,u.__)("Default (

)"),value:"p"},{label:"",value:"span"},{label:"

",value:"h1"},{label:"

",value:"h2"},{label:"

",value:"h3"},{label:"

",value:"h4"},{label:"

",value:"h5"},{label:"
",value:"h6"}],value:h,onChange:e=>l({tagName:e})})))},icon:l})}()}(); \ No newline at end of file diff --git a/build/blocks/block-coauthors/block.json b/build/blocks/block-coauthors/block.json new file mode 100644 index 00000000..28ee2988 --- /dev/null +++ b/build/blocks/block-coauthors/block.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "co-authors-plus/coauthors", + "version": "1.0.0", + "title": "Co-Authors", + "category": "theme", + "description": "Displays the co-authors of a post by using blocks to create a template. Start with co-author name and add any other co-author blocks.", + "supports": { + "html": false, + "color": { + "link": true, + "text": true, + "background": true, + "__experimentalDefaultControls": {} + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalTextDecoration": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalLetterSpacing": true, + "__experimentalTextTransform": true, + "__experimentalDefaultControls": {} + }, + "spacing": { + "margin": true, + "padding": true, + "blockGap": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false, + "blockGap": false + } + }, + "layout": true + }, + "attributes": { + "layout": { + "type": "object", + "default": { + "type": "default" + } + }, + "textAlign": { + "type": "string" + } + }, + "usesContext": [ + "postId" + ], + "textdomain": "co-authors-plus", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} \ No newline at end of file diff --git a/build/blocks/block-coauthors/index.asset.php b/build/blocks/block-coauthors/index.asset.php new file mode 100644 index 00000000..02276ba8 --- /dev/null +++ b/build/blocks/block-coauthors/index.asset.php @@ -0,0 +1 @@ + array('wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '73645b9145fe40093212'); diff --git a/build/blocks/block-coauthors/index.js b/build/blocks/block-coauthors/index.js new file mode 100644 index 00000000..838f5491 --- /dev/null +++ b/build/blocks/block-coauthors/index.js @@ -0,0 +1 @@ +!function(){var e,t={843:function(e,t,o){"use strict";var r=window.wp.blocks,n=window.wp.element,a=window.wp.primitives,l=(0,n.createElement)(a.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,n.createElement)(a.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"})),c=window.wp.blockEditor,s=window.wp.components,i=window.wp.apiFetch,u=o.n(i),h=window.wp.data,p=window.wp.i18n,v=(0,n.createElement)(a.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,n.createElement)(a.Path,{fillRule:"evenodd",d:"M5 11.25h3v1.5H5v-1.5zm5.5 0h3v1.5h-3v-1.5zm8.5 0h-3v1.5h3v-1.5z",clipRule:"evenodd"})),m=(0,n.createElement)(a.SVG,{viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},(0,n.createElement)(a.Path,{d:"M4 4v1.5h16V4H4zm8 8.5h8V11h-8v1.5zM4 20h16v-1.5H4V20zm4-8c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2z"})),d=(0,n.createElement)(a.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,n.createElement)(a.Path,{d:"m3 5c0-1.10457.89543-2 2-2h13.5c1.1046 0 2 .89543 2 2v13.5c0 1.1046-.8954 2-2 2h-13.5c-1.10457 0-2-.8954-2-2zm2-.5h6v6.5h-6.5v-6c0-.27614.22386-.5.5-.5zm-.5 8v6c0 .2761.22386.5.5.5h6v-6.5zm8 0v6.5h6c.2761 0 .5-.2239.5-.5v-6zm0-8v6.5h6.5v-6c0-.27614-.2239-.5-.5-.5z",fillRule:"evenodd",clipRule:"evenodd"})),f=(0,n.createElement)(a.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,n.createElement)(a.Path,{d:"M4 6.5h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4V16h5a.5.5 0 0 0 .5-.5v-7A.5.5 0 0 0 9 8H4V6.5Zm16 0h-5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h5V16h-5a.5.5 0 0 1-.5-.5v-7A.5.5 0 0 1 15 8h5V6.5Z"})),w=(0,n.createElement)(a.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,n.createElement)(a.Path,{d:"M17.5 4v5a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2V4H8v5a.5.5 0 0 0 .5.5h7A.5.5 0 0 0 16 9V4h1.5Zm0 16v-5a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2v5H8v-5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v5h1.5Z"})),g=o(184),x=o.n(g);function b(){return b=Object.assign?Object.assign.bind():function(e){for(var t=1;t{a(o)},i={display:r?"none":void 0};return(0,n.createElement)("div",b({},l,{tabIndex:0,role:"button",onClick:s,onKeyUp:s,style:i}))}));function E(){return(0,n.createElement)("div",(0,c.useInnerBlocksProps)({className:"wp-block-co-authors-plus-coauthor"},{template:[["co-authors-plus/name"]],__unstableDisableLayoutClassNames:!0}))}const k=["core/bold","core/italic","core/text-color"];var y=JSON.parse('{"u2":"co-authors-plus/coauthors"}');(0,r.registerBlockType)(y.u2,{edit:function(e){let{attributes:t,setAttributes:o,clientId:r,context:a,isSelected:l,__unstableLayoutClassNames:i}=e;const{prefix:g,separator:b,lastSeparator:y,suffix:C,layout:S,textAlign:A}=t,{type:z,orientation:P}=S||{},{postId:B}=a,O=(0,h.useSelect)((e=>e("co-authors-plus/blocks").getAuthorPlaceholder()),[]),[V,N]=(0,n.useState)([O]),[I,j]=(0,n.useState)(),H=(0,h.useDispatch)("core/notices");function G(e){"AbortError"!==e.name&&H.createErrorNotice(e.message,{isDismissible:!0})}(0,n.useEffect)((()=>{if(!B)return;const e=new AbortController;return u()({path:`/coauthors/v1/coauthors?post_id=${B}`,signal:e.signal}).then(N).catch(G),()=>{e.abort()}}),[B]);const M=(0,h.useSelect)((e=>e(c.store).getBlocks(r))),R=e=>{o({layout:e})},T=[{icon:v,title:(0,p.__)("Inline view"),onClick:()=>R({type:"default"}),isActive:"default"===z},{icon:m,title:(0,p.__)("List view"),onClick:()=>R({type:"constrained"}),isActive:"constrained"===z},{icon:d,title:(0,p.__)("Grid view"),onClick:()=>R({type:"grid"}),isActive:"grid"===z},{icon:f,title:(0,p.__)("Row view"),onClick:()=>R({type:"flex",orientation:"horizontal"}),isActive:"flex"===z&&"horizontal"===P},{icon:w,title:(0,p.__)("Stack view"),onClick:()=>R({type:"flex",orientation:"vertical"}),isActive:"flex"===z&&"vertical"===P}];return(0,n.createElement)(n.Fragment,null,(0,n.createElement)(c.BlockControls,null,(0,n.createElement)(s.ToolbarGroup,{controls:T}),(0,n.createElement)(c.AlignmentControl,{value:A,onChange:e=>{o({textAlign:e})}})),(0,n.createElement)("div",(0,c.useBlockProps)({className:x()(i,{[`has-text-align-${A}`]:A},"remove-outline")}),V&&"default"===z&&(l||g)&&(0,n.createElement)(c.RichText,{allowedFormats:k,className:"wp-block-co-authors-plus-coauthors__prefix",multiline:!1,"aria-label":(0,p.__)("Prefix","co-authors-plus"),placeholder:(0,p.__)("Prefix","co-authors-plus")+" ",value:g,onChange:e=>o({prefix:e}),tagName:"span"}),V&&V.map((e=>{var t;const o=e.id===(I||(null===(t=V[0])||void 0===t?void 0:t.id));return(0,n.createElement)(c.BlockContextProvider,{key:e.id,value:{"co-authors-plus/author":e,"co-authors-plus/layout":z}},o?(0,n.createElement)(E,null):null,(0,n.createElement)(_,{blocks:M,blockContextId:e.id,setActiveBlockContextId:j,isHidden:o}))})).reduce(((e,t,o,r)=>(0,n.createElement)(n.Fragment,null,e,"default"===z&&(0,n.createElement)("span",{className:"wp-block-co-authors-plus-coauthors__separator"},y&&o===r.length-1?`${y}`:`${b}`),t))),V&&"default"===z&&(l||C)&&(0,n.createElement)(c.RichText,{allowedFormats:k,className:"wp-block-co-authors-plus-coauthors__suffix",multiline:!1,"aria-label":(0,p.__)("Suffix"),placeholder:(0,p.__)("Suffix")+" ",value:C,onChange:e=>o({suffix:e}),tagName:"span"})),(0,n.createElement)(c.InspectorControls,null,"default"===z&&(0,n.createElement)(s.PanelBody,{title:(0,p.__)("Co-Authors Layout","co-authors-plus")},(0,n.createElement)(s.TextControl,{autoComplete:"off",label:(0,p.__)("Separator","co-authors-plus"),value:b||"",onChange:e=>{o({separator:e})},help:(0,p.__)("Enter character(s) used to separate authors.","co-authors-plus")}),(0,n.createElement)(s.TextControl,{autoComplete:"off",label:(0,p.__)("Last Separator","co-authors-plus"),value:y||"",onChange:e=>{o({lastSeparator:e})},help:(0,p.__)("Enter character(s) used to separate the last author.","co-authors-plus")}))))},save:function(e){let{attributes:t}=e;const{textAlign:o}=t,r=x()({[`has-text-align-${o}`]:o});return(0,n.createElement)("div",c.useBlockProps.save({className:r}),(0,n.createElement)(c.InnerBlocks.Content,null))},icon:l})},184:function(e,t){var o;!function(){"use strict";var r={}.hasOwnProperty;function n(){for(var e=[],t=0;t=a)&&Object.keys(r.O).every((function(e){return r.O[e](o[s])}))?o.splice(s--,1):(c=!1,a0&&e[u-1][2]>a;u--)e[u]=e[u-1];e[u]=[o,n,a]},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e={887:0,270:0};r.O.j=function(t){return 0===e[t]};var t=function(t,o){var n,a,l=o[0],c=o[1],s=o[2],i=0;if(l.some((function(t){return 0!==e[t]}))){for(n in c)r.o(c,n)&&(r.m[n]=c[n]);if(s)var u=s(r)}for(t&&t(o);iget_coauthor_terms_for_post( $post_id ); } + +/** + * Register CoAuthor REST API Routes + */ +function cap_register_coauthors_rest_api_routes(): void { + global $coauthors_plus; + (new CoAuthors\API\Endpoints\CoAuthors_Controller( $coauthors_plus ))->register_routes(); +} +add_action( 'rest_api_init', 'cap_register_coauthors_rest_api_routes' ); diff --git a/package-lock.json b/package-lock.json index 186408b8..59085892 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "co-authors-plus", - "version": "3.5.15", + "version": "3.6.0", "lockfileVersion": 2, "requires": true, "packages": { @@ -16,8 +16,9 @@ "@wordpress/i18n": "^4.17.0" }, "devDependencies": { - "@wordpress/icons": "^2.10.2", + "@wordpress/icons": "^9.33.0", "@wordpress/scripts": "^24.0.0", + "classnames": "^2.3.2", "prettier": "npm:wp-prettier@^2.2.1-beta-1", "prop-types": "^15.8.1", "version-bump-prompt": "^6.1.0" @@ -4688,19 +4689,6 @@ "react-dom": "^17.0.0" } }, - "node_modules/@wordpress/components/node_modules/@wordpress/icons": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.13.0.tgz", - "integrity": "sha512-V8q55fI0rtzxRdJbQsAjUgg7V8JbWoncm5SyuvfEtmkL+IKTQUrYgaKO0DKPf7qaTPcZJlnOXUzy6XW+fxHmxA==", - "dependencies": { - "@babel/runtime": "^7.16.0", - "@wordpress/element": "^4.20.0", - "@wordpress/primitives": "^3.20.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wordpress/compose": { "version": "5.20.0", "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-5.20.0.tgz", @@ -4837,9 +4825,9 @@ } }, "node_modules/@wordpress/escape-html": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.22.0.tgz", - "integrity": "sha512-GUo6VLugIZxen1rdYuotvz6Vqa+5fNtVelNjXLwDqRu0iY2RXeoTux9V5bZWXPnGb54ryqfYmR4gH6F8xZhWzQ==", + "version": "2.42.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.42.0.tgz", + "integrity": "sha512-hC/SfA3mrLEL1QiXEp+yEb7BhgqUkmYnXnuuuGD/xxazPVdMoW80gNxeFYnVQrNnc48EC7JbWGlTuB93D2EeMw==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -4947,21 +4935,22 @@ } }, "node_modules/@wordpress/icons": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-2.10.3.tgz", - "integrity": "sha512-hVXArGOHLE5pL1G3rHNzsUEuTR4/G6lB+enKYwhYSSIqWuSbyXbZq3nvibxpepPrLy9B3d5t6aR6QUmjMVzIcQ==", - "dev": true, + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.33.0.tgz", + "integrity": "sha512-yD8b2Q21/X1riFIUBbgVh6sVhrnOYpstV9hxa4/MKesWYkyDN75KM6uaor1/tn5wyq40Shnpwxvc9kPUGI4Kgw==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@wordpress/element": "^2.20.3", - "@wordpress/primitives": "^1.12.3" + "@babel/runtime": "^7.16.0", + "@wordpress/element": "^5.19.0", + "@wordpress/primitives": "^3.40.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/@wordpress/icons/node_modules/@types/react": { - "version": "16.14.34", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.34.tgz", - "integrity": "sha512-b99nWeGGReLh6aKBppghVqp93dFJtgtDOzc8NXM6hewD8PQ2zZG5kBLgbx+VJr7Q7WBMjHxaIl3dwpwwPIUgyA==", - "dev": true, + "version": "18.2.22", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.22.tgz", + "integrity": "sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4969,86 +4958,60 @@ } }, "node_modules/@wordpress/icons/node_modules/@types/react-dom": { - "version": "16.9.17", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.17.tgz", - "integrity": "sha512-qSRyxEsrm5btPXnowDOs5jSkgT8ldAA0j6Qp+otHUh+xHzy3sXmgNfyhucZjAjkgpdAUw9rJe0QRtX/l+yaS4g==", - "dev": true, + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", + "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", "dependencies": { - "@types/react": "^16" + "@types/react": "*" } }, "node_modules/@wordpress/icons/node_modules/@wordpress/element": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-2.20.3.tgz", - "integrity": "sha512-f4ZPTDf9CxiiOXiMxc4v1K7jcBMT4dsiehVOpkKzCDKboNXp4qVf8oe5PE23VGZNEjcOj5Mkg9hB57R0nqvMTw==", - "dev": true, + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.19.0.tgz", + "integrity": "sha512-uTRrt6zrtdXT5DkffvKSLoCw0aLOHHbV4dDnh6NNR4n2roxhPK7MQRUpybVfuzyvCTIzKPI/0E3q87AWshePNg==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@types/react": "^16.9.0", - "@types/react-dom": "^16.9.0", - "@wordpress/escape-html": "^1.12.2", - "lodash": "^4.17.19", - "react": "^16.13.1", - "react-dom": "^16.13.1" - } - }, - "node_modules/@wordpress/icons/node_modules/@wordpress/escape-html": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-1.12.2.tgz", - "integrity": "sha512-FabgSwznhdaUwe6hr1CsGpgxQbzqEoGevv73WIL1B9GvlZ6csRWodgHfWh4P6fYqpzxFL4WYB8wPJ1PdO32XFA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/@wordpress/icons/node_modules/@wordpress/primitives": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-1.12.3.tgz", - "integrity": "sha512-LIF44bVlJS7CJEVmk6TLuV6HZMdj5iwkyM8do4ukGY6qnZIzrXpBablgJeDBcyjzWrWRLn+w+tiZ/8l+2egoVA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@wordpress/element": "^2.20.3", - "classnames": "^2.2.5" + "@babel/runtime": "^7.16.0", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@wordpress/escape-html": "^2.42.0", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/@wordpress/icons/node_modules/react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "dev": true, + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "loose-envify": "^1.1.0" }, "engines": { "node": ">=0.10.0" } }, "node_modules/@wordpress/icons/node_modules/react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "dev": true, + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "dependencies": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" + "scheduler": "^0.23.0" }, "peerDependencies": { - "react": "^16.14.0" + "react": "^18.2.0" } }, "node_modules/@wordpress/icons/node_modules/scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "dev": true, + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "node_modules/@wordpress/is-shallow-equal": { @@ -5152,18 +5115,85 @@ } }, "node_modules/@wordpress/primitives": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.20.0.tgz", - "integrity": "sha512-+30QC2bPv3sj3aYlS9q0TZh8LSYIERd49CizHqJ2/M9XCpWV6jLPwZk+k3pcOJHIpHEBNB9lB+4UG7ulj8WxkQ==", + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.40.0.tgz", + "integrity": "sha512-NGrcMsIKA7bBRVJPkiweOeUlnai335fyQTpwASwkpfWLbUTQ+LwKDn6hnwAsLpYot7uwflar2TPJdakglwzfIQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^4.20.0", + "@wordpress/element": "^5.19.0", "classnames": "^2.3.1" }, "engines": { "node": ">=12" } }, + "node_modules/@wordpress/primitives/node_modules/@types/react": { + "version": "18.2.22", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.22.tgz", + "integrity": "sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@wordpress/primitives/node_modules/@types/react-dom": { + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", + "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@wordpress/primitives/node_modules/@wordpress/element": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.19.0.tgz", + "integrity": "sha512-uTRrt6zrtdXT5DkffvKSLoCw0aLOHHbV4dDnh6NNR4n2roxhPK7MQRUpybVfuzyvCTIzKPI/0E3q87AWshePNg==", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@wordpress/escape-html": "^2.42.0", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@wordpress/primitives/node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@wordpress/primitives/node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/@wordpress/primitives/node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/@wordpress/priority-queue": { "version": "2.22.0", "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.22.0.tgz", @@ -22839,18 +22869,6 @@ "use-lilius": "^2.0.1", "uuid": "^8.3.0", "valtio": "^1.7.0" - }, - "dependencies": { - "@wordpress/icons": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.13.0.tgz", - "integrity": "sha512-V8q55fI0rtzxRdJbQsAjUgg7V8JbWoncm5SyuvfEtmkL+IKTQUrYgaKO0DKPf7qaTPcZJlnOXUzy6XW+fxHmxA==", - "requires": { - "@babel/runtime": "^7.16.0", - "@wordpress/element": "^4.20.0", - "@wordpress/primitives": "^3.20.0" - } - } } }, "@wordpress/compose": { @@ -22956,9 +22974,9 @@ } }, "@wordpress/escape-html": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.22.0.tgz", - "integrity": "sha512-GUo6VLugIZxen1rdYuotvz6Vqa+5fNtVelNjXLwDqRu0iY2RXeoTux9V5bZWXPnGb54ryqfYmR4gH6F8xZhWzQ==", + "version": "2.42.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.42.0.tgz", + "integrity": "sha512-hC/SfA3mrLEL1QiXEp+yEb7BhgqUkmYnXnuuuGD/xxazPVdMoW80gNxeFYnVQrNnc48EC7JbWGlTuB93D2EeMw==", "requires": { "@babel/runtime": "^7.16.0" } @@ -23026,21 +23044,19 @@ } }, "@wordpress/icons": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-2.10.3.tgz", - "integrity": "sha512-hVXArGOHLE5pL1G3rHNzsUEuTR4/G6lB+enKYwhYSSIqWuSbyXbZq3nvibxpepPrLy9B3d5t6aR6QUmjMVzIcQ==", - "dev": true, + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.33.0.tgz", + "integrity": "sha512-yD8b2Q21/X1riFIUBbgVh6sVhrnOYpstV9hxa4/MKesWYkyDN75KM6uaor1/tn5wyq40Shnpwxvc9kPUGI4Kgw==", "requires": { - "@babel/runtime": "^7.13.10", - "@wordpress/element": "^2.20.3", - "@wordpress/primitives": "^1.12.3" + "@babel/runtime": "^7.16.0", + "@wordpress/element": "^5.19.0", + "@wordpress/primitives": "^3.40.0" }, "dependencies": { "@types/react": { - "version": "16.14.34", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.34.tgz", - "integrity": "sha512-b99nWeGGReLh6aKBppghVqp93dFJtgtDOzc8NXM6hewD8PQ2zZG5kBLgbx+VJr7Q7WBMjHxaIl3dwpwwPIUgyA==", - "dev": true, + "version": "18.2.22", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.22.tgz", + "integrity": "sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -23048,80 +23064,51 @@ } }, "@types/react-dom": { - "version": "16.9.17", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.17.tgz", - "integrity": "sha512-qSRyxEsrm5btPXnowDOs5jSkgT8ldAA0j6Qp+otHUh+xHzy3sXmgNfyhucZjAjkgpdAUw9rJe0QRtX/l+yaS4g==", - "dev": true, + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", + "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", "requires": { - "@types/react": "^16" + "@types/react": "*" } }, "@wordpress/element": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-2.20.3.tgz", - "integrity": "sha512-f4ZPTDf9CxiiOXiMxc4v1K7jcBMT4dsiehVOpkKzCDKboNXp4qVf8oe5PE23VGZNEjcOj5Mkg9hB57R0nqvMTw==", - "dev": true, + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.19.0.tgz", + "integrity": "sha512-uTRrt6zrtdXT5DkffvKSLoCw0aLOHHbV4dDnh6NNR4n2roxhPK7MQRUpybVfuzyvCTIzKPI/0E3q87AWshePNg==", "requires": { - "@babel/runtime": "^7.13.10", - "@types/react": "^16.9.0", - "@types/react-dom": "^16.9.0", - "@wordpress/escape-html": "^1.12.2", - "lodash": "^4.17.19", - "react": "^16.13.1", - "react-dom": "^16.13.1" - } - }, - "@wordpress/escape-html": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-1.12.2.tgz", - "integrity": "sha512-FabgSwznhdaUwe6hr1CsGpgxQbzqEoGevv73WIL1B9GvlZ6csRWodgHfWh4P6fYqpzxFL4WYB8wPJ1PdO32XFA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.13.10" - } - }, - "@wordpress/primitives": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-1.12.3.tgz", - "integrity": "sha512-LIF44bVlJS7CJEVmk6TLuV6HZMdj5iwkyM8do4ukGY6qnZIzrXpBablgJeDBcyjzWrWRLn+w+tiZ/8l+2egoVA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.13.10", - "@wordpress/element": "^2.20.3", - "classnames": "^2.2.5" + "@babel/runtime": "^7.16.0", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@wordpress/escape-html": "^2.42.0", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" } }, "react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "dev": true, + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "loose-envify": "^1.1.0" } }, "react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "dev": true, + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "requires": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" + "scheduler": "^0.23.0" } }, "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "dev": true, + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } } } @@ -23190,13 +23177,73 @@ "requires": {} }, "@wordpress/primitives": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.20.0.tgz", - "integrity": "sha512-+30QC2bPv3sj3aYlS9q0TZh8LSYIERd49CizHqJ2/M9XCpWV6jLPwZk+k3pcOJHIpHEBNB9lB+4UG7ulj8WxkQ==", + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.40.0.tgz", + "integrity": "sha512-NGrcMsIKA7bBRVJPkiweOeUlnai335fyQTpwASwkpfWLbUTQ+LwKDn6hnwAsLpYot7uwflar2TPJdakglwzfIQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^4.20.0", + "@wordpress/element": "^5.19.0", "classnames": "^2.3.1" + }, + "dependencies": { + "@types/react": { + "version": "18.2.22", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.22.tgz", + "integrity": "sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", + "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "requires": { + "@types/react": "*" + } + }, + "@wordpress/element": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.19.0.tgz", + "integrity": "sha512-uTRrt6zrtdXT5DkffvKSLoCw0aLOHHbV4dDnh6NNR4n2roxhPK7MQRUpybVfuzyvCTIzKPI/0E3q87AWshePNg==", + "requires": { + "@babel/runtime": "^7.16.0", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@wordpress/escape-html": "^2.42.0", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + } } }, "@wordpress/priority-queue": { diff --git a/package.json b/package.json index d19cb236..550a6c1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "co-authors-plus", - "version": "3.5.15", + "version": "3.6.0", "description": "Allows multiple authors to be assigned to a post.", "license": "GPL-2.0-or-later", "private": true, @@ -39,8 +39,9 @@ "@wordpress/i18n": "^4.17.0" }, "devDependencies": { - "@wordpress/icons": "^2.10.2", + "@wordpress/icons": "^9.33.0", "@wordpress/scripts": "^24.0.0", + "classnames": "^2.3.2", "prettier": "npm:wp-prettier@^2.2.1-beta-1", "prop-types": "^15.8.1", "version-bump-prompt": "^6.1.0" diff --git a/php/api/endpoints/class-coauthors-controller.php b/php/api/endpoints/class-coauthors-controller.php new file mode 100644 index 00000000..7faef532 --- /dev/null +++ b/php/api/endpoints/class-coauthors-controller.php @@ -0,0 +1,389 @@ +coauthors_plus = $coauthors_plus; + } + + /** + * Register Rest Routes + * + * @since 3.6.0 + */ + public function register_routes(): void { + $this->register_coauthors_route(); + $this->register_coauthor_route(); + } + + /** + * Register Co-Authors Route + * + * Provide a post ID as an integer to retrieve an array of associated co-authors. + * + * Example: `/wp-json/coauthors/v1/coauthors?post_id=11111` + * + * @since 3.6.0 + */ + public function register_coauthors_route(): void { + register_rest_route( + 'coauthors/v1', + '/coauthors', + array( + 'args' => array( + 'post_id' => array( + 'description' => __( 'Unique identifier for a post.', 'co-authors-plus' ), + 'type' => 'integer', + 'required' => true, + 'validate_callback' => function( $post_id ): bool { + return 0 !== absint( $post_id ); + }, + 'sanitize_callback' => function( $post_id ): int { + return absint( $post_id ); + }, + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => '__return_true' + ), + ) + ); + } + + /** + * Register Co-Author Route + * + * Provide a user nicename as a hyphen-separated string to retrieve a single co-author. + * + * Example: `/wp-json/coauthors/v1/coauthors/user-nicename` + * + * @since 3.6.0 + */ + public function register_coauthor_route(): void { + register_rest_route( + 'coauthors/v1', + '/coauthors/(?P[\w-]+)', + array( + 'args' => array( + 'user_nicename' => array( + 'description' => __( 'Nicename / slug for co-author.', 'co-authors-plus' ), + 'type' => 'string', + 'required' => true, + 'validate_callback' => function( $slug ): bool { + return is_string( $slug ); + }, + 'sanitize_callback' => function( $slug ) { + return sanitize_title( $slug ); + }, + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => '__return_true', + ), + ) + ); + } + + /** + * Get Item + * + * @since 3.6.0 + * @param WP_REST_Request $request + * @return WP_REST_Response|WP_Error + */ + public function get_item( $request ) { + + $coauthor = $this->coauthors_plus->get_coauthor_by( + 'user_nicename', + $request->get_param( 'user_nicename' ) + ); + + if ( ! is_object( $coauthor ) ) { + return new WP_Error( + 'rest_not_found', + __( 'Sorry, we could not find that co-author.', 'co-authors-plus' ), + array( 'status' => 404 ) + ); + } + + if ( ! self::is_coauthor( $coauthor ) ) { + return new WP_Error( + 'rest_unusable_data', + __( 'Sorry, an unusable response was produced.', 'co-authors-plus' ), + array( 'status' => 404 ) + ); + } + + return self::prepare_item_for_response( $coauthor, $request ); + } + + /** + * Is Valid CoAuthor + * + * @since 3.6.0 + * @param WP_User|stdClass $coauthor + */ + public static function is_coauthor( $coauthor ): bool { + return is_a( $coauthor, 'WP_User' ) || self::is_guest_author( $coauthor ); + } + + /** + * Is Guest Author + * + * @since 3.6.0 + * @param WP_User|stdClass $coauthor + */ + public static function is_guest_author( $coauthor ): bool { + return property_exists( $coauthor, 'type' ) && 'guest-author' === $coauthor->type; + } + + /** + * Get Items + * + * @since 3.6.0 + * @param WP_REST_Request $request + * @return WP_REST_Response|WP_Error + */ + public function get_items( $request ) { + + $coauthors = get_coauthors( $request->get_param( 'post_id' ) ); + + if ( ! is_array( $coauthors ) ) { + return new WP_Error( + 'rest_unusable_data', + __( 'Sorry, an unusable response was produced.', 'co-authors-plus' ), + array( 'status' => 406 ) + ); + } + + return rest_ensure_response( + array_map( + function( $author ) use ( $request ) : array { + return $this->prepare_response_for_collection( + $this->prepare_item_for_response( $author, $request ) + ); + }, + $coauthors + ) + ); + } + + /** + * Retrieves the CoAuthor schema, conforming to JSON Schema. + * + * @since 3.6.0 + * @return array Item schema data. + */ + public function get_item_schema(): array { + + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'coauthor', + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'Either user ID or guest author ID.', 'co-authors-plus' ), + 'type' => 'integer', + 'context' => array( 'view' ), + 'readonly' => true, + ), + 'display_name' => array( + 'description' => __( 'Author name for display.', 'co-authors-plus' ), + 'type' => 'string', + 'context' => array( 'view' ), + 'readonly' => true, + ), + 'description' => array( + 'description' => __( 'Author description.', 'co-authors-plus' ), + 'type' => 'object', + 'context' => array( 'view' ), + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'Author description as stored in database.', 'co-authors-plus' ), + 'type' => 'string', + 'context' => array( 'view' ), + 'readonly' => true, + ), + 'rendered' => array( + 'description' => __( 'Author description as rendered in HTML content.', 'co-authors-plus' ), + 'type' => 'string', + 'context' => array( 'view' ), + 'readonly' => true, + ), + ), + ), + 'user_nicename' => array( + 'description' => __( 'Unique author slug.', 'co-authors-plus' ), + 'type' => 'string', + 'context' => array( 'view' ), + 'readonly' => true, + ), + 'link' => array( + 'description' => __( 'URL of author archive.', 'co-authors-plus' ), + 'type' => 'string', + 'context' => array( 'view' ), + 'readonly' => true, + ), + 'featured_media' => array( + 'description' => __( 'ID of guest author featured image.', 'co-authors-plus' ), + 'type' => 'integer', + 'context' => array( 'view' ), + 'readonly' => true, + ), + ), + ); + + if ( get_option( 'show_avatars' ) ) { + $schema['properties']['avatar_urls'] = array( + 'description' => __( 'URL for author avatar.', 'co-authors-plus' ), + 'type' => 'object', + 'context' => array( 'view' ), + 'readonly' => true, + ); + } + + // Take a snapshot of which fields are in the schema pre-filtering. + $schema_fields = array_keys( $schema['properties'] ); + + $schema = apply_filters( 'rest_coauthors_item_schema', $schema ); + + // Emit a _doing_it_wrong warning if user tries to add new properties using this filter. + $new_fields = array_diff( array_keys( $schema['properties'] ), $schema_fields ); + if ( count( $new_fields ) > 0 ) { + _doing_it_wrong( + __METHOD__, + sprintf( + /* translators: %s: register_rest_field */ + esc_html__( 'Please use %s to add new schema properties.', 'co-authors-plus' ), + 'register_rest_field' + ), + '5.4.0' + ); + } + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Prepare Item For Response + * + * @since 3.6.0 + * @param stdClass|WP_User $author + * @param WP_REST_Request $request + * @return WP_REST_Response|WP_Error + */ + public function prepare_item_for_response( $author, $request ) { + + $fields = $this->get_fields_for_response( $request ); + + if ( is_a( $author, 'WP_User' ) ) { + $author = $author->data; + $author->description = get_user_meta( $author->ID, 'description', true ); + } + + $data = array(); + + if ( rest_is_field_included( 'id', $fields ) ) { + $data['id'] = (int) $author->ID; + } + + if ( rest_is_field_included( 'avatar_urls', $fields ) ) { + $data['avatar_urls'] = rest_get_avatar_urls( $author->ID ); + } + + if ( rest_is_field_included( 'description', $fields ) ) { + $data['description'] = array(); + } + + if ( rest_is_field_included( 'description.raw', $fields ) ) { + $data['description']['raw'] = (string) $author->description; + } + + if ( rest_is_field_included( 'description.rendered', $fields ) ) { + $data['description']['rendered'] = wp_kses_post( wpautop( wptexturize( (string) $author->description ) ) ); + } + + if ( rest_is_field_included( 'display_name', $fields ) ) { + $data['display_name'] = (string) $author->display_name; + } + + if ( rest_is_field_included( 'link', $fields ) ) { + $data['link'] = (string) get_author_posts_url( $author->ID, $author->user_nicename ); + } + + if ( rest_is_field_included( 'featured_media', $fields ) ) { + if ( self::is_guest_author( $author ) ) { + $data['featured_media'] = (int) get_post_thumbnail_id( $author->ID ); + } else { + $data['featured_media'] = 0; + } + } + + if ( rest_is_field_included( 'user_nicename', $fields ) ) { + $data['user_nicename'] = (string) $author->user_nicename; + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + $response = rest_ensure_response( $data ); + + /** + * Filters the post data for a REST API response. + * + * @since 3.6.0 + * @param WP_REST_Response $response The response object. + * @param stdClass|WP_User $author + * @param WP_REST_Request $request Request object. + */ + return apply_filters( 'rest_prepare_coauthor', $response, $author, $request ); + } +} diff --git a/php/blocks/block-coauthor-avatar/class-block-coauthor-avatar.php b/php/blocks/block-coauthor-avatar/class-block-coauthor-avatar.php new file mode 100644 index 00000000..b9541cfa --- /dev/null +++ b/php/blocks/block-coauthor-avatar/class-block-coauthor-avatar.php @@ -0,0 +1,134 @@ + array( __CLASS__, 'render_block' ), + ) + ); + } + /** + * Render Block + * + * @since 3.6.0 + * @param array $attributes + * @param string $content + * @param WP_Block $block + * @return string + */ + public static function render_block( array $attributes, string $content, WP_Block $block ): string { + + $author = $block->context['co-authors-plus/author'] ?? array(); + $layout = $block->context['co-authors-plus/layout'] ?? ''; + + if ( empty( $author ) ) { + return ''; + } + + $avatar_urls = $author['avatar_urls'] ?? array(); + + if ( empty( $avatar_urls ) ) { + return ''; + } + + $display_name = esc_html( $author['display_name'] ?? '' ); + $link = esc_url( $author['link'] ?? '' ); + $is_link = '' !== $link && $attributes['isLink'] ?? false; + $rel = $attributes['rel'] ?? ''; + $size = $attributes['size'] ?? array_keys( $avatar_urls )[0]; + $align = esc_attr( $attributes['align'] ?? '' ); + + $srcset = array_map( + function( $size, $url ) { + return "{$url} {$size}w"; + }, + array_keys( $avatar_urls ), + array_values( $avatar_urls ) + ); + + $image_attributes = array_merge( + array( + 'src' => $avatar_urls[ $size ], + 'width' => $size, + 'height' => $size, + 'sizes' => "{$size}px", + 'srcset' => implode( ', ', $srcset ), + 'style' => '', + 'class' => '', + ), + get_block_core_post_featured_image_border_attributes( $attributes ) + ); + + $style_attribute_key_map = array( + 'verticalAlign' => 'vertical-align', + ); + + $styles = array_map( + function( string $key, string $style ) use ( $attributes ) : string { + if ( empty( $attributes[ $key ] ) ) { + return ''; + } + return sprintf( + '%s;', + safecss_filter_attr( + "{$style}:{$attributes[$key]}" + ) + ); + }, + array_keys( $style_attribute_key_map ), + array_values( $style_attribute_key_map ) + ); + + $image_attributes['style'] .= implode( '', $styles ); + + $image = Templating::render_self_closing_element( + 'img', + Templating::render_attributes( $image_attributes ) + ); + + if ( $is_link ) { + $link_attributes = Templating::render_attributes( + array( + 'href' => $link, + 'rel' => $rel, + 'title' => sprintf( __( 'Posts by %s', 'co-authors-plus' ), $display_name ), + ) + ); + $inner_content = Templating::render_element( 'a', $link_attributes, $image ); + } else { + $inner_content = $image; + } + + return Templating::render_element( + 'div', + get_block_wrapper_attributes( + array( + 'class' => ( 'default' !== $layout && ! empty( $align ) && 'none' !== $align ) ? "align{$align}" : '' + ) + ), + $inner_content + ); + } +} diff --git a/php/blocks/block-coauthor-description/class-block-coauthor-description.php b/php/blocks/block-coauthor-description/class-block-coauthor-description.php new file mode 100644 index 00000000..9098a308 --- /dev/null +++ b/php/blocks/block-coauthor-description/class-block-coauthor-description.php @@ -0,0 +1,87 @@ + array( __CLASS__, 'render_block' ), + ) + ); + } + + /** + * Render Block + * + * @since 3.6.0 + * @param array $attributes + * @param string $content + * @param WP_Block $block + * @return string + */ + public static function render_block( array $attributes, string $content, WP_Block $block ): string { + + $author = $block->context['co-authors-plus/author'] ?? array(); + + if ( empty( $author ) ) { + return ''; + } + + $description = $author['description']['raw'] ?? ''; + + if ( '' === $description ) { + return ''; + } + + return Templating::render_element( + 'div', + get_block_wrapper_attributes( + self::get_custom_block_wrapper_attributes( $attributes ) + ), + wp_kses_post( wpautop( wptexturize( $description ) ) ) + ); + } + /** + * Get Custom Block Wrapper Attributes + * + * @since 3.6.0 + * @param array $attributes + * @return array + */ + public static function get_custom_block_wrapper_attributes( array $attributes ): array { + + $default = array( + 'class' => 'is-layout-flow', + ); + + $text_align = $attributes['textAlign'] ?? ''; + + if ( empty( $text_align ) ) { + return $default; + } + + return array( + 'class' => $default['class'] . ' ' . sanitize_html_class( "has-text-align-{$text_align}" ), + ); + } +} diff --git a/php/blocks/block-coauthor-image/class-block-coauthor-image.php b/php/blocks/block-coauthor-image/class-block-coauthor-image.php new file mode 100644 index 00000000..0467740b --- /dev/null +++ b/php/blocks/block-coauthor-image/class-block-coauthor-image.php @@ -0,0 +1,137 @@ + array( __CLASS__, 'render_block' ), + ) + ); + } + + /** + * Render Block + * + * @since 3.6.0 + * @param array $attributes + * @param string $content + * @param WP_Block $block + * @return string + */ + public static function render_block( array $attributes, string $content, WP_Block $block ): string { + + $author = $block->context['co-authors-plus/author'] ?? array(); + $layout = $block->context['co-authors-plus/layout'] ?? ''; + + if ( empty( $author ) ) { + return ''; + } + + $featured_media_id = absint( $author['featured_media'] ?? 0 ); + $display_name = esc_html( $author['display_name'] ?? '' ); + $link = esc_url( $author['link'] ?? '' ); + $align = esc_attr( $attributes['align'] ?? '' ); + + if ( 0 === $featured_media_id ) { + return ''; + } + + $attributes = array_merge( + array( + 'width' => '', + 'height' => '', + 'sizeSlug' => 'thumbnail', + 'scale' => '', + 'aspectRatio' => '', + 'isLink' => false, + 'rel' => '', + 'verticalAlign' => '', + ), + $attributes + ); + + if ( empty( $attributes['width'] ) && ! empty( $attributes['height'] ) ) { + $attributes['width'] = 'auto'; + } + + $style_attribute_key_map = array( + 'width' => 'width', + 'height' => 'height', + 'scale' => 'object-fit', + 'aspectRatio' => 'aspect-ratio', + 'verticalAlign' => 'vertical-align', + ); + + $styles = array_map( + function( string $key, string $style ) use ( $attributes ) : string { + if ( empty( $attributes[ $key ] ) ) { + return ''; + } + return sprintf( + '%s;', + safecss_filter_attr( + "{$style}:{$attributes[$key]}" + ) + ); + }, + array_keys( $style_attribute_key_map ), + array_values( $style_attribute_key_map ) + ); + + $image_attributes = array_merge( + array( + 'class' => '', + 'style' => '', + ), + get_block_core_post_featured_image_border_attributes( $attributes ) + ); + + $image_attributes['style'] .= implode( '', $styles ); + + $feature_image = wp_get_attachment_image( $featured_media_id, $attributes['sizeSlug'], false, $image_attributes ); + + if ( '' !== $link && true === $attributes['isLink'] ) { + $link_attributes = Templating::render_attributes( + array( + 'href' => $author['link'], + 'rel' => $attributes['rel'], + 'title' => sprintf( __( 'Posts by %s', 'co-authors-plus' ), $display_name ), + ) + ); + $inner_content = Templating::render_element( 'a', $link_attributes, $feature_image ); + } else { + $inner_content = $feature_image; + } + + return Templating::render_element( + 'figure', + get_block_wrapper_attributes( + array( + 'class' => ( 'default' !== $layout && ! empty( $align ) && 'none' !== $align ) ? "align{$align}" : '' + ) + ), + $inner_content + ); + } +} diff --git a/php/blocks/block-coauthor-name/class-block-coauthor-name.php b/php/blocks/block-coauthor-name/class-block-coauthor-name.php new file mode 100644 index 00000000..b410f6bf --- /dev/null +++ b/php/blocks/block-coauthor-name/class-block-coauthor-name.php @@ -0,0 +1,123 @@ + array( __CLASS__, 'render_block' ), + ) + ); + } + + /** + * Render Block + * + * @since 3.6.0 + * @param array $attributes + * @param string $content + * @param WP_Block $block + * @return string + */ + public static function render_block( array $attributes, string $content, WP_Block $block ): string { + + $author = $block->context['co-authors-plus/author'] ?? array(); + + if ( empty( $author ) ) { + return ''; + } + + $display_name = esc_html( $author['display_name'] ?? '' ); + $link = esc_url( $author['link'] ?? '' ); + + if ( '' === $display_name ) { + return ''; + } + + $attributes = array_merge( + array( + 'isLink' => false, + 'rel' => '', + 'tagName' => 'p', + 'textAlign' => '', + ), + $attributes + ); + + if ( '' !== $link && true === $attributes['isLink'] ) { + $link_attributes = Templating::render_attributes( + array( + 'href' => $link, + 'rel' => $attributes['rel'], + 'title' => sprintf( __( 'Posts by %s', 'co-authors-plus' ), $display_name ), + ) + ); + $inner_content = Templating::render_element( 'a', $link_attributes, $display_name ); + } else { + $inner_content = $display_name; + } + + $tag_name = self::sanitize_tag_name( $attributes['tagName'] ); + + return Templating::render_element( + $tag_name, + get_block_wrapper_attributes( + self::get_custom_block_wrapper_attributes( $attributes ) + ), + $inner_content + ); + } + + /** + * Sanitize Tag Name + * + * @since 3.6.0 + * @param string $tag_name + * @return string + */ + public static function sanitize_tag_name( string $tag_name ): string { + if ( in_array( $tag_name, array_keys( wp_kses_allowed_html( 'post' ) ), true ) ) { + return $tag_name; + } + return 'p'; + } + + /** + * Get Custom Block Wrapper Attributes + * + * @since 3.6.0 + * @param array $attributes + * @return array + */ + public static function get_custom_block_wrapper_attributes( array $attributes ): array { + + $text_align = $attributes['textAlign'] ?? ''; + + if ( empty( $text_align ) ) { + return array(); + } + return array( + 'class' => sanitize_html_class( "has-text-align-{$text_align}" ), + ); + } +} diff --git a/php/blocks/block-coauthors/class-block-coauthors.php b/php/blocks/block-coauthors/class-block-coauthors.php new file mode 100644 index 00000000..5456984f --- /dev/null +++ b/php/blocks/block-coauthors/class-block-coauthors.php @@ -0,0 +1,339 @@ + array( __CLASS__, 'render_block' ), + ) + ); + } + + /** + * Separator Internationalization + * Provide i18n for the default prefix, separators and suffix attributes during block registration. + * + * @param array $settings Array of determined settings for registering a block type. + * @param array $metadata Metadata provided for registering a block type. + * @return array Updated settings that include internationalized attributes. + */ + public static function separator_internationalization( array $settings, array $metadata ): array { + + if ( ! array_key_exists( 'name', $metadata ) ) { + return $settings; + } + + if ( 'co-authors-plus/coauthors' !== $metadata['name'] ) { + return $settings; + } + + return array_merge( + $settings, + array( + 'attributes' => array_merge( + $settings['attributes'], + array( + 'prefix' => array( + 'type' => 'string', + 'default' => apply_filters( 'coauthors_default_before', __( 'By ', 'co-authors-plus' ) ), + ), + 'separator' => array( + 'type' => 'string', + 'default' => apply_filters( 'coauthors_default_between', ', ' ), + ), + 'lastSeparator' => array( + 'type' => 'string', + 'default' => apply_filters( 'coauthors_default_between_last', __( ' and ', 'co-authors-plus' ) ), + ), + 'suffix' => array( + 'type' => 'string', + 'default' => apply_filters( 'coauthors_default_after', '' ), + ), + ) + ), + ) + ); + } + + /** + * Render Block + * + * @since 3.6.0 + * @param array $attributes + * @param string $content + * @param WP_Block $block + * @return string + */ + public static function render_block( array $attributes, string $content, WP_Block $block ): string { + + $post_id = array_key_exists( 'postId', $block->context ) ? absint( $block->context['postId'] ) : 0; + + if ( 0 === $post_id ) { + return ''; + } + + $authors = Blocks::get_authors_with_api_schema( $post_id ); + + if ( empty( $authors ) ) { + return ''; + } + + $blocks = self::render_coauthors_blocks_with_template( + self::get_block_as_template( $block ), + $authors + ); + + $separators = self::get_separators( + count( $blocks ), + $attributes + ); + + $blocks_with_separators = self::merge_blocks_with_separators( + $blocks, + $separators + ); + + if ( 'default' === $attributes['layout']['type'] ) { + array_unshift( + $blocks_with_separators, + self::render_prefix( $attributes['prefix'] ?? '' ) + ); + array_push( + $blocks_with_separators, + self::render_suffix( $attributes['suffix'] ?? '' ) + ); + } + + return current( $block->parsed_block['innerContent'] ) . implode( $blocks_with_separators ) . end( $block->parsed_block['innerContent'] ); + } + + /** + * Get Composed Map Function + * Use array reduce so an unknown array of functions can be used as single array_map callback + * + * @since 3.6.0 + * @param array $fns + * @return callable + */ + public static function get_composed_map_function( ...$fns ): callable { + return function ( $value ) use ( $fns ) { + return array_reduce( + $fns, + function( $v, callable $f ) { + return $f( $v ); + }, + $value + ); + }; + } + + /** + * Render Prefix + * + * @since 3.6.0 + * @param string $prefix + * @return string + */ + public static function render_prefix( string $prefix ): string { + if ( empty( $prefix ) ) { + return $prefix; + } + return Templating::render_element( 'span', 'class="wp-block-co-authors-plus-coauthors__prefix"', $prefix ); + } + + /** + * Render Suffix + * + * @since 3.6.0 + * @param string $suffix + * @return string + */ + public static function render_suffix( string $suffix ): string { + if ( empty( $suffix ) ) { + return $suffix; + } + return Templating::render_element( 'span', 'class="wp-block-co-authors-plus-coauthors__suffix"', $suffix ); + } + + /** + * Render Co-Authors Blocks with Template + * + * @since 3.6.0 + * @param array $template + * @param array $authors + * @return array + */ + public static function render_coauthors_blocks_with_template( array $template, array $authors ): array { + return array_map( + self::get_composed_map_function( + self::get_template_render_function( $template ), + // To match JSX from editor, remove line-breaks between blocks. + function( $content ) { + return str_replace( "\n", '', $content ); + }, + // To match JSX from editor, trim whitespace around blocks. + 'trim', + Templating::get_render_element_function( 'div', 'class="wp-block-co-authors-plus-coauthor"' ) + ), + $authors + ); + } + + /** + * Merge Blocks with Separators + * + * @since 3.6.0 + * @param array $blocks + * @param array $separators + * @return array + */ + private static function merge_blocks_with_separators( array $blocks, array $separators ): array { + return array_map( + function( ...$args ) : string { + return implode( $args ); + }, + $blocks, + $separators + ); + } + + /** + * Get Separators + * + * @since 3.6.0 + * @param int $count + * @param array $attributes + */ + private static function get_separators( int $count, array $attributes ): array { + if ( 1 === $count ) { + return array(); + } + + if ( 'default' !== $attributes['layout']['type'] ) { + return array(); + } + + $separator = self::get_separator( $attributes ); + $last_separator = self::get_last_separator( $attributes, $separator ); + $separators = array_fill( 0, $count - 1, $separator ); + + if ( ! empty( $separators ) ) { + array_splice( $separators, -1, 1, $last_separator ); + } + + return $separators; + } + + /** + * Get Separator + * + * @since 3.6.0 + * @param array $attributes + * @return string $separator + */ + private static function get_separator( array $attributes ): string { + $separator = esc_html( + $attributes['separator'] ?? '' + ); + + if ( '' === $separator ) { + return $separator; + } + + return Templating::render_element( + 'span', + 'class="wp-block-co-authors-plus-coauthors__separator"', + $separator + ); + } + + /** + * Get Last Separator + * + * @since 3.6.0 + * @param array $attributes + * @param string $default + */ + private static function get_last_separator( array $attributes, string $default ): string { + $last_separator = esc_html( + $attributes['lastSeparator'] ?? '' + ); + + if ( '' === $last_separator ) { + return $default; + } + + return Templating::render_element( + 'span', + 'class="wp-block-co-authors-plus-coauthors__separator"', + $last_separator + ); + } + + /** + * Get Template Render Function + * + * @since 3.6.0 + * @param array $block_template + * @return callable + */ + private static function get_template_render_function( array $block_template ): callable { + return function( array $author ) use ( $block_template ): string { + return ( + new WP_Block( + $block_template, + array( + 'co-authors-plus/author' => $author, + 'co-authors-plus/layout' => $block_template['attrs']['layout']['type'] ?? 'default' + ) + ) + )->render( + array( + 'dynamic' => false, + ) + ); + }; + } + + /** + * Get Block as Template + * + * @since 3.6.0 + * @param WP_Block $block + * @return array + */ + private static function get_block_as_template( WP_Block $block ): array { + return array_merge( + $block->parsed_block, + array( + 'innerContent' => array_slice( $block->parsed_block['innerContent'], 1, -1 ), + 'blockName' => 'core/null', + ) + ); + } +} diff --git a/php/blocks/class-blocks.php b/php/blocks/class-blocks.php new file mode 100644 index 00000000..2f8abd81 --- /dev/null +++ b/php/blocks/class-blocks.php @@ -0,0 +1,234 @@ + $author, + ); + } + + /** + * Block Uses Author Context + * + * @param string $block_name Block name to check for use of author context. + * @return bool Whether the `uses_context` property of the registered block type includes `'co-authors-plus/author'` + */ + public static function block_uses_author_context( string $block_name ): bool { + $block = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); + + if ( ! is_a( $block, 'WP_Block_Type' ) ) { + return false; + } + + return in_array( 'co-authors-plus/author', $block->uses_context, true ); + } + + /** + * Enqueue Store + * + * @since 3.6.0 + */ + public static function enqueue_store(): void { + $asset = require dirname( COAUTHORS_PLUS_FILE ) . '/build/blocks-store/index.asset.php'; + + wp_enqueue_script( + 'coauthors-blocks-store', + plugins_url( '/co-authors-plus/build/blocks-store/index.js' ), + $asset['dependencies'], + $asset['version'] + ); + + $data = apply_filters( + 'coauthors_blocks_store_data', + array( + 'authorPlaceholder' => array( + 'id' => 0, + 'display_name' => __( 'FirstName LastName', 'co-authors-plus' ), + 'description' => array( + 'raw' => __( 'Placeholder description from Co-Authors block.', 'co-authors-plus' ), + 'rendered' => '

' . __( 'Placeholder description from Co-Authors block.', 'co-authors-plus' ) . '

', + ), + 'link' => '#', + 'featured_media' => 0, + 'avatar_urls' => array_map( '__return_empty_string', array_flip( rest_get_avatar_sizes() ) ), + ), + ) + ); + + wp_localize_script( + 'coauthors-blocks-store', + 'coAuthorsBlocks', + $data + ); + } + + /** + * Get CoAuthor with API Schema + * + * Use the global WP_REST_Server to fetch author data, + * so that it matches what a user would see in the editor. + * + * @since 3.6.0 + * @param false|WP_User|stdClass $author An author object from CoAuthors Plus. + * @return null|array Either an array of data about an author, or null. + */ + public static function get_author_with_api_schema( $author ): ?array { + if ( ! ( is_a( $author, 'stdClass' ) || is_a( $author, 'WP_User' ) ) ) { + return null; + } + + $data = rest_get_server()->dispatch( + WP_REST_Request::from_url( + home_url( + sprintf( + '/wp-json/coauthors/v1/coauthors/%s', + $author->user_nicename + ) + ) + ) + )->get_data(); + + if ( ! is_array( $data ) ) { + return null; + } + + // Lack of an `id` indicates an author was not found. + if ( ! array_key_exists( 'id', $data ) ) { + return null; + } + + // The presence of `code` indicates this is an error response. + if ( array_key_exists( 'code', $data ) ) { + return null; + } + + return $data; + } + + /** + * Get CoAuthors with API Schema + * + * Use the global WP_REST_Server to fetch co-authors for a post, + * so that it matches what a user would see in the editor. + * + * @since 3.6.0 + * @param int $post_id Post ID for querying co-authors. + * @param array $data Co-authors as returned by the REST API. + */ + public static function get_authors_with_api_schema( int $post_id ): array { + + $data = rest_get_server()->dispatch( + WP_REST_Request::from_url( + home_url( + sprintf( + '/wp-json/coauthors/v1/coauthors?post_id=%d', + $post_id + ) + ) + ) + )->get_data(); + + if ( ! is_array( $data ) ) { + return array(); + } + + // The presence of `code` indicates this is an error response. + if ( array_key_exists( 'code', $data ) ) { + return array(); + } + + return $data; + } +} diff --git a/php/blocks/templating/class-templating.php b/php/blocks/templating/class-templating.php new file mode 100644 index 00000000..31360633 --- /dev/null +++ b/php/blocks/templating/class-templating.php @@ -0,0 +1,100 @@ +{$content}"; + } + + /** + * Get Render Element Function + * Dependency inject render_element so you can use in array_map or add_filter. + * + * @since 3.6.0 + * @param string $name + * @param null|string $attributes + * @return callable + */ + public static function get_render_element_function( string $name, ?string $attributes = '' ): callable { + return function( string $content ) use ( $name, $attributes ) : string { + return self::render_element( $name, $attributes, $content ); + }; + } + + /** + * Render Self Closing Element + * + * @since 3.6.0 + * @param string $name + * @param null|string $attributes + * @return string + */ + public static function render_self_closing_element( string $name, ?string $attributes = '' ): string { + return "<{$name} $attributes/>"; + } + + /** + * Render Attribute String + * + * @since 3.6.0 + * @param string|int $key Attribute key. + * @param mixed $value Attribute value. For boolean attributes, set value the same as the key. + * @return string + */ + public static function render_attribute_string( $key, $value ): string { + if ( empty( $value ) ) { + return ''; + } + if ( $key === $value ) { + return $key; + } + return sprintf( '%s="%s"', $key, esc_attr( $value ) ); + } + + /** + * Render Attributes + * + * @since 3.6.0 + * @param array $attributes An associative array of attributes and their values. + */ + public static function render_attributes( array $attributes ): string { + + $attribute_strings = array_map( + array( __CLASS__, 'render_attribute_string' ), + array_keys( $attributes ), + array_values( $attributes ) + ); + + $validated_attribute_strings = array_values( + array_filter( + $attribute_strings, + function( $value ) { + return is_string( $value ) && '' !== $value; + } + ) + ); + + return implode( ' ', $validated_attribute_strings ); + } +} diff --git a/src/blocks-store/index.js b/src/blocks-store/index.js new file mode 100644 index 00000000..eb1deda2 --- /dev/null +++ b/src/blocks-store/index.js @@ -0,0 +1,13 @@ +import { createReduxStore, register } from '@wordpress/data'; +import { applyFilters } from '@wordpress/hooks'; + +register( + createReduxStore( 'co-authors-plus/blocks', { + reducer: ( state = window.coAuthorsBlocks ) => { + return state; + }, + selectors: { + getAuthorPlaceholder: ( state ) => applyFilters( 'co-authors-plus.author-placeholder', state.authorPlaceholder ), + }, + } ) +); diff --git a/src/blocks/block-coauthor-avatar/block.json b/src/blocks/block-coauthor-avatar/block.json new file mode 100644 index 00000000..1963598e --- /dev/null +++ b/src/blocks/block-coauthor-avatar/block.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "co-authors-plus/avatar", + "version": "1.0.0", + "title": "Co-Author Avatar", + "category": "theme", + "description": "Displays a small scale version of a co-author's avatar. Utilizes fallbacks from Gravatar so everyone has an avatar.", + "keywords": [ "coauthors" ], + "supports": { + "html": false, + "__experimentalBorder": { + "color": true, + "radius": true, + "width": true, + "__experimentalSelector": "img, .block-editor-media-placeholder", + "__experimentalSkipSerialization": true, + "__experimentalDefaultControls": { + "color": false, + "radius": false, + "width": false + } + }, + "spacing": { + "margin": true, + "padding": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false + } + } + }, + "usesContext": [ + "co-authors-plus/author", + "co-authors-plus/layout" + ], + "attributes": { + "size": { + "type": "number", + "default": 24 + }, + "isLink": { + "type": "boolean", + "default": false + }, + "rel": { + "type": "string" + }, + "verticalAlign": { + "type": "string" + }, + "align": { + "type": "string" + } + }, + "textdomain": "co-authors-plus", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/src/blocks/block-coauthor-avatar/edit.js b/src/blocks/block-coauthor-avatar/edit.js new file mode 100644 index 00000000..57f3927c --- /dev/null +++ b/src/blocks/block-coauthor-avatar/edit.js @@ -0,0 +1,188 @@ +import { __ } from '@wordpress/i18n'; +import { + useBlockProps, + InspectorControls, + __experimentalUseBorderProps as useBorderProps, + BlockControls, + BlockAlignmentToolbar, +} from '@wordpress/block-editor'; +import { + SelectControl, + PanelBody, + ToggleControl, + TextControl, +} from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import classnames from 'classnames'; + +import PlaceholderImage from '../components/placeholder-image'; + +/** + * The edit function describes the structure of your block in the context of the + * editor. This represents what the editor will render when the block is used. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit + * + * @return {WPElement} Element to render. + */ +export default function Edit( { context, attributes, setAttributes } ) { + const { isLink, rel, size, verticalAlign, align } = attributes; + const authorPlaceholder = useSelect( + ( select ) => select( 'co-authors-plus/blocks' ).getAuthorPlaceholder(), + [] + ); + const author = context[ 'co-authors-plus/author' ] || authorPlaceholder; + const layout = context[ 'co-authors-plus/layout' ] || ''; + + const { avatar_urls } = author; + + if ( ! avatar_urls || 0 === avatar_urls.length ) { + return null; + } + + const sizes = Object.keys( avatar_urls ).map( ( size ) => { + return { + value: size, + label: `${ size } x ${ size }`, + }; + } ); + + const borderProps = useBorderProps( attributes ); + + const src = avatar_urls[ size ] ?? ''; + + return ( + <> + + { 'default' !== layout ? ( + + { setAttributes({align: nextAlign}) } } controls={['none', 'left', 'center', 'right']} /> + + ) : ( + null + ) } + +
+ { '' === src ? ( + + ) : ( + + ) } +
+ + + { + setAttributes( { + size: Number( nextSize ), + } ); + } } + /> + setAttributes( { isLink: ! isLink } ) } + checked={ isLink } + /> + { isLink && ( + + setAttributes( { rel: newRel } ) + } + /> + ) } + + { 'default' === layout ? ( + + { + setAttributes( { + verticalAlign: '' === value ? undefined : value, + } ); + } } + help={ __( + 'Vertical alignment defaults to bottom in the block layout and middle in the inline layout.', + 'co-authors-plus' + ) } + /> + + ) : ( + null + ) } + + + ); +} diff --git a/src/blocks/block-coauthor-avatar/index.js b/src/blocks/block-coauthor-avatar/index.js new file mode 100644 index 00000000..f58908ed --- /dev/null +++ b/src/blocks/block-coauthor-avatar/index.js @@ -0,0 +1,31 @@ +/** + * Registers a new block provided a unique name and an object defining its behavior. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +import { registerBlockType } from '@wordpress/blocks'; +import { commentAuthorAvatar as icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import Edit from './edit'; +import metadata from './block.json'; + +/** + * Style shared between editor and content + */ +import './style.css'; + +/** + * Every block starts by registering a new block type definition. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, + icon, +} ); diff --git a/src/blocks/block-coauthor-avatar/style.css b/src/blocks/block-coauthor-avatar/style.css new file mode 100644 index 00000000..517ecc45 --- /dev/null +++ b/src/blocks/block-coauthor-avatar/style.css @@ -0,0 +1,24 @@ +/* Default Layout */ + +.wp-block-co-authors-plus-avatar :where(img) { + height: auto; + max-width: 100%; + vertical-align: bottom; +} + +/* Inline Layout */ + +.wp-block-co-authors-plus-coauthors.is-layout-flow .wp-block-co-authors-plus-avatar :where(img) { + vertical-align: middle; +} + +/* Align left, right, center */ + +.wp-block-co-authors-plus-avatar:is(.alignleft,.alignright) { + display: table; +} + +.wp-block-co-authors-plus-avatar.aligncenter { + display: table; + margin-inline: auto; +} diff --git a/src/blocks/block-coauthor-description/block.json b/src/blocks/block-coauthor-description/block.json new file mode 100644 index 00000000..e699be35 --- /dev/null +++ b/src/blocks/block-coauthor-description/block.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "co-authors-plus/description", + "version": "1.0.0", + "title": "Co-Author Biography", + "category": "theme", + "description": "Displays a co-author's biographical description.", + "keywords": [ "coauthors", "description", "bio", "biography" ], + "supports": { + "html": false, + "color": { + "link": true, + "text": true, + "background": true, + "__experimentalDefaultControls": {} + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalTextDecoration": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalLetterSpacing": true, + "__experimentalTextTransform": true, + "__experimentalDefaultControls": {} + }, + "spacing": { + "margin": true, + "padding": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false + } + } + }, + "usesContext": [ "co-authors-plus/author" ], + "attributes": { + "textAlign": { + "type": "string" + } + }, + "textdomain": "co-authors-plus", + "editorScript": "file:./index.js", + "editorStyle": "file:./index.css" +} diff --git a/src/blocks/block-coauthor-description/edit.js b/src/blocks/block-coauthor-description/edit.js new file mode 100644 index 00000000..67a02d31 --- /dev/null +++ b/src/blocks/block-coauthor-description/edit.js @@ -0,0 +1,56 @@ +/** + * React hook that is used to mark the block wrapper element. + * It provides all the necessary props like the class name. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops + */ +import { + useBlockProps, + AlignmentControl, + BlockControls, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import classnames from 'classnames'; +import './editor.css'; + +/** + * The edit function describes the structure of your block in the context of the + * editor. This represents what the editor will render when the block is used. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit + * + * @return {WPElement} Element to render. + */ +export default function Edit( { context, attributes, setAttributes } ) { + const { textAlign } = attributes; + const authorPlaceholder = useSelect( + ( select ) => select( 'co-authors-plus/blocks' ).getAuthorPlaceholder(), + [] + ); + const author = context[ 'co-authors-plus/author' ] || authorPlaceholder; + const { description } = author; + + return ( + <> + + { + setAttributes( { textAlign: nextAlign } ); + } } + /> + +
+ + ); +} diff --git a/src/blocks/block-coauthor-description/editor.css b/src/blocks/block-coauthor-description/editor.css new file mode 100644 index 00000000..7a48d580 --- /dev/null +++ b/src/blocks/block-coauthor-description/editor.css @@ -0,0 +1,3 @@ +.wp-block-co-authors-plus-description a { + pointer-events: none; +} diff --git a/src/blocks/block-coauthor-description/index.js b/src/blocks/block-coauthor-description/index.js new file mode 100644 index 00000000..4b4e8463 --- /dev/null +++ b/src/blocks/block-coauthor-description/index.js @@ -0,0 +1,25 @@ +/** + * Registers a new block provided a unique name and an object defining its behavior. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +import { registerBlockType } from '@wordpress/blocks'; +import { termDescription as icon } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import Edit from './edit'; +import metadata from './block.json'; + +/** + * Every block starts by registering a new block type definition. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, + icon, +} ); diff --git a/src/blocks/block-coauthor-image/block.json b/src/blocks/block-coauthor-image/block.json new file mode 100644 index 00000000..00ac92ec --- /dev/null +++ b/src/blocks/block-coauthor-image/block.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "co-authors-plus/image", + "version": "1.0.0", + "title": "Co-Author Featured Image", + "category": "theme", + "description": "Uses your theme's image sizes to display a scalable avatar for a co-author with a guest author profile. Does not fallback to Gravatar images.", + "keywords": [ "coauthors" ], + "supports": { + "__experimentalBorder": { + "color": true, + "radius": true, + "width": true, + "__experimentalSelector": "img, .block-editor-media-placeholder", + "__experimentalSkipSerialization": true, + "__experimentalDefaultControls": { + "color": false, + "radius": false, + "width": false + } + }, + "spacing": { + "margin": true, + "padding": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false + } + } + }, + "usesContext": [ + "co-authors-plus/author", + "co-authors-plus/layout" + ], + "attributes": { + "isLink": { + "type": "boolean", + "default": false + }, + "rel": { + "type": "string" + }, + "aspectRatio": { + "type": "string" + }, + "width": { + "type": "string" + }, + "height": { + "type": "string" + }, + "scale": { + "type": "string", + "default": "cover" + }, + "sizeSlug": { + "type": "string" + }, + "verticalAlign": { + "type": "string" + }, + "align": { + "type": "string" + } + }, + "textdomain": "co-authors-plus", + "editorScript": "file:./index.js", + "editorStyle": "file:./index.css", + "style": "file:./style-index.css" +} diff --git a/src/blocks/block-coauthor-image/dimension-controls.js b/src/blocks/block-coauthor-image/dimension-controls.js new file mode 100644 index 00000000..2f79d443 --- /dev/null +++ b/src/blocks/block-coauthor-image/dimension-controls.js @@ -0,0 +1,237 @@ +/** + * WordPress dependencies + */ +import { __, _x } from '@wordpress/i18n'; +import { + SelectControl, + __experimentalUnitControl as UnitControl, + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, + __experimentalUseCustomUnits as useCustomUnits, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; +import { InspectorControls, useSetting } from '@wordpress/block-editor'; + +const SCALE_OPTIONS = ( + <> + + + + +); + +const DEFAULT_SCALE = 'cover'; +const DEFAULT_SIZE = 'thumbnail'; + +const scaleHelp = { + cover: __( + 'Image is scaled and cropped to fill the entire space without being distorted.' + ), + contain: __( + 'Image is scaled to fill the space without clipping nor distorting.' + ), + fill: __( + 'Image will be stretched and distorted to completely fill the space.' + ), +}; + +const DimensionControls = ( { + clientId, + attributes: { aspectRatio, width, height, scale, sizeSlug }, + setAttributes, + imageSizeOptions = [], +} ) => { + const defaultUnits = [ 'px', '%', 'vw', 'em', 'rem' ]; + const units = useCustomUnits( { + availableUnits: useSetting( 'spacing.units' ) || defaultUnits, + } ); + const onDimensionChange = ( dimension, nextValue ) => { + const parsedValue = parseFloat( nextValue ); + /** + * If we have no value set and we change the unit, + * we don't want to set the attribute, as it would + * end up having the unit as value without any number. + */ + if ( isNaN( parsedValue ) && nextValue ) return; + setAttributes( { + [ dimension ]: parsedValue < 0 ? '0' : nextValue, + } ); + }; + const scaleLabel = _x( 'Scale', 'Image scaling options' ); + + const showScaleControl = + height || ( aspectRatio && aspectRatio !== 'auto' ); + + return ( + + !! aspectRatio } + label={ __( 'Aspect ratio' ) } + onDeselect={ () => setAttributes( { aspectRatio: undefined } ) } + resetAllFilter={ () => ( { + aspectRatio: undefined, + } ) } + isShownByDefault={ true } + panelId={ clientId } + > + + setAttributes( { aspectRatio: nextAspectRatio } ) + } + /> + + !! height } + label={ __( 'Height' ) } + onDeselect={ () => setAttributes( { height: undefined } ) } + resetAllFilter={ () => ( { + height: undefined, + } ) } + isShownByDefault={ true } + panelId={ clientId } + > + + onDimensionChange( 'height', nextHeight ) + } + units={ units } + /> + + !! width } + label={ __( 'Width' ) } + onDeselect={ () => setAttributes( { width: undefined } ) } + resetAllFilter={ () => ( { + width: undefined, + } ) } + isShownByDefault={ true } + panelId={ clientId } + > + + onDimensionChange( 'width', nextWidth ) + } + units={ units } + /> + + { showScaleControl && ( + !! scale && scale !== DEFAULT_SCALE } + label={ scaleLabel } + onDeselect={ () => + setAttributes( { + scale: DEFAULT_SCALE, + } ) + } + resetAllFilter={ () => ( { + scale: DEFAULT_SCALE, + } ) } + isShownByDefault={ true } + panelId={ clientId } + > + + setAttributes( { + scale: value, + } ) + } + isBlock + > + { SCALE_OPTIONS } + + + ) } + { !! imageSizeOptions.length && ( + !! sizeSlug } + label={ __( 'Resolution' ) } + onDeselect={ () => + setAttributes( { sizeSlug: undefined } ) + } + resetAllFilter={ () => ( { + sizeSlug: undefined, + } ) } + isShownByDefault={ false } + panelId={ clientId } + > + + setAttributes( { sizeSlug: nextSizeSlug } ) + } + help={ __( 'Select the size of the source image.' ) } + /> + + ) } + + ); +}; + +export default DimensionControls; diff --git a/src/blocks/block-coauthor-image/edit.js b/src/blocks/block-coauthor-image/edit.js new file mode 100644 index 00000000..efcf3a9c --- /dev/null +++ b/src/blocks/block-coauthor-image/edit.js @@ -0,0 +1,247 @@ +/** + * Co-Author Feature Image + */ + +import { __ } from '@wordpress/i18n'; +import { + useBlockProps, + store as blockEditorStore, + __experimentalUseBorderProps as useBorderProps, + InspectorControls, + BlockControls, + BlockAlignmentToolbar, +} from '@wordpress/block-editor'; +import { + TextControl, + PanelBody, + ToggleControl, + SelectControl, +} from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import DimensionControls from './dimension-controls'; +import PlaceholderImage from '../components/placeholder-image'; +import { + getAvailableSizeSlug, + getMediaSrc, + getMediaDimensions, + getPlaceholderImageDimensions, +} from './utils'; + +import classnames from 'classnames'; + +/** + * Edit + * + * @export + * @param {Object} props { attributes, setAttributes, context, clientId } + * @return {WPElement} + */ +export default function Edit( { + attributes, + setAttributes, + context, + clientId, +} ) { + const { + aspectRatio, + height, + isLink, + rel, + scale, + sizeSlug, + verticalAlign, + width, + align + } = attributes; + + // Author + const authorPlaceholder = useSelect( + ( select ) => select( 'co-authors-plus/blocks' ).getAuthorPlaceholder(), + [] + ); + const author = context[ 'co-authors-plus/author' ] || authorPlaceholder; + const layout = context[ 'co-authors-plus/layout' ] || ''; + + // Media + const media = useSelect( + ( select ) => + 0 !== author.featured_media && + select( coreStore ).getMedia( author.featured_media, { + context: 'view', + } ), + [ author.featured_media ] + ); + + // Image Sizes and Dimensions + const { imageSizes, imageDimensions } = useSelect( + ( select ) => select( blockEditorStore ).getSettings(), + [] + ); + const imageSizeOptions = imageSizes.map( ( { name, slug } ) => ( { + value: slug, + label: name, + } ) ); + const availableSizeSlug = getAvailableSizeSlug( + media, + imageDimensions, + sizeSlug + ); + const dimensions = getMediaDimensions( + media, + imageDimensions, + availableSizeSlug + ); + const placeholderDimensions = media + ? {} + : getPlaceholderImageDimensions( imageDimensions, availableSizeSlug ); + + // Border + const borderProps = useBorderProps( attributes ); + + // Don't placehold feature images for real authors with no image. + // Do placehold them in author archive contexts. + const panic = 0 !== author.id && false === media; + + return ( + <> + + { + '' === layout ? ( + + { setAttributes({align: nextAlign}) } } controls={['none', 'left', 'center', 'right', 'wide', 'full']} /> + + ) : (null) + } + { panic ? null : ( +
+ { media ? ( + { + ) : ( + + ) } +
+ ) } + + + setAttributes( { isLink: ! isLink } ) } + checked={ isLink } + /> + { isLink && ( + + setAttributes( { rel: newRel } ) + } + /> + ) } + + { 'default' === layout ? ( + + { + setAttributes( { + verticalAlign: '' === value ? undefined : value, + } ); + } } + help={ __( + 'Vertical alignment defaults to bottom in the block layout and middle in the inline layout.', + 'co-authors-plus' + ) } + /> + + ) : ( + null + ) } + + + ); +} diff --git a/src/blocks/block-coauthor-image/index.js b/src/blocks/block-coauthor-image/index.js new file mode 100644 index 00000000..31c6727f --- /dev/null +++ b/src/blocks/block-coauthor-image/index.js @@ -0,0 +1,35 @@ +/** + * Registers a new block provided a unique name and an object defining its behavior. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +import { registerBlockType } from '@wordpress/blocks'; +import { image as icon } from '@wordpress/icons'; + +/** + * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files. + * All files containing `style` keyword are bundled together. The code used + * gets applied both to the front of your site and to the editor. + * + * @see https://www.npmjs.com/package/@wordpress/scripts#using-css + */ +import './style.css'; + +/** + * Internal dependencies + */ +import Edit from './edit'; +import metadata from './block.json'; + +/** + * Every block starts by registering a new block type definition. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, + icon, +} ); diff --git a/src/blocks/block-coauthor-image/style.css b/src/blocks/block-coauthor-image/style.css new file mode 100644 index 00000000..42f0d671 --- /dev/null +++ b/src/blocks/block-coauthor-image/style.css @@ -0,0 +1,41 @@ +/** + * The following styles get applied both on the front of your site + * and in the editor. + * + * Replace them with your own styles or remove the file completely. + */ + +/* Default Layout */ + +.wp-block-co-authors-plus-image { + margin-bottom: 0; +} + +.wp-block-co-authors-plus-image :where(img) { + height: auto; + max-width: 100%; + vertical-align: bottom; +} + +/* Inline Layout */ + +.wp-block-co-authors-plus-coauthors.is-layout-flow .wp-block-co-authors-plus-image :where(img) { + vertical-align: middle; +} + +/* Align wide, full */ + +.wp-block-co-authors-plus-image:is(.alignfull,.alignwide) :where(img) { + width: 100%; +} + +/* Align left, right, center */ + +.wp-block-co-authors-plus-image:is(.alignleft,.alignright) { + display: table; +} + +.wp-block-co-authors-plus-image.aligncenter { + display: table; + margin-inline: auto; +} diff --git a/src/blocks/block-coauthor-image/utils.js b/src/blocks/block-coauthor-image/utils.js new file mode 100644 index 00000000..1af22efa --- /dev/null +++ b/src/blocks/block-coauthor-image/utils.js @@ -0,0 +1,130 @@ +/** + * Get Media Dimensions + * + * @param {Object} media + * @param {Object} imageDimensions + * @param {string} sizeSlug + * @return {Object} {width,height} + */ +export function getMediaDimensions( media, imageDimensions, sizeSlug ) { + if ( ! media ) { + return {}; + } + + const mediaSize = media.media_details.sizes[ sizeSlug ]; + + if ( 'full' === sizeSlug ) { + return { + width: mediaSize.width, + height: mediaSize.height, + }; + } + + const imageSize = imageDimensions[ sizeSlug ]; + + if ( true === imageSize.crop || imageSize.width === imageSize.height ) { + return { + width: imageSize.width, + height: imageSize.height, + }; + } + + const mediaAspectRatio = mediaSize.width / mediaSize.height; + + if ( imageSize.width > imageSize.height ) { + return { + width: imageSize.width, + height: imageSize.width / mediaAspectRatio, + }; + } + + return { + width: imageSize.height * mediaAspectRatio, + height: imageSize.height, + }; +} + +/** + * Get Media Src + * + * @param {Object} media + * @param {string} sizeSlug + * @return {string} + */ +export function getMediaSrc( media, sizeSlug ) { + return media?.media_details?.sizes[ sizeSlug ]?.source_url; +} + +/** + * Get Placeholder Image Dimensions + * + * @param {Object} imageDimensions + * @param {string} sizeSlug + * @return {Object} {width,height} + */ +export function getPlaceholderImageDimensions( imageDimensions, sizeSlug ) { + const size = imageDimensions[ sizeSlug ]; + + if ( true === size.crop || size.width === size.height ) { + return { + width: size.width, + height: size.height, + }; + } + + if ( size.width > size.height ) { + return { + width: size.width, + height: size.width, + }; + } + + return { + width: size.height, + height: size.height, + }; +} + +/** + * Get Size Keys Intersection + * + * @param {Object} media + * @param {Object} imageDimensions + * @return {Array} + */ +export function getSizeKeysIntersection( media, imageDimensions ) { + if ( ! media ) { + return Object.keys( imageDimensions ); + } + + const mediaKeys = Object.keys( media.media_details.sizes ); + const sizeKeys = Object.keys( imageDimensions ); + + return Array.from( + new Set( [ + ...mediaKeys.filter( ( key ) => sizeKeys.includes( key ) ), + ] ) + ); +} + +/** + * Get Available Size Slug + * + * @param {Object} media + * @param {Object} imageDimensions + * @param {string} sizeSlug + * @return {string} + */ +export function getAvailableSizeSlug( media, imageDimensions, sizeSlug ) { + if ( media && 'full' === sizeSlug ) { + return sizeSlug; + } + + const keys = getSizeKeysIntersection( media, imageDimensions ); + + if ( sizeSlug && keys.includes( sizeSlug ) ) { + return sizeSlug; + } + + return keys[0]; +} diff --git a/src/blocks/block-coauthor-name/block.json b/src/blocks/block-coauthor-name/block.json new file mode 100644 index 00000000..0b1297f5 --- /dev/null +++ b/src/blocks/block-coauthor-name/block.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "co-authors-plus/name", + "version": "1.0.0", + "title": "Co-Author Name", + "category": "theme", + "description": "Displays a co-author's display name and optionally links to their author archive.", + "keywords": [ "coauthors" ], + "supports": { + "html": false, + "color": { + "link": true, + "text": true, + "background": true, + "__experimentalDefaultControls": {} + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalTextDecoration": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalLetterSpacing": true, + "__experimentalTextTransform": true, + "__experimentalDefaultControls": {} + }, + "spacing": { + "margin": true, + "padding": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false + } + } + }, + "usesContext": [ "co-authors-plus/author" ], + "attributes": { + "isLink": { + "type": "boolean", + "default": false + }, + "rel": { + "type": "string", + "default": "author" + }, + "tagName": { + "type": "string", + "default": "p" + }, + "textAlign": { + "type": "string" + } + }, + "textdomain": "co-authors-plus", + "editorScript": "file:./index.js", + "editorStyle": "file:./index.css", + "style": "file:./style-index.css" +} diff --git a/src/blocks/block-coauthor-name/edit.js b/src/blocks/block-coauthor-name/edit.js new file mode 100644 index 00000000..16ac3d50 --- /dev/null +++ b/src/blocks/block-coauthor-name/edit.js @@ -0,0 +1,118 @@ +/** + * React hook that is used to mark the block wrapper element. + * It provides all the necessary props like the class name. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops + */ +import { + useBlockProps, + InspectorControls, + AlignmentControl, + BlockControls, +} from '@wordpress/block-editor'; +import { + TextControl, + PanelBody, + ToggleControl, + SelectControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import classnames from 'classnames'; + +/** + * The edit function describes the structure of your block in the context of the + * editor. This represents what the editor will render when the block is used. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit + * + * @return {WPElement} Element to render. + */ +export default function Edit( { context, attributes, setAttributes } ) { + const { isLink, rel, tagName, textAlign } = attributes; + const authorPlaceholder = useSelect( + ( select ) => select( 'co-authors-plus/blocks' ).getAuthorPlaceholder(), + [] + ); + const author = context[ 'co-authors-plus/author' ] || authorPlaceholder; + const { link, display_name } = author; + + const TagName = tagName; + + return ( + <> + + { + setAttributes( { textAlign: nextAlign } ); + } } + /> + + + { isLink ? ( + event.preventDefault() } + > + { display_name } + + ) : ( + display_name + ) } + + + + setAttributes( { isLink: ! isLink } ) } + checked={ isLink } + /> + { isLink && ( + <> + + setAttributes( { rel: newRel } ) + } + /> + + ) } + + + + )' ), value: 'p' }, + { label: '', value: 'span' }, + { label: '

', value: 'h1' }, + { label: '

', value: 'h2' }, + { label: '

', value: 'h3' }, + { label: '

', value: 'h4' }, + { label: '

', value: 'h5' }, + { label: '
', value: 'h6' }, + ] } + value={ tagName } + onChange={ ( value ) => + setAttributes( { tagName: value } ) + } + /> + + + ); +} diff --git a/src/blocks/block-coauthor-name/index.js b/src/blocks/block-coauthor-name/index.js new file mode 100644 index 00000000..1a50659e --- /dev/null +++ b/src/blocks/block-coauthor-name/index.js @@ -0,0 +1,26 @@ +/** + * Registers a new block provided a unique name and an object defining its behavior. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +import { registerBlockType } from '@wordpress/blocks'; +import { postAuthor as icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import Edit from './edit'; +import metadata from './block.json'; + +/** + * Every block starts by registering a new block type definition. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, + icon, +} ); diff --git a/src/blocks/block-coauthors/block.json b/src/blocks/block-coauthors/block.json new file mode 100644 index 00000000..02d35dfc --- /dev/null +++ b/src/blocks/block-coauthors/block.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "co-authors-plus/coauthors", + "version": "1.0.0", + "title": "Co-Authors", + "category": "theme", + "description": "Displays the co-authors of a post by using blocks to create a template. Start with co-author name and add any other co-author blocks.", + "supports": { + "html": false, + "color": { + "link": true, + "text": true, + "background": true, + "__experimentalDefaultControls": {} + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalTextDecoration": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalLetterSpacing": true, + "__experimentalTextTransform": true, + "__experimentalDefaultControls": {} + }, + "spacing": { + "margin": true, + "padding": true, + "blockGap": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false, + "blockGap": false + } + }, + "layout": true + }, + "attributes": { + "layout": { + "type": "object", + "default": { + "type": "default" + } + }, + "textAlign": { + "type": "string" + } + }, + "usesContext": [ "postId" ], + "textdomain": "co-authors-plus", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/src/blocks/block-coauthors/components/memoized-coauthor-template-block-preview.js b/src/blocks/block-coauthors/components/memoized-coauthor-template-block-preview.js new file mode 100644 index 00000000..bf0e9d0a --- /dev/null +++ b/src/blocks/block-coauthors/components/memoized-coauthor-template-block-preview.js @@ -0,0 +1,40 @@ +import { memo } from '@wordpress/element'; +import { __experimentalUseBlockPreview as useBlockPreview } from '@wordpress/block-editor'; + +/** + * CoAuthor Template Block Preview + */ +function CoAuthorTemplateBlockPreview( { + blocks, + blockContextId, + isHidden, + setActiveBlockContextId, +} ) { + const blockPreviewProps = useBlockPreview( { + blocks, + props: { + className: 'wp-block-co-authors-plus-coauthor', + }, + } ); + + const handleOnClick = () => { + setActiveBlockContextId( blockContextId ); + }; + + const style = { + display: isHidden ? 'none' : undefined, + }; + + return ( +
+ ); +} + +export default memo( CoAuthorTemplateBlockPreview ); diff --git a/src/blocks/block-coauthors/edit.js b/src/blocks/block-coauthors/edit.js new file mode 100644 index 00000000..6f223064 --- /dev/null +++ b/src/blocks/block-coauthors/edit.js @@ -0,0 +1,296 @@ +/** + * React hook that is used to mark the block wrapper element. + * It provides all the necessary props like the class name. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops + */ +import { + BlockControls, + BlockContextProvider, + useBlockProps, + useInnerBlocksProps, + store as blockEditorStore, + InspectorControls, + RichText, + __experimentalGetGapCSSValue, + AlignmentControl, +} from '@wordpress/block-editor'; +import { TextControl, ToolbarGroup, PanelBody } from '@wordpress/components'; +import apiFetch from '@wordpress/api-fetch'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { useState, useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { row, stack, grid, list, lineDashed } from '@wordpress/icons'; +import classnames from 'classnames'; + +import MemoizedCoAuthorTemplateBlockPreview from './components/memoized-coauthor-template-block-preview'; + +/** + * CoAuthor Template Inner Blocks + */ +function CoAuthorTemplateInnerBlocks() { + return ( +
+ ); +} + +const ALLOWED_FORMATS = [ 'core/bold', 'core/italic', 'core/text-color' ]; + +/** + * The edit function describes the structure of your block in the context of the + * editor. This represents what the editor will render when the block is used. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit + * + * @return {WPElement} Element to render. + */ +export default function Edit( { + attributes, + setAttributes, + clientId, + context, + isSelected, + __unstableLayoutClassNames +} ) { + const { prefix, separator, lastSeparator, suffix, layout, textAlign } = attributes; + const { type: layoutType, orientation: layoutOrientation } = layout || {}; + + const { postId } = context; + const authorPlaceholder = useSelect( + ( select ) => select( 'co-authors-plus/blocks' ).getAuthorPlaceholder(), + [] + ); + const [ coAuthors, setCoAuthors ] = useState( [ authorPlaceholder ] ); + const [ activeBlockContextId, setActiveBlockContextId ] = useState(); + const noticesDispatch = useDispatch( 'core/notices' ); + + useEffect( () => { + if ( ! postId ) { + return; + } + + const controller = new AbortController(); + + apiFetch( { + path: `/coauthors/v1/coauthors?post_id=${ postId }`, + signal: controller.signal, + } ) + .then( setCoAuthors ) + .catch( handleError ); + + return () => { + controller.abort(); + }; + }, [ postId ] ); + + /** + * Handle Error + * + * @param {Error} + */ + function handleError( error ) { + if ( 'AbortError' === error.name ) { + return; + } + noticesDispatch.createErrorNotice( error.message, { + isDismissible: true, + } ); + } + + const blocks = useSelect( ( select ) => { + return select( blockEditorStore ).getBlocks( clientId ); + } ); + + const setLayout = ( nextLayout ) => { + setAttributes( { + layout: nextLayout, + } ); + }; + + const layoutControls = [ + { + icon: lineDashed, + title: __( 'Inline view' ), + onClick: () => setLayout( { + type: 'default' + }), + isActive: layoutType === 'default' + }, + { + icon: list, + title: __( 'List view' ), + onClick: () => setLayout( { + type: 'constrained' + } ), + isActive: layoutType === 'constrained', + }, + { + icon: grid, + title: __( 'Grid view' ), + onClick: () => + setLayout( { + type: 'grid' + } ), + isActive: layoutType === 'grid', + }, + { + icon: row, + title: __( 'Row view' ), + onClick: () => + setLayout( { + type: 'flex', + orientation: 'horizontal' + } ), + isActive: layoutType === 'flex' && 'horizontal' === layoutOrientation, + }, + { + icon: stack, + title: __( 'Stack view' ), + onClick: () => + setLayout( { + type: 'flex', + orientation: 'vertical' + } ), + isActive: layoutType === 'flex' && 'vertical' === layoutOrientation, + }, + ]; + + return ( + <> + + + { + setAttributes( { textAlign: nextAlign } ); + } } + /> + +
+ { coAuthors && + 'default' === layoutType && + ( isSelected || prefix ) && ( + + setAttributes( { prefix: value } ) + } + tagName="span" + /> + ) } + { coAuthors && + coAuthors + .map( ( author ) => { + const isHidden = author.id === ( activeBlockContextId || coAuthors[ 0 ]?.id ); + return ( + + { isHidden ? ( + + ) : null } + + + ); + } ) + .reduce( ( previous, current, index, all ) => ( + <> + { previous } + { 'default' === layoutType && ( + + { lastSeparator && + index === all.length - 1 + ? `${ lastSeparator }` + : `${ separator }` } + + ) } + { current } + + ) ) } + { coAuthors && + 'default' === layoutType && + ( isSelected || suffix ) && ( + + setAttributes( { suffix: value } ) + } + tagName="span" + /> + ) } +
+ + { 'default' === layoutType && ( + + { + setAttributes( { separator: nextValue } ); + } } + help={ __( + 'Enter character(s) used to separate authors.', + 'co-authors-plus' + ) } + /> + { + setAttributes( { lastSeparator: nextValue } ); + } } + help={ __( + 'Enter character(s) used to separate the last author.', + 'co-authors-plus' + ) } + /> + + ) } + + + ); +} diff --git a/src/blocks/block-coauthors/index.js b/src/blocks/block-coauthors/index.js new file mode 100644 index 00000000..d6b58a83 --- /dev/null +++ b/src/blocks/block-coauthors/index.js @@ -0,0 +1,38 @@ +/** + * Registers a new block provided a unique name and an object defining its behavior. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +import { registerBlockType } from '@wordpress/blocks'; +import { people as icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import Edit from './edit'; +import save from './save'; +import metadata from './block.json'; + +/** + * Style shared between editor and content + */ +import './style.css'; + +/** + * Every block starts by registering a new block type definition. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, + + /** + * @see ./save.js + */ + save, + + icon, +} ); diff --git a/src/blocks/block-coauthors/save.js b/src/blocks/block-coauthors/save.js new file mode 100644 index 00000000..0b8bda78 --- /dev/null +++ b/src/blocks/block-coauthors/save.js @@ -0,0 +1,27 @@ +/** + * Save + */ +import { + useBlockProps, + InnerBlocks, +} from '@wordpress/block-editor'; +import classnames from 'classnames'; + +/** + * Save + * + * @return {WPElement} Element to render. + */ +export default function save( { attributes } ) { + const { textAlign } = attributes; + + const className = classnames( { + [ `has-text-align-${ textAlign }` ]: textAlign, + } ); + + return ( +
+ +
+ ); +} diff --git a/src/blocks/block-coauthors/style.css b/src/blocks/block-coauthors/style.css new file mode 100644 index 00000000..69e11bda --- /dev/null +++ b/src/blocks/block-coauthors/style.css @@ -0,0 +1,3 @@ +.wp-block-co-authors-plus-coauthors.is-layout-flow [class*=wp-block-co-authors-plus] { + display: inline; +} diff --git a/src/blocks/components/placeholder-image.jsx b/src/blocks/components/placeholder-image.jsx new file mode 100644 index 00000000..c6cac7f7 --- /dev/null +++ b/src/blocks/components/placeholder-image.jsx @@ -0,0 +1,64 @@ +import { useMemo } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Encode SVG + * + * @param {string} svgHTML + * @return {string} + */ +function encodeSVG( svgHTML ) { + return ( + encodeURIComponent( + svgHTML + // Strip newlines and tabs + .replace( /[\t\n\r]/gim, '' ) + // Condense multiple spaces + .replace( /\s\s+/g, ' ' ) + ) + // Encode parenthesis + .replace( /\(/g, '%28' ) + .replace( /\)/g, '%29' ) + ); +} + +/** + * Get Placeholder Src + * + * @param {Object} { width, height } + * @return {string} + */ +function getPlaceholderSrc( { width, height } ) { + const svg = encodeSVG( + ` + + + ` + ); + return `data:image/svg+xml;charset=UTF-8,${ svg }`; +} + +/** + * Placeholder Image + * + * @export + * @param {Object} props { dimensions, style, className } + * @return {WPElement} + */ +export default function PlaceholderImage( { dimensions, style, className } ) { + const src = useMemo( + () => getPlaceholderSrc( dimensions ), + [ dimensions ] + ); + + return ( + { + ); +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..e8640692 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,14 @@ +const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); + +module.exports = { + ...defaultConfig, + entry : { + 'index': './src/index.js', + 'blocks/block-coauthor-avatar/index': './src/blocks/block-coauthor-avatar/index.js', + 'blocks/block-coauthor-description/index': './src/blocks/block-coauthor-description/index.js', + 'blocks/block-coauthor-image/index': './src/blocks/block-coauthor-image/index.js', + 'blocks/block-coauthor-name/index': './src/blocks/block-coauthor-name/index.js', + 'blocks/block-coauthors/index': './src/blocks/block-coauthors/index.js', + 'blocks-store/index': './src/blocks-store/index.js', + } +};