Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "superlist-block",
"version": "0.1.1",
"version": "0.2.0",
"description": "A list block that allows you to nest other blocks in each list item.",
"author": "Aurooba Ahmed",
"license": "GPL-2.0-or-later",
Expand Down
14 changes: 13 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tags: block, list, nesting, repeater, superlist
Requires at least: 6.3
Tested up to: 6.9
Requires PHP: 7.0
Stable tag: 0.1.4
Stable tag: 0.2.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Donate link: https://github.com/sponsors/aurooba
Expand Down Expand Up @@ -58,6 +58,18 @@ https://youtu.be/wqzw-XrwuQE

== Changelog ==

= 0.2.0 =
* Fix critical editor crash caused by missing `sprintf` import in the list-item block (#60).
* Update block icons to a simpler black-on-transparent style for better cohesion with core blocks (#44).
* Rename "List Orientation" to "List Style" and the previous "List Style" (ul/ol/none) to "List Type", with clearer icons (#36).
* Add background image support via the standard block-supports API (#35).
* Add support for ordered list `start` value and reverse numbering (#25).
* Add optional list title rendered as `<figure>` + `<figcaption>` (#13).
* Surface the list-item max-width control on the list-item block as well (#9).
* Add a "double-Enter on an empty trailing paragraph" shortcut to create a new list item (#7).
* Add a custom marker character (e.g. ★, →, •) for unordered lists (#3).
* Update transforms to/from the modern `core/list` block, which uses nested `core/list-item` inner blocks (#54).

= 0.1.4 =
* Fix issue where you can't add new list items
* When adding a new Super List, only one list item is added by default
Expand Down
193 changes: 155 additions & 38 deletions src/superlist-item/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* @see https://developer.wordpress.org/block-editor/packages/packages-i18n/
*/
import { __ } from "@wordpress/i18n";
import { __, sprintf } from "@wordpress/i18n";

/**
* React hook that is used to mark the block wrapper element.
Expand All @@ -15,13 +15,21 @@ import {
useBlockProps,
InnerBlocks,
BlockControls,
InspectorControls,
useInnerBlocksProps,
store as blockEditorStore,
} from "@wordpress/block-editor";
import { ToolbarButton, ToolbarGroup } from "@wordpress/components";
import {
ToolbarButton,
ToolbarGroup,
PanelBody,
PanelRow,
__experimentalUnitControl as UnitControl,
} from "@wordpress/components";
import { useSelect, useDispatch } from "@wordpress/data";
import { createBlock, store as blocksStore } from "@wordpress/blocks";
import { plusCircle } from "@wordpress/icons";
import { useCallback } from "@wordpress/element";

/**
* Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
Expand All @@ -32,6 +40,7 @@ import { plusCircle } from "@wordpress/icons";
import "./editor.scss";

const LISTITEM_TEMPLATE = [["core/paragraph"]];

/**
* 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.
Expand All @@ -42,48 +51,136 @@ const LISTITEM_TEMPLATE = [["core/paragraph"]];
*/
export default function Edit(props) {
const { clientId, name } = props;
const { insertBlock, selectBlock } = useDispatch("core/block-editor");
const { parentBlock, parentClientId, parentBlockType, hasInnerBlocks } = useSelect((select) => {
const parentClientId = select(blockEditorStore).getBlockParentsByBlockName(
clientId,
"createwithrani/superlist-block",
)[0];
const parentBlock = select(blockEditorStore).getBlock(parentClientId);
const parentBlockType = select(blocksStore).getBlockType(
parentBlock ? parentBlock.name : "",
);
const { getBlock } = select(blockEditorStore);
const block = getBlock(clientId);
return {
parentClientId,
parentBlock,
parentBlockType,
hasInnerBlocks: !!(block && block.innerBlocks.length),
};
}, [clientId, name]);

// set up block properties and inner blocks settings
const blockProps = useBlockProps({});
const { insertBlock, selectBlock, removeBlock, updateBlockAttributes } =
useDispatch(blockEditorStore);

const {
parentBlock,
parentClientId,
parentBlockType,
parentItemWidth,
parentOrientation,
hasInnerBlocks,
innerBlocks,
selectedBlockClientId,
} = useSelect(
(select) => {
const store = select(blockEditorStore);
const blocks = select(blocksStore);

const parents = store.getBlockParentsByBlockName(
clientId,
"createwithrani/superlist-block"
);
const parentId = parents[0];
const parent = parentId ? store.getBlock(parentId) : null;
const parentType = blocks.getBlockType(
parent ? parent.name : ""
);
const block = store.getBlock(clientId);

return {
parentClientId: parentId,
parentBlock: parent,
parentBlockType: parentType,
parentItemWidth: parent ? parent.attributes.itemWidth : undefined,
parentOrientation: parent ? parent.attributes.orientation : undefined,
hasInnerBlocks: !!(block && block.innerBlocks.length),
innerBlocks: block ? block.innerBlocks : [],
selectedBlockClientId: store.getSelectedBlockClientId(),
};
},
[clientId]
);

// Insert a new list item block after the current block. The toolbar "Add"
// button leaves the new item empty (matching core's list behavior); the
// double-enter shortcut prefills it with an empty paragraph so the cursor
// has somewhere to land for typing.
const insertListItem = useCallback(
({ withParagraph = false } = {}) => {
if (!parentBlock || !parentClientId) {
return null;
}
const inner = withParagraph ? [createBlock("core/paragraph")] : [];
const newListItem = createBlock(name, {}, inner);

const currentIndex = parentBlock.innerBlocks.findIndex(
(block) => block.clientId === clientId
);
const insertIndex =
currentIndex === -1
? parentBlock.innerBlocks.length
: currentIndex + 1;
insertBlock(newListItem, insertIndex, parentClientId);
return newListItem.clientId;
},
[parentBlock, parentClientId, clientId, name, insertBlock]
);

const blockProps = useBlockProps({
// When the user presses Enter inside an empty trailing paragraph of
// this list-item, escape out and start a new list-item. The first
// Enter (in a non-empty paragraph) splits the paragraph as usual; the
// second Enter — now in the resulting empty paragraph — fires this.
onKeyDown: (event) => {
if (
event.key !== "Enter" ||
event.shiftKey ||
event.metaKey ||
event.ctrlKey ||
event.isDefaultPrevented()
) {
return;
}
if (!innerBlocks || innerBlocks.length === 0) {
return;
}

const lastChild = innerBlocks[innerBlocks.length - 1];
if (
!lastChild ||
lastChild.name !== "core/paragraph" ||
lastChild.clientId !== selectedBlockClientId
) {
return;
}

const content = lastChild.attributes && lastChild.attributes.content;
const contentString =
content == null
? ""
: typeof content === "string"
? content
: typeof content.toString === "function"
? content.toString()
: "";

if (contentString.replace(/<br\s*\/?>/gi, "").trim() !== "") {
return;
}

event.preventDefault();
removeBlock(lastChild.clientId, false);
insertListItem({ withParagraph: true });
},
});

const innerBlockProps = useInnerBlocksProps(blockProps, {
templateInsertUpdateSelection: true,
renderAppender: hasInnerBlocks
? undefined
: InnerBlocks.ButtonBlockAppender,
});

// Insert a new list item block after the current block
const insertListItem = () => {
const newListItem = createBlock(name);

// Get the index of the current block in the parent block's inner blocks
const currentIndex = parentBlock.innerBlocks.findIndex(
(block) => block.clientId === clientId,
);

// Insert the new list item block after the current block
const insertIndex = currentIndex === -1 ? parentBlock.innerBlocks.length : currentIndex + 1;
insertBlock(newListItem, insertIndex, parentClientId);
};
const updateParentItemWidth = useCallback(
(value) => {
if (parentClientId) {
updateBlockAttributes(parentClientId, { itemWidth: value });
}
},
[parentClientId, updateBlockAttributes]
);

return (
<>
Expand All @@ -104,12 +201,32 @@ export default function Edit(props) {
</ToolbarGroup>
<ToolbarGroup>
<ToolbarButton
onClick={insertListItem}
onClick={() => insertListItem()}
icon={plusCircle}
label={__("Add another list item", "superlist-block")}
/>
</ToolbarGroup>
</BlockControls>
{parentOrientation === "horizontal" && (
<InspectorControls>
<PanelBody
title={__("List Settings", "superlist-block")}
initialOpen={true}
>
<PanelRow>
<UnitControl
label={__("List-item max-width", "superlist-block")}
help={__(
"Applies to all items in this list.",
"superlist-block"
)}
value={parentItemWidth || ""}
onChange={updateParentItemWidth}
/>
</PanelRow>
</PanelBody>
</InspectorControls>
)}
<li {...innerBlockProps} />
</>
);
Expand Down
19 changes: 13 additions & 6 deletions src/superlist-item/icons.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
/**
* Monochrome list-item icon: a single bold square + line, on transparent
* background, to match the core block icon style.
*/
export const ListItem = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="10.814"
height="2.703"
viewBox="0 0 10.814 2.703"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M8.8 6.811v1.014h6.015V6.811zM5.352 6A1.352 1.352 0 106.7 7.352 1.356 1.356 0 005.352 6z"
transform="translate(-4 -6)"
></path>
fillRule="evenodd"
clipRule="evenodd"
d="M3 11a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-2zm6.5.25a.75.75 0 0 1 .75-.75h9.5a.75.75 0 0 1 0 1.5h-9.5a.75.75 0 0 1-.75-.75z"
fill="currentColor"
/>
</svg>
);
20 changes: 18 additions & 2 deletions src/superlist/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
"$schema": "https://json.schemastore.org/block.json",
"apiVersion": 3,
"name": "createwithrani/superlist-block",
"version": "0.1.3",
"version": "0.2.0",
"title": "Super List",
"category": "design",
"description": "Nest multiple blocks inside list items in any kind of list (ordered, unordered, no marker, etc)",
"allowedBlocks": ["createwithrani/superlist-item"],
"attributes": {
"listStyle":{
"listStyle": {
"type": "string",
"default": "ul"
},
Expand All @@ -20,6 +20,18 @@
},
"verticalAlignment": {
"type": "string"
},
"start": {
"type": "number"
},
"reversed": {
"type": "boolean"
},
"caption": {
"type": "string"
},
"customMarker": {
"type": "string"
}
},
"supports": {
Expand All @@ -32,6 +44,10 @@
"text": true,
"link": true
},
"background": {
"backgroundImage": true,
"backgroundSize": true
},
"spacing": {
"margin": true,
"padding": true
Expand Down
Loading