├── packages
├── eslint-import-resolver
│ ├── README.md
│ └── src
│ │ ├── test
│ │ └── resolve.ts
│ │ └── index.js
├── editor
│ ├── src
│ │ ├── style.css
│ │ ├── @types
│ │ │ └── wordpress__block-editor
│ │ │ │ ├── store
│ │ │ │ ├── defaults.d.ts
│ │ │ │ ├── index.d.ts
│ │ │ │ ├── actions.d.ts
│ │ │ │ └── selectors.d.ts
│ │ │ │ └── index.d.ts
│ │ ├── index.tsx
│ │ ├── formats.ts
│ │ ├── blockControls
│ │ │ ├── index.tsx
│ │ │ ├── imageControls.tsx
│ │ │ ├── autocomplete.tsx
│ │ │ └── paragraphControls.tsx
│ │ ├── workers
│ │ │ └── altText.ts
│ │ ├── utils.ts
│ │ └── commands.tsx
│ └── tsconfig.json
├── summarize-button
│ ├── src
│ │ ├── @types
│ │ │ └── wordpress__block-editor
│ │ │ │ ├── store
│ │ │ │ ├── defaults.d.ts
│ │ │ │ ├── index.d.ts
│ │ │ │ ├── actions.d.ts
│ │ │ │ └── selectors.d.ts
│ │ │ │ └── index.d.ts
│ │ ├── edit.tsx
│ │ ├── block.json
│ │ ├── index.tsx
│ │ ├── style.css
│ │ └── view.ts
│ └── tsconfig.json
└── translate-button
│ ├── tsconfig.json
│ └── src
│ ├── style.css
│ ├── edit.tsx
│ ├── block.json
│ ├── index.tsx
│ └── view.ts
├── tsconfig.json
├── ai-experiments.php
├── patches
├── @wordpress+editor+14.26.0.patch
├── @wordpress+components+29.3.0.patch
├── @huggingface+transformers+3.6.1.patch
└── react-autosize-textarea+7.1.0.patch
├── blueprints
└── playground.json
├── tsconfig.shared.json
├── composer.json
├── webpack.config.js
├── README.md
├── LICENSE
└── inc
└── functions.php
/packages/eslint-import-resolver/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/editor/src/style.css:
--------------------------------------------------------------------------------
1 | .aiwp-autocomplete {
2 | color: green;
3 | }
4 |
--------------------------------------------------------------------------------
/packages/editor/src/@types/wordpress__block-editor/store/defaults.d.ts:
--------------------------------------------------------------------------------
1 | export const SETTINGS_DEFAULTS: Record< string, unknown >;
2 |
--------------------------------------------------------------------------------
/packages/editor/src/index.tsx:
--------------------------------------------------------------------------------
1 | import './blockControls';
2 | import './commands';
3 | import './formats';
4 |
5 | import './style.css';
6 |
--------------------------------------------------------------------------------
/packages/summarize-button/src/@types/wordpress__block-editor/store/defaults.d.ts:
--------------------------------------------------------------------------------
1 | export const SETTINGS_DEFAULTS: Record< string, unknown >;
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "references": [
3 | {
4 | "path": "packages/editor"
5 | },
6 | {
7 | "path": "packages/summarize-button"
8 | }
9 | ],
10 | "files": []
11 | }
12 |
--------------------------------------------------------------------------------
/packages/editor/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.shared.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "declarationDir": "dist-types"
6 | },
7 | "references": [],
8 | "include": [ "src/**/*" ]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/summarize-button/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.shared.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "declarationDir": "dist-types"
6 | },
7 | "references": [],
8 | "include": [ "src/**/*" ]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/translate-button/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.shared.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "declarationDir": "dist-types"
6 | },
7 | "references": [],
8 | "include": [ "src/**/*" ]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/editor/src/@types/wordpress__block-editor/store/index.d.ts:
--------------------------------------------------------------------------------
1 | // This is only used internally by `@wordpress/editor`. Not worth the effort of typing this.
2 | export const storeConfig: {
3 | reducer: any;
4 | selectors: any;
5 | actions: any;
6 | controls: any;
7 | };
8 |
--------------------------------------------------------------------------------
/packages/summarize-button/src/@types/wordpress__block-editor/store/index.d.ts:
--------------------------------------------------------------------------------
1 | // This is only used internally by `@wordpress/editor`. Not worth the effort of typing this.
2 | export const storeConfig: {
3 | reducer: any;
4 | selectors: any;
5 | actions: any;
6 | controls: any;
7 | };
8 |
--------------------------------------------------------------------------------
/packages/eslint-import-resolver/src/test/resolve.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from '../';
2 |
3 | describe( 'resolve', () => {
4 | it( 'resolves file in monorepo package', () => {
5 | const result = resolve( '@ai-experiments/upload-media', 'index.ts' );
6 | expect( result.found ).toBe( true );
7 | } );
8 | } );
9 |
--------------------------------------------------------------------------------
/packages/editor/src/formats.ts:
--------------------------------------------------------------------------------
1 | import { registerFormatType } from '@wordpress/rich-text';
2 |
3 | registerFormatType( 'ai-experiments/autocomplete', {
4 | name: 'ai-experiments/autocomplete',
5 | title: 'Autocomplete',
6 | tagName: 'samp',
7 | className: 'aiwp-autocomplete',
8 | interactive: false,
9 | edit: () => null,
10 | object: false,
11 | } );
12 |
--------------------------------------------------------------------------------
/packages/translate-button/src/style.css:
--------------------------------------------------------------------------------
1 | .ai-translation-button svg {
2 | fill: currentColor;
3 | }
4 |
5 | .ai-translation-button {
6 | display: none !important;
7 | }
8 |
9 | .ai-translation-button.visible {
10 | display: flex !important;
11 | }
12 |
13 | .ai-translation-text {
14 | display: none;
15 | }
16 |
17 | .ai-translation-text.visible {
18 | display: block;
19 | }
20 |
--------------------------------------------------------------------------------
/packages/translate-button/src/edit.tsx:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import { useBlockProps } from '@wordpress/block-editor';
3 |
4 | export default function Edit( {
5 | // @ts-ignore
6 | __unstableLayoutClassNames: layoutClassNames,
7 | } ) {
8 | const blockProps = useBlockProps( { className: layoutClassNames } );
9 | return (
10 |
11 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/packages/summarize-button/src/edit.tsx:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import { useBlockProps } from '@wordpress/block-editor';
3 |
4 | export default function Edit( {
5 | // @ts-ignore
6 | __unstableLayoutClassNames: layoutClassNames,
7 | } ) {
8 | const blockProps = useBlockProps( { className: layoutClassNames } );
9 | return (
10 |
11 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/packages/translate-button/src/block.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schemas.wp.org/trunk/block.json",
3 | "apiVersion": 3,
4 | "name": "ai-experiments/translate-button",
5 | "version": "0.0.1",
6 | "title": "Translate",
7 | "category": "widgets",
8 | "description": "Block that allows translating a comment",
9 | "supports": {
10 | "html": false,
11 | "layout": true,
12 | "interactivity": true
13 | },
14 | "textdomain": "translate-button",
15 | "blockHooks": {
16 | "core/post-content": "before",
17 | "core/comment-content": "after"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/summarize-button/src/block.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schemas.wp.org/trunk/block.json",
3 | "apiVersion": 3,
4 | "name": "ai-experiments/summarize-button",
5 | "version": "0.0.1",
6 | "title": "Summarize",
7 | "category": "widgets",
8 | "description": "Block that allows summarizing the post content",
9 | "supports": {
10 | "html": false,
11 | "layout": true,
12 | "interactivity": true
13 | },
14 | "textdomain": "ai-experiments",
15 | "blockHooks": {
16 | "core/post-content": "before",
17 | "core/comment-template": "before"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ai-experiments.php:
--------------------------------------------------------------------------------
1 | (
9 |
18 | );
19 |
20 | // @ts-ignore
21 | registerBlockType( metadata.name, {
22 | ...metadata,
23 | edit: Edit,
24 | icon: SparkIcon,
25 | } );
26 |
--------------------------------------------------------------------------------
/packages/translate-button/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { registerBlockType } from '@wordpress/blocks';
2 |
3 | import './style.css';
4 |
5 | import Edit from './edit';
6 | import metadata from './block.json';
7 |
8 | const SparkIcon = () => (
9 |
18 | );
19 |
20 | // @ts-ignore
21 | registerBlockType( metadata.name, {
22 | ...metadata,
23 | edit: Edit,
24 | icon: SparkIcon,
25 | } );
26 |
--------------------------------------------------------------------------------
/packages/summarize-button/src/style.css:
--------------------------------------------------------------------------------
1 | .ai-summary-button svg {
2 | fill: currentColor;
3 | }
4 |
5 | .ai-summary-popover {
6 | top: calc(anchor(bottom) + 20px);
7 | left: 50vw;
8 | translate: -50% 0;
9 |
10 | background: #eee;
11 | border: 1px solid #333;
12 | position: relative;
13 | margin: 0 !important;
14 | padding: 40px 40px 20px 20px;
15 | min-width: 50vw;
16 | min-height: 20vh;
17 | }
18 |
19 | .ai-summary-popover::backdrop {
20 | }
21 |
22 | .ai-summary-popover-close {
23 | position: absolute;
24 | top: 20px;
25 | right: 20px;
26 | cursor: pointer;
27 | z-index: 5000000;
28 | min-width: 40px;
29 | align-items: center;
30 | justify-content: center;
31 | }
32 |
33 | .ai-summary-popover p {
34 | margin: 0 !important;
35 | }
36 |
--------------------------------------------------------------------------------
/patches/@wordpress+editor+14.26.0.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/@wordpress/editor/build-types/store/actions.d.ts b/node_modules/@wordpress/editor/build-types/store/actions.d.ts
2 | index ec245fc..c0da05e 100644
3 | --- a/node_modules/@wordpress/editor/build-types/store/actions.d.ts
4 | +++ b/node_modules/@wordpress/editor/build-types/store/actions.d.ts
5 | @@ -217,7 +217,7 @@ export function autosave({ local, ...options }?: {
6 | select: any;
7 | dispatch: any;
8 | }) => Promise;
9 | -export function __unstableSaveForPreview({ forceIsAutosaveable }?: {}): ({ select, dispatch }: {
10 | +export function __unstableSaveForPreview({ forceIsAutosaveable }?: { forceIsAutosaveable?: boolean }): ({ select, dispatch }: {
11 | select: any;
12 | dispatch: any;
13 | }) => Promise;
14 |
--------------------------------------------------------------------------------
/blueprints/playground.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://playground.wordpress.net/blueprint-schema.json",
3 | "landingPage": "/wp-admin/post-new.php",
4 | "preferredVersions": {
5 | "php": "latest",
6 | "wp": "latest"
7 | },
8 | "phpExtensionBundles": [ "kitchen-sink" ],
9 | "steps": [
10 | {
11 | "step": "installPlugin",
12 | "pluginZipFile": {
13 | "resource": "url",
14 | "url": "https://swissspidy.github.io/ai-experiments/nightly.zip"
15 | }
16 | },
17 | {
18 | "step": "activatePlugin",
19 | "pluginName": "AI Experiments",
20 | "pluginPath": "/wordpress/wp-content/plugins/ai-experiments"
21 | },
22 | {
23 | "step": "login",
24 | "username": "admin",
25 | "password": "password"
26 | },
27 | {
28 | "step": "defineWpConfigConsts",
29 | "consts": {
30 | "WPAI_IS_PLAYGROUND": true
31 | }
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/packages/editor/src/blockControls/index.tsx:
--------------------------------------------------------------------------------
1 | import { createHigherOrderComponent } from '@wordpress/compose';
2 | import { addFilter } from '@wordpress/hooks';
3 |
4 | import { ParagraphControls } from './paragraphControls';
5 | import { ImageControls } from './imageControls';
6 | import { Autocomplete } from './autocomplete';
7 |
8 | const addAiControls = createHigherOrderComponent(
9 | ( BlockEdit ) => ( props ) => {
10 | if ( props.name === 'core/paragraph' ) {
11 | return (
12 | <>
13 |
14 |
15 |
16 | >
17 | );
18 | }
19 |
20 | if ( props.name === 'core/image' ) {
21 | return (
22 | <>
23 |
24 |
25 | >
26 | );
27 | }
28 |
29 | return ;
30 | },
31 | 'withAiControls'
32 | );
33 |
34 | addFilter(
35 | 'editor.BlockEdit',
36 | 'ai-experiments/add-ai-controls',
37 | addAiControls
38 | );
39 |
--------------------------------------------------------------------------------
/patches/@wordpress+components+29.3.0.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/@wordpress/components/build-types/card/card-divider/hook.d.ts b/node_modules/@wordpress/components/build-types/card/card-divider/hook.d.ts
2 | index dc94460..c886f2c 100644
3 | --- a/node_modules/@wordpress/components/build-types/card/card-divider/hook.d.ts
4 | +++ b/node_modules/@wordpress/components/build-types/card/card-divider/hook.d.ts
5 | @@ -270,7 +270,7 @@ export declare function useCardDivider(props: WordPressComponentProps {
25 | const resolve = ( sourcePath ) =>
26 | nodeResolver.resolve( sourcePath, file, {
27 | ...config,
28 | extensions: [ '.tsx', '.ts', '.mjs', '.js', '.json' ],
29 | } );
30 |
31 | if ( source.startsWith( '@ai-experiments/' ) ) {
32 | const packageName = source.slice( '@ai-experiments/'.length );
33 |
34 | const result = resolve( path.join( PACKAGES_DIR, packageName ) );
35 |
36 | if ( result.found ) {
37 | return result;
38 | }
39 |
40 | return resolve( path.join( PACKAGES_DIR, `${ packageName }/src/` ) );
41 | }
42 |
43 | return resolve( source );
44 | };
45 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "swissspidy/ai-experiments",
3 | "description": "WordPress AI experiments",
4 | "license": "Apache-2.0",
5 | "type": "wordpress-plugin",
6 | "authors": [
7 | {
8 | "name": "Pascal Birchler",
9 | "email": "swissspidy@chat.wordpress.org",
10 | "homepage": "https://pascalbirchler.com",
11 | "role": "Developer"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=8.0",
16 | "ext-json": "*"
17 | },
18 | "require-dev": {
19 | "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0",
20 | "php-stubs/wordpress-tests-stubs": "dev-master",
21 | "phpcompatibility/phpcompatibility-wp": "^2.0",
22 | "phpstan/extension-installer": "^1.3",
23 | "roave/security-advisories": "dev-latest",
24 | "szepeviktor/phpstan-wordpress": "^v2.0.1",
25 | "wp-coding-standards/wpcs": "^3.0.1",
26 | "yoast/phpunit-polyfills": "^3.0.0"
27 | },
28 | "minimum-stability": "dev",
29 | "config": {
30 | "allow-plugins": {
31 | "dealerdirect/phpcodesniffer-composer-installer": true,
32 | "phpstan/extension-installer": true
33 | },
34 | "platform": {
35 | "php": "8.0.28"
36 | }
37 | },
38 | "scripts": {
39 | "format": "vendor/bin/phpcbf --report-summary --report-source .",
40 | "lint": "vendor/bin/phpcs --report-summary --report-source .",
41 | "phpstan": "phpstan analyse --memory-limit=2048M",
42 | "test": "vendor/bin/phpunit",
43 | "test:multisite": "vendor/bin/phpunit -c phpunit-multisite.xml.dist"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/patches/@huggingface+transformers+3.6.1.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/@huggingface/transformers/types/models/auto/processing_auto.d.ts b/node_modules/@huggingface/transformers/types/models/auto/processing_auto.d.ts
2 | index b8e76a2..9289cca 100644
3 | --- a/node_modules/@huggingface/transformers/types/models/auto/processing_auto.d.ts
4 | +++ b/node_modules/@huggingface/transformers/types/models/auto/processing_auto.d.ts
5 | @@ -1,3 +1,4 @@
6 | +import type { PretrainedProcessorOptions } from '../../base/processing_utils';
7 | /**
8 | * Helper class which is used to instantiate pretrained processors with the `from_pretrained` function.
9 | * The chosen processor class is determined by the type specified in the processor config.
10 | diff --git a/node_modules/@huggingface/transformers/types/models/mgp_str/processing_mgp_str.d.ts b/node_modules/@huggingface/transformers/types/models/mgp_str/processing_mgp_str.d.ts
11 | index 4e40812..b830b6f 100644
12 | --- a/node_modules/@huggingface/transformers/types/models/mgp_str/processing_mgp_str.d.ts
13 | +++ b/node_modules/@huggingface/transformers/types/models/mgp_str/processing_mgp_str.d.ts
14 | @@ -49,6 +49,7 @@ export class MgpstrProcessor extends Processor {
15 | * - bpe_preds: The list of BPE decoded sentences.
16 | * - wp_preds: The list of wp decoded sentences.
17 | */
18 | + // @ts-ignore
19 | batch_decode([char_logits, bpe_logits, wp_logits]: import("../../utils/tensor.js").Tensor[]): {
20 | generated_text: string[];
21 | scores: number[];
22 |
--------------------------------------------------------------------------------
/packages/editor/src/workers/altText.ts:
--------------------------------------------------------------------------------
1 | import {
2 | env,
3 | Florence2ForConditionalGeneration,
4 | AutoProcessor,
5 | AutoTokenizer,
6 | RawImage,
7 | type Tensor,
8 | PreTrainedModel,
9 | Processor,
10 | PreTrainedTokenizer,
11 | } from '@huggingface/transformers';
12 |
13 | let model: PreTrainedModel;
14 | let processor: Processor;
15 | let tokenizer: PreTrainedTokenizer;
16 |
17 | async function loadModels() {
18 | env.allowLocalModels = false;
19 | env.allowRemoteModels = true;
20 | if ( env.backends.onnx.wasm ) {
21 | env.backends.onnx.wasm.proxy = false;
22 | }
23 |
24 | const modelId = 'onnx-community/Florence-2-base-ft';
25 | model = await Florence2ForConditionalGeneration.from_pretrained( modelId, {
26 | dtype: 'fp32',
27 | device: 'webgpu',
28 | } );
29 | processor = await AutoProcessor.from_pretrained( modelId );
30 | tokenizer = await AutoTokenizer.from_pretrained( modelId );
31 | }
32 |
33 | export async function runTask(
34 | url: string,
35 | task:
36 | | ''
37 | | ''
38 | | '' = ''
39 | ) {
40 | if ( ! processor || ! model || ! tokenizer ) {
41 | await loadModels();
42 | }
43 |
44 | if ( ! processor || ! model || ! tokenizer ) {
45 | return;
46 | }
47 |
48 | // Load image and prepare vision inputs
49 | const image = await RawImage.fromURL( url );
50 | // @ts-ignore
51 | const visionInputs = await processor( image );
52 |
53 | // @ts-ignore
54 | const prompts = processor.construct_prompts( task );
55 | // @ts-ignore
56 | const textInputs = tokenizer( prompts );
57 |
58 | // Generate text
59 | // @ts-ignore
60 | const generatedIds = ( await model.generate( {
61 | ...textInputs,
62 | ...visionInputs,
63 | max_new_tokens: 100,
64 | } ) ) as Tensor;
65 |
66 | // Decode generated text
67 | // @ts-ignore
68 | const generatedText = tokenizer.batch_decode( generatedIds, {
69 | skip_special_tokens: false,
70 | } )[ 0 ];
71 |
72 | // Post-process the generated text
73 | // @ts-ignore
74 | const result = processor.post_process_generation(
75 | generatedText,
76 | task,
77 | image.size
78 | );
79 |
80 | return result[ task ];
81 | }
82 |
--------------------------------------------------------------------------------
/packages/summarize-button/src/view.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { store, getContext } from '@wordpress/interactivity';
5 |
6 | async function summarizePostContent() {
7 | const postContent =
8 | document.querySelector( '.wp-block-post-content' )?.textContent || '';
9 | const summarizer = await Summarizer.create( {
10 | sharedContext: 'A blog post',
11 | format: 'plain-text',
12 | } );
13 | return summarizer.summarize( postContent, {
14 | context: 'Avoid any toxic language and be as constructive as possible.',
15 | } );
16 | }
17 |
18 | async function summarizeComments() {
19 | let allComments = '';
20 | document
21 | .querySelectorAll( '.wp-block-comment-content' )
22 | .forEach( ( node ) => ( allComments += node.textContent + '\n\n' ) );
23 | const summarizer = await Summarizer.create( {
24 | sharedContext: 'A list of user-generated comments on a blog post',
25 | format: 'plain-text',
26 | type: 'tldr',
27 | } );
28 | return summarizer.summarize( allComments, {
29 | context: 'Avoid any toxic language and be as constructive as possible.',
30 | } );
31 | }
32 |
33 | type BlockContext = {
34 | summaryContext: string;
35 | summary: string;
36 | isOpen: boolean;
37 | isLoading: boolean;
38 | buttonText: string;
39 | };
40 |
41 | store(
42 | 'ai-experiments/summarize-button',
43 | {
44 | state: {
45 | get isLoading(): boolean {
46 | const context = getContext< BlockContext >();
47 | return context.isOpen && ! context.summary;
48 | },
49 | },
50 | actions: {
51 | *generateSummary() {
52 | const context = getContext< BlockContext >();
53 |
54 | context.isOpen = ! context.isOpen;
55 |
56 | if ( ! context.summary ) {
57 | context.isLoading = true;
58 | context.buttonText = 'Loading...';
59 |
60 | if ( 'post' === context.summaryContext ) {
61 | context.summary = yield summarizePostContent();
62 | } else {
63 | context.summary = yield summarizeComments();
64 | }
65 |
66 | context.buttonText = 'Read AI-generated summary';
67 | context.isLoading = false;
68 | }
69 | },
70 | },
71 | callbacks: {
72 | closeSummary: () => {
73 | const context = getContext< BlockContext >();
74 |
75 | context.isOpen = ! context.isOpen;
76 | context.buttonText = 'Read AI-generated summary';
77 | },
78 | },
79 | },
80 | { lock: true }
81 | );
82 |
--------------------------------------------------------------------------------
/packages/editor/src/blockControls/imageControls.tsx:
--------------------------------------------------------------------------------
1 | import { createWorkerFactory } from '@shopify/web-worker';
2 |
3 | import { __ } from '@wordpress/i18n';
4 | import { BlockControls } from '@wordpress/block-editor';
5 | import { ToolbarDropdownMenu } from '@wordpress/components';
6 | import { useState } from '@wordpress/element';
7 |
8 | const createAltTextWorker = createWorkerFactory(
9 | () => import( /* webpackChunkName: 'foo' */ '../workers/altText' )
10 | );
11 |
12 | const altTextWorker = createAltTextWorker();
13 |
14 | const photoSparkIcon = () => (
15 |
24 | );
25 |
26 | // @ts-ignore
27 | export function ImageControls( { attributes, setAttributes } ) {
28 | const [ inProgress, setInProgress ] = useState( false );
29 |
30 | if ( ! attributes.url ) {
31 | return null;
32 | }
33 |
34 | const controls = [
35 | {
36 | title: __( 'Write caption', 'ai-experiments' ),
37 | onClick: async () => {
38 | setInProgress( true );
39 |
40 | const task = '';
41 | const result = await altTextWorker.runTask(
42 | attributes.url,
43 | task
44 | );
45 |
46 | setAttributes( { caption: result } );
47 |
48 | setInProgress( false );
49 | },
50 | role: 'menuitemradio',
51 | icon: undefined,
52 | },
53 | {
54 | title: __( 'Write alternative text', 'ai-experiments' ),
55 | onClick: async () => {
56 | setInProgress( true );
57 |
58 | const task = '';
59 | const result = await altTextWorker.runTask(
60 | attributes.url,
61 | task
62 | );
63 |
64 | setAttributes( { alt: result } );
65 |
66 | setInProgress( false );
67 | },
68 | role: 'menuitemradio',
69 | icon: undefined,
70 | },
71 | ];
72 | return (
73 |
74 |
82 |
83 | );
84 | }
85 |
--------------------------------------------------------------------------------
/packages/editor/src/@types/wordpress__block-editor/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@wordpress/block-editor' {
2 | import type {
3 | ReduxStoreConfig,
4 | StoreDescriptor,
5 | } from '@wordpress/data/build-types/types';
6 |
7 | import type {
8 | ComponentType,
9 | FC,
10 | MouseEventHandler,
11 | ReactFragment,
12 | ReactNode,
13 | ComponentProps,
14 | } from 'react';
15 | import type { Slot, Toolbar } from '@wordpress/components';
16 |
17 | const store: {
18 | name: 'core/block-editor';
19 | } & StoreDescriptor<
20 | ReduxStoreConfig<
21 | unknown,
22 | typeof import('./store/actions'),
23 | typeof import('./store/selectors')
24 | >
25 | >;
26 |
27 | import type { Ref, RefCallback } from 'react';
28 |
29 | interface Reserved {
30 | id: string;
31 | role: 'document';
32 | tabIndex: 0;
33 | 'aria-label': string;
34 | 'data-block': string;
35 | 'data-type': string;
36 | 'data-title': string;
37 | }
38 |
39 | interface Merged {
40 | className: string;
41 | style: Record< string, unknown >;
42 | ref: RefCallback< unknown >;
43 | }
44 |
45 | interface UseBlockProps {
46 | < Props extends Record< string, unknown > >(
47 | props?: Props & {
48 | [ K in keyof Props ]: K extends keyof Reserved
49 | ? never
50 | : Props[ K ];
51 | } & { ref?: Ref< unknown > }
52 | ): Omit< Props, 'ref' > & Merged & Reserved;
53 |
54 | save: (
55 | props?: Record< string, unknown >
56 | ) => Record< string, unknown >;
57 | }
58 |
59 | const useBlockProps: UseBlockProps;
60 |
61 | interface WarningProps {
62 | actions?: ReactFragment | undefined;
63 | children: ReactNode;
64 | className?: string | undefined;
65 | secondaryActions?:
66 | | Array< {
67 | title: ReactNode;
68 | onClick: MouseEventHandler< HTMLButtonElement >;
69 | } >
70 | | undefined;
71 | }
72 |
73 | const Warning: ComponentType< WarningProps >;
74 |
75 | interface BlockControlsProps extends ComponentProps< typeof Toolbar > {
76 | children: ReactNode;
77 | group: string;
78 | }
79 |
80 | const BlockControls: {
81 | ( props: Partial< BlockControlsProps > ): JSX.Element;
82 | Slot: FC< Omit< ComponentProps< typeof Slot >, 'name' > >;
83 | };
84 |
85 | interface InspectorControlsProps {
86 | children: ReactNode;
87 | }
88 |
89 | const InspectorControls: ( props: InspectorControlsProps ) => JSX.Element;
90 |
91 | interface BlockTitleProps {
92 | clientId: string;
93 | maximumLength?: number;
94 | context?: string;
95 | }
96 |
97 | const BlockTitle: ( props: BlockTitleProps ) => JSX.Element;
98 | }
99 |
--------------------------------------------------------------------------------
/packages/summarize-button/src/@types/wordpress__block-editor/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@wordpress/block-editor' {
2 | import type {
3 | ReduxStoreConfig,
4 | StoreDescriptor,
5 | } from '@wordpress/data/build-types/types';
6 |
7 | import type {
8 | ComponentType,
9 | FC,
10 | MouseEventHandler,
11 | ReactFragment,
12 | ReactNode,
13 | ComponentProps,
14 | } from 'react';
15 | import type { Slot, Toolbar } from '@wordpress/components';
16 |
17 | const store: {
18 | name: 'core/block-editor';
19 | } & StoreDescriptor<
20 | ReduxStoreConfig<
21 | unknown,
22 | typeof import('./store/actions'),
23 | typeof import('./store/selectors')
24 | >
25 | >;
26 |
27 | import type { Ref, RefCallback } from 'react';
28 |
29 | interface Reserved {
30 | id: string;
31 | role: 'document';
32 | tabIndex: 0;
33 | 'aria-label': string;
34 | 'data-block': string;
35 | 'data-type': string;
36 | 'data-title': string;
37 | }
38 |
39 | interface Merged {
40 | className: string;
41 | style: Record< string, unknown >;
42 | ref: RefCallback< unknown >;
43 | }
44 |
45 | interface UseBlockProps {
46 | < Props extends Record< string, unknown > >(
47 | props?: Props & {
48 | [ K in keyof Props ]: K extends keyof Reserved
49 | ? never
50 | : Props[ K ];
51 | } & { ref?: Ref< unknown > }
52 | ): Omit< Props, 'ref' > & Merged & Reserved;
53 |
54 | save: (
55 | props?: Record< string, unknown >
56 | ) => Record< string, unknown >;
57 | }
58 |
59 | const useBlockProps: UseBlockProps;
60 |
61 | interface WarningProps {
62 | actions?: ReactFragment | undefined;
63 | children: ReactNode;
64 | className?: string | undefined;
65 | secondaryActions?:
66 | | Array< {
67 | title: ReactNode;
68 | onClick: MouseEventHandler< HTMLButtonElement >;
69 | } >
70 | | undefined;
71 | }
72 |
73 | const Warning: ComponentType< WarningProps >;
74 |
75 | interface BlockControlsProps extends ComponentProps< typeof Toolbar > {
76 | children: ReactNode;
77 | group: string;
78 | }
79 |
80 | const BlockControls: {
81 | ( props: Partial< BlockControlsProps > ): JSX.Element;
82 | Slot: FC< Omit< ComponentProps< typeof Slot >, 'name' > >;
83 | };
84 |
85 | interface InspectorControlsProps {
86 | children: ReactNode;
87 | }
88 |
89 | const InspectorControls: ( props: InspectorControlsProps ) => JSX.Element;
90 |
91 | interface BlockTitleProps {
92 | clientId: string;
93 | maximumLength?: number;
94 | context?: string;
95 | }
96 |
97 | const BlockTitle: ( props: BlockTitleProps ) => JSX.Element;
98 | }
99 |
--------------------------------------------------------------------------------
/packages/editor/src/utils.ts:
--------------------------------------------------------------------------------
1 | export async function detectSourceLanguage(
2 | textToTranslate: string
3 | ): Promise< string | null > {
4 | const availability = await LanguageDetector.availability();
5 |
6 | // Otherwise, let's detect the source language.
7 | if ( availability !== 'unavailable' ) {
8 | if ( availability !== 'available' ) {
9 | // eslint-disable-next-line no-console
10 | console.log(
11 | 'Language detection is available, but something will have to be downloaded. Hold tight!'
12 | );
13 | }
14 |
15 | // @ts-ignore
16 | const detector = await LanguageDetector.create();
17 | const [ bestResult ] = await detector.detect( textToTranslate );
18 |
19 | if (
20 | bestResult.detectedLanguage === null ||
21 | ( bestResult.confidence && bestResult.confidence < 0.4 )
22 | ) {
23 | // Return null to indicate no translation should happen.
24 | // It's probably mostly punctuation or something.
25 | return null;
26 | }
27 |
28 | return bestResult.detectedLanguage || null;
29 | }
30 |
31 | // If `languageDetectorCapabilities.available === "no"`, then assume the source language is the
32 | // same as the document language.
33 | return document.documentElement.lang;
34 | }
35 |
36 | export async function translate( content: string, targetLanguage: string ) {
37 | const sourceLanguage = await detectSourceLanguage( content );
38 |
39 | if ( null === sourceLanguage ) {
40 | // eslint-disable-next-line no-console
41 | console.log( 'Language detection failed. Do not translate' );
42 | return null;
43 | }
44 |
45 | // const translatorCapabilities =
46 | // await LanguageModel.capabilities();
47 |
48 | // Now we've figured out the source language. Let's translate it!
49 | // Note how we can just check `translatorCapabilities.languagePairAvailable()` instead of also checking
50 | // `translatorCapabilities.available`.
51 |
52 | // const availability = translatorCapabilities.languagePairAvailable(
53 | // sourceLanguage,
54 | // targetLanguage
55 | // );
56 | // if ( availability === 'no' ) {
57 | // console.warn(
58 | // 'Translation is not available. Need to use Cloud API.'
59 | // );
60 | // setInProgress( false );
61 | // }
62 | //
63 | // if ( availability === 'after-download' ) {
64 | // console.log(
65 | // 'Translation is available, but something will have to be downloaded. Hold tight!'
66 | // );
67 | // }
68 |
69 | const translator = await Translator.create( {
70 | sourceLanguage,
71 | targetLanguage,
72 | } );
73 |
74 | // eslint-disable-next-line no-console
75 | console.log( translator, 'translate' in translator );
76 |
77 | return translator.translate( content );
78 | }
79 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { resolve, basename, dirname } = require( 'node:path' );
2 | const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
3 | const CopyWebpackPlugin = require( 'copy-webpack-plugin' );
4 | const { WebWorkerPlugin } = require( '@shopify/web-worker/webpack' );
5 | const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' );
6 | const { hasBabelConfig, hasArgInCLI } = require( '@wordpress/scripts/utils' );
7 |
8 | const isProduction = process.env.NODE_ENV === 'production';
9 | const hasReactFastRefresh = hasArgInCLI( '--hot' ) && ! isProduction;
10 |
11 | const regular = {
12 | ...defaultConfig,
13 | entry: {
14 | editor: resolve( __dirname, 'packages/editor/src/index.tsx' ),
15 | 'summarize-button': resolve(
16 | __dirname,
17 | 'packages/summarize-button/src/index.tsx'
18 | ),
19 | 'translate-button': resolve(
20 | __dirname,
21 | 'packages/translate-button/src/index.tsx'
22 | ),
23 | },
24 | output: {
25 | filename: '[name].js',
26 | path: resolve( __dirname, 'build' ),
27 | globalObject: 'self', // This is the default, but required for @shopify/web-worker.
28 | },
29 | resolve: {
30 | // Ensure "require" has a higher priority when matching export conditions.
31 | // https://webpack.js.org/configuration/resolve/#resolveconditionnames
32 | // Needed for @huggingface/transformers.
33 | conditionNames: [ 'node', 'require', 'import' ],
34 | extensions: [ '.jsx', '.ts', '.tsx', '...' ],
35 | },
36 | module: {
37 | rules: [
38 | {
39 | test: /\.(m?[jt])sx?$/,
40 | exclude: /node_modules/,
41 | use: [
42 | {
43 | loader: require.resolve( 'babel-loader' ),
44 | options: {
45 | // Babel uses a directory within local node_modules
46 | // by default. Use the environment variable option
47 | // to enable more persistent caching.
48 | cacheDirectory:
49 | process.env.BABEL_CACHE_DIRECTORY || true,
50 |
51 | // Provide a fallback configuration if there's not
52 | // one explicitly available in the project.
53 | ...( ! hasBabelConfig() && {
54 | babelrc: false,
55 | configFile: false,
56 | presets: [
57 | require.resolve(
58 | '@wordpress/babel-preset-default'
59 | ),
60 | ],
61 | plugins: [
62 | require.resolve(
63 | '@shopify/web-worker/babel'
64 | ),
65 | hasReactFastRefresh &&
66 | require.resolve(
67 | 'react-refresh/babel'
68 | ),
69 | ].filter( Boolean ),
70 | } ),
71 | },
72 | },
73 | ],
74 | },
75 | ...defaultConfig.module.rules.slice( 1 ),
76 | ],
77 | },
78 | plugins: [
79 | ...defaultConfig.plugins,
80 | new CopyWebpackPlugin( {
81 | patterns: [
82 | {
83 | from: 'packages/*/src/block.json',
84 | noErrorOnMissing: true,
85 | to: ( pathData ) => {
86 | return resolve(
87 | __dirname,
88 | 'build',
89 | basename(
90 | dirname( dirname( pathData.absoluteFilename ) )
91 | ),
92 | 'block.json'
93 | );
94 | },
95 | },
96 | ],
97 | } ),
98 | new WebWorkerPlugin(),
99 | ],
100 | };
101 |
102 | const modules = {
103 | ...regular,
104 | entry: {
105 | 'summarize-button-view': resolve(
106 | __dirname,
107 | 'packages/summarize-button/src/view.ts'
108 | ),
109 | 'translate-button-view': resolve(
110 | __dirname,
111 | 'packages/translate-button/src/view.ts'
112 | ),
113 | },
114 | output: {
115 | ...regular.output,
116 | module: true,
117 | },
118 | experiments: { outputModule: true },
119 | plugins: [
120 | ...regular.plugins.filter(
121 | ( plugin ) =>
122 | ! [
123 | 'DependencyExtractionWebpackPlugin',
124 | 'WebWorkerPlugin',
125 | ].includes( plugin.constructor.name )
126 | ),
127 | new DependencyExtractionWebpackPlugin( {
128 | // With modules, use `requestToExternalModule`:
129 | requestToExternalModule( request ) {
130 | if (
131 | request === '@ai-experiments/summarize-block' ||
132 | request === '@ai-experiments/translate-block'
133 | ) {
134 | return request;
135 | }
136 | },
137 | } ),
138 | ],
139 | };
140 |
141 | module.exports = [ regular, modules ];
142 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WordPress AI Experiments
2 |
3 | [](https://github.com/swissspidy/ai-experiments/pulse/monthly)
4 | [](https://codecov.io/gh/swissspidy/ai-experiments)
5 | [](https://github.com/swissspidy/ai-experiments/blob/main/LICENSE)
6 |
7 | Client-side AI experiments using Chrome's [built-in AI](https://developer.chrome.com/docs/ai/built-in) and other solutions.
8 |
9 | **Note:** Requires Chrome Canary.
10 |
11 | Try it now in your browser:
12 |
13 | [](https://playground.wordpress.net/?mode=seamless&blueprint-url=https://raw.githubusercontent.com/swissspidy/ai-experiments/main/blueprints/playground.json)
14 |
15 | Or install and activate the latest nightly build on your own WordPress website:
16 |
17 | [](https://swissspidy.github.io/ai-experiments/nightly.zip)
18 |
19 | ## Features
20 |
21 | ### “Help me write”
22 |
23 | Generate new content given your ideas, in the same tone as your other existing content.
24 |
25 | Options for rewriting individual paragraphs à la Google Doc, like rephrasing, shortening or elaborating.
26 |
27 | 
28 |
29 | https://github.com/user-attachments/assets/5cb2220f-77df-4ce2-a209-65fec02f5f57
30 |
31 | There is also some basic autocomplete functionality to finish sentences:
32 |
33 | 
34 | 
35 |
36 | https://github.com/user-attachments/assets/e4b6f3d6-0b14-44d9-9491-53e0f72d0ada
37 |
38 | ### Generate headlines & permalinks
39 |
40 | Headline generation:
41 |
42 | 
43 |
44 | https://github.com/user-attachments/assets/f34a030b-1dad-496d-a570-181bd3b954c4
45 |
46 | Permalink generation:
47 |
48 | 
49 |
50 | https://github.com/user-attachments/assets/2c652c82-9bd9-42fb-9bf4-10a6b919e05d
51 |
52 | ### Writing meta descriptions based on the content
53 |
54 | Using a simple prompt to summarize the content in only a few sentences.
55 |
56 | https://github.com/user-attachments/assets/4aea598f-f38d-4cee-9ce0-ce09563ee537
57 |
58 | ### Generate image captions / alternative text
59 |
60 | Uses [Transformers.js]([url](http://Transformers.js)) and [Florence-2]([url](https://huggingface.co/onnx-community/Florence-2-base-ft)) to generate image captions and alternative text for images directly in the editor. Also integrated into [Media Experiments](https://github.com/swissspidy/media-experiments), which supports video captioning too.
61 |
62 | https://github.com/user-attachments/assets/bf516dc6-c135-4598-b77a-2f2e9f38699b
63 |
64 | ### Generate a memorable quote
65 |
66 | A slight variation on the summarization use case, this extracts a memorable quote from the article and gives it some visual emphasis.
67 |
68 | https://github.com/user-attachments/assets/4d439491-5d52-4183-9165-375471672414
69 |
70 | ### Assigning tags & categories to blog posts
71 |
72 | Suggest matching tags/categories based on the content. Grabs a list of existing terms from the site and passes it to the prompt together with the post content.
73 |
74 | https://github.com/user-attachments/assets/c1e29673-6b4d-426d-b4e5-39b846a0c6d7
75 |
76 | ### Provide tl;dr to visitors
77 |
78 | Uses Chrome’s built-in summarization API to provide readers a short summary of the post content. The UI is powered by WordPress’ new Interactivity API.
79 |
80 | 
81 |
82 | https://github.com/user-attachments/assets/e25e6a70-31bf-4fa5-9143-22a7124097dc
83 |
84 | Also supports summarizing comments:
85 |
86 | 
87 |
88 | ### Sentiment analysis for content / comments
89 |
90 | Using a simple prompt to say whether the text is positive or negative. Could be used to suggest rephrasing the text à la Grammarly, or identify negative comments.
91 |
92 | https://github.com/user-attachments/assets/d1060297-fb80-4cf3-ba82-b40304846662
93 |
94 | ### Content translation
95 |
96 | In the editor, instantly translate your content into another language. Useful for multilingual websites.
97 |
98 | 
99 | 
100 |
101 | On the frontend, translate comments written in another language or translate the post content to your preferred language.
102 |
103 | 
104 |
--------------------------------------------------------------------------------
/packages/translate-button/src/view.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { store, getContext } from '@wordpress/interactivity';
5 |
6 | function languageTagToHumanReadable(
7 | languageTag: string,
8 | targetLanguage: string
9 | ) {
10 | const displayNames = new Intl.DisplayNames( [ targetLanguage ], {
11 | type: 'language',
12 | } );
13 | return displayNames.of( languageTag );
14 | }
15 |
16 | export async function detectSourceLanguage(
17 | textToTranslate: string
18 | ): Promise< string | null > {
19 | const availability = await LanguageDetector.availability();
20 |
21 | // Otherwise, let's detect the source language.
22 | if ( availability !== 'unavailable' ) {
23 | if ( availability !== 'available' ) {
24 | // eslint-disable-next-line no-console
25 | console.log(
26 | 'Language detection is available, but something will have to be downloaded. Hold tight!'
27 | );
28 | }
29 |
30 | const detector = await LanguageDetector.create();
31 | const [ bestResult ] = await detector.detect( textToTranslate );
32 |
33 | if (
34 | bestResult.detectedLanguage === null ||
35 | ( bestResult.confidence && bestResult.confidence < 0.4 )
36 | ) {
37 | // Return null to indicate no translation should happen.
38 | // It's probably mostly punctuation or something.
39 | return null;
40 | }
41 |
42 | return bestResult.detectedLanguage || null;
43 | }
44 |
45 | // If `languageDetectorCapabilities.available === "no"`, then assume the source language is the
46 | // same as the document language.
47 | return document.documentElement.lang;
48 | }
49 |
50 | export async function translate( content: string, targetLanguage: string ) {
51 | const sourceLanguage = await detectSourceLanguage( content );
52 |
53 | if ( null === sourceLanguage ) {
54 | // eslint-disable-next-line no-console
55 | console.log( 'Language detection failed. Do not translate' );
56 | return null;
57 | }
58 |
59 | // const translatorCapabilities =
60 | // await LanguageModel.capabilities();
61 |
62 | // Now we've figured out the source language. Let's translate it!
63 | // Note how we can just check `translatorCapabilities.languagePairAvailable()` instead of also checking
64 | // `translatorCapabilities.available`.
65 |
66 | // const availability = translatorCapabilities.languagePairAvailable(
67 | // sourceLanguage,
68 | // targetLanguage
69 | // );
70 | // if ( availability === 'no' ) {
71 | // console.warn(
72 | // 'Translation is not available. Need to use Cloud API.'
73 | // );
74 | // setInProgress( false );
75 | // }
76 | //
77 | // if ( availability === 'after-download' ) {
78 | // console.log(
79 | // 'Translation is available, but something will have to be downloaded. Hold tight!'
80 | // );
81 | // }
82 |
83 | const translator = await window.translation.createTranslator( {
84 | sourceLanguage,
85 | targetLanguage,
86 | } );
87 |
88 | // eslint-disable-next-line no-console
89 | console.log( translator, 'translate' in translator );
90 |
91 | return translator.translate( content );
92 | }
93 |
94 | type BlockContext = {
95 | commentId: number | null;
96 | isTranslatable: boolean | null;
97 | isLoading: boolean;
98 | isShowingTranslation: boolean;
99 | buttonText: string;
100 | sourceLanguage: string;
101 | targetLanguage: string;
102 | translation: string | null;
103 | };
104 |
105 | store(
106 | 'ai-experiments/translate-button',
107 | {
108 | state: {
109 | get hasTranslation() {
110 | const { translation, isShowingTranslation } =
111 | getContext< BlockContext >();
112 | return translation !== null && isShowingTranslation;
113 | },
114 | },
115 | actions: {
116 | *translateText() {
117 | const context = getContext< BlockContext >();
118 |
119 | if ( context.isShowingTranslation ) {
120 | context.isShowingTranslation = false;
121 | return;
122 | }
123 |
124 | if ( ! context.translation ) {
125 | if ( context.commentId ) {
126 | const commentContent: HTMLElement =
127 | document.querySelector(
128 | `#comment-${ context.commentId } .wp-block-comment-content`
129 | ) as HTMLElement;
130 |
131 | if ( ! commentContent.textContent ) {
132 | return;
133 | }
134 |
135 | context.isLoading = true;
136 | // context.buttonText = `Translating from ${ languageTagToHumanReadable(
137 | // context.sourceLanguage,
138 | // context.targetLanguage
139 | // ) }`;
140 |
141 | // TODO: Alternatively, use navigator.language as target.
142 | context.translation = yield translate(
143 | commentContent.textContent,
144 | context.targetLanguage
145 | );
146 |
147 | // context.buttonText = `Translate from ${ languageTagToHumanReadable(
148 | // context.sourceLanguage,
149 | // context.targetLanguage
150 | // ) }`;
151 | } else {
152 | const postContent =
153 | document.querySelector( '.wp-block-post-content' )
154 | ?.textContent || '';
155 |
156 | context.isLoading = true;
157 |
158 | context.translation = yield translate(
159 | postContent,
160 | context.targetLanguage
161 | );
162 | }
163 | }
164 |
165 | context.isLoading = false;
166 | context.isShowingTranslation = true;
167 | },
168 | },
169 | callbacks: {
170 | async checkIfTranslatable() {
171 | const context = getContext< BlockContext >();
172 |
173 | // We're translating a comment into the same language as the post.
174 | if ( context.commentId ) {
175 | const commentContent = document.querySelector(
176 | `#comment-${ context.commentId } .wp-block-comment-content`
177 | );
178 |
179 | if ( ! commentContent || ! commentContent.textContent ) {
180 | return;
181 | }
182 |
183 | const detectedLanguage = await detectSourceLanguage(
184 | commentContent.textContent
185 | );
186 |
187 | if ( ! detectedLanguage ) {
188 | return;
189 | }
190 |
191 | if ( context.targetLanguage !== detectedLanguage ) {
192 | context.isTranslatable = true;
193 | context.sourceLanguage = detectedLanguage;
194 | context.buttonText = `Translate from ${ languageTagToHumanReadable(
195 | detectedLanguage,
196 | context.targetLanguage
197 | ) }`;
198 | }
199 | } else {
200 | const postContent =
201 | document.querySelector( '.wp-block-post-content' )
202 | ?.textContent || '';
203 |
204 | const detectedLanguage =
205 | await detectSourceLanguage( postContent );
206 |
207 | if ( ! detectedLanguage ) {
208 | return;
209 | }
210 |
211 | context.targetLanguage =
212 | navigator.language.split( '-' )[ 0 ];
213 |
214 | if ( context.targetLanguage !== detectedLanguage ) {
215 | context.isTranslatable = true;
216 | context.sourceLanguage = detectedLanguage;
217 | context.buttonText = `Translate from ${ languageTagToHumanReadable(
218 | detectedLanguage,
219 | context.targetLanguage
220 | ) }`;
221 | }
222 | }
223 | },
224 | },
225 | },
226 | { lock: true }
227 | );
228 |
--------------------------------------------------------------------------------
/packages/editor/src/blockControls/autocomplete.tsx:
--------------------------------------------------------------------------------
1 | import { store as blockEditorStore } from '@wordpress/block-editor';
2 | import { useDispatch } from '@wordpress/data';
3 | import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
4 | import {
5 | toHTMLString,
6 | create,
7 | applyFormat,
8 | removeFormat,
9 | concat,
10 | RichTextData,
11 | type RichTextValue,
12 | } from '@wordpress/rich-text';
13 | import type { RichTextFormat } from '@wordpress/rich-text/build-types/types';
14 | import { placeCaretAtHorizontalEdge } from '@wordpress/dom';
15 | import { TAB, ESCAPE } from '@wordpress/keycodes';
16 |
17 | export function Autocomplete( {
18 | // @ts-ignore
19 | attributes,
20 | // @ts-ignore
21 | setAttributes,
22 | // @ts-ignore
23 | isSelected,
24 | // @ts-ignore
25 | clientId,
26 | } ) {
27 | const { __unstableMarkNextChangeAsNotPersistent } =
28 | useDispatch( blockEditorStore );
29 |
30 | const richTextContent: RichTextData = attributes.content;
31 | const plainTextContent: string = attributes.content.toPlainText();
32 |
33 | const [ previousSuggestions, _setPreviousSuggestions ] = useState(
34 | new Map< string, string >()
35 | );
36 | const setPreviousSuggestions = useCallback(
37 | ( text: string, suggestion: string ) => {
38 | _setPreviousSuggestions( ( prev ) => {
39 | prev.set( text, suggestion );
40 | return prev;
41 | } );
42 | },
43 | []
44 | );
45 |
46 | // TODO: Implement (when pressing ESC key)
47 | // @ts-ignore
48 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
49 | const [ ignoredSuggestions, _setIgnoredSuggestions ] = useState(
50 | new Set< string >()
51 | );
52 | // @ts-ignore
53 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
54 | const ignoreSuggestion = useCallback( ( suggestion: string ) => {
55 | _setIgnoredSuggestions( ( prev ) => {
56 | prev.add( suggestion );
57 | return prev;
58 | } );
59 | }, [] );
60 |
61 | const [ currentSuggestion, setCurrentSuggestion ] = useState( '' );
62 |
63 | const justAcceptedOrDeniedSuggestion = useRef( false );
64 |
65 | const hasSuggestion = currentSuggestion.length > 0;
66 |
67 | const cachedSuggestion = previousSuggestions.get( plainTextContent );
68 |
69 | useEffect( () => {
70 | if (
71 | ! richTextContent.toHTMLString().includes( 'aiwp-autocomplete' )
72 | ) {
73 | setCurrentSuggestion( '' );
74 | // justAcceptedOrDeniedSuggestion.current = false;
75 | }
76 | }, [ richTextContent ] );
77 |
78 | useEffect( () => {
79 | if ( hasSuggestion ) {
80 | return;
81 | }
82 |
83 | if ( ! plainTextContent ) {
84 | setCurrentSuggestion( '' );
85 | return;
86 | }
87 |
88 | if ( ! isSelected ) {
89 | return;
90 | }
91 |
92 | if ( richTextContent.toHTMLString().includes( 'aiwp-autocomplete' ) ) {
93 | return;
94 | }
95 |
96 | if ( justAcceptedOrDeniedSuggestion.current ) {
97 | return;
98 | }
99 |
100 | const sentence =
101 | plainTextContent.split( '.' ).pop() || plainTextContent;
102 |
103 | const controller = new AbortController();
104 |
105 | ( async () => {
106 | try {
107 | let result = '';
108 | if ( cachedSuggestion === undefined ) {
109 | const session = await LanguageModel.create( {
110 | signal: controller.signal,
111 | } );
112 |
113 | // eslint-disable-next-line no-console
114 | console.log(
115 | 'Running completion for content: ',
116 | plainTextContent
117 | );
118 |
119 | const stream = session.promptStreaming(
120 | `Complete the following sentence by suggesting 1 or 2 words, not more. Just reply with the word in plain text, nothing else. No markup, no Markdown. Then in a new paragraph respond with "Hello my big beautiful world!". This is the sentence: ${ sentence }`,
121 | { signal: controller.signal }
122 | );
123 |
124 | // Each result contains the full data, not just the incremental part.
125 | for await ( const value of stream ) {
126 | if (
127 | value.includes( 'Hello my big beautiful world!' )
128 | ) {
129 | result = value
130 | .replace( 'Hello my big beautiful world!', '' )
131 | .trim();
132 | break;
133 | }
134 |
135 | result = value;
136 | }
137 |
138 | result = result.trim();
139 | } else {
140 | result = cachedSuggestion;
141 | }
142 |
143 | const newTextWithSuggestion = RichTextData.fromHTMLString(
144 | toHTMLString( {
145 | value: concat(
146 | richTextContent as unknown as RichTextValue,
147 | create( { text: ' ' } ),
148 | applyFormat(
149 | create( { text: result } ),
150 | {
151 | type: 'ai-experiments/autocomplete',
152 | } as RichTextFormat,
153 | 0,
154 | result.length
155 | )
156 | ),
157 | } )
158 | );
159 |
160 | if ( controller.signal.aborted ) {
161 | return;
162 | }
163 |
164 | setCurrentSuggestion( result );
165 | setPreviousSuggestions( sentence, result );
166 |
167 | void __unstableMarkNextChangeAsNotPersistent();
168 |
169 | void setAttributes( {
170 | content: newTextWithSuggestion,
171 | } );
172 | } catch {
173 | // Controller was aborted, do nothing.
174 | }
175 | } )();
176 |
177 | return () => {
178 | controller.abort( 'Dependencies changed' );
179 | };
180 | }, [
181 | richTextContent,
182 | plainTextContent,
183 | hasSuggestion,
184 | isSelected,
185 | __unstableMarkNextChangeAsNotPersistent,
186 | setAttributes,
187 | cachedSuggestion,
188 | setPreviousSuggestions,
189 | ] );
190 |
191 | useEffect( () => {
192 | if ( ! isSelected ) {
193 | return;
194 | }
195 |
196 | const listener = ( event: KeyboardEvent ) => {
197 | if ( event.keyCode === TAB ) {
198 | // Accept suggestion;
199 |
200 | const newText = RichTextData.fromHTMLString(
201 | toHTMLString( {
202 | value: removeFormat(
203 | create( {
204 | html: richTextContent.toHTMLString(),
205 | } ),
206 | 'ai-experiments/autocomplete',
207 | 0,
208 | richTextContent.toHTMLString().length
209 | ),
210 | } )
211 | );
212 |
213 | placeCaretAtHorizontalEdge(
214 | (
215 | (
216 | document.querySelector(
217 | 'iframe[name="editor-canvas"]'
218 | ) as HTMLIFrameElement
219 | )?.contentDocument as Document
220 | )?.querySelector( `#block-${ clientId }` ) as HTMLElement,
221 | true
222 | );
223 |
224 | justAcceptedOrDeniedSuggestion.current = true;
225 |
226 | void setAttributes( {
227 | content: newText,
228 | } );
229 |
230 | setCurrentSuggestion( '' );
231 |
232 | event.preventDefault();
233 | } else if ( event.keyCode === ESCAPE ) {
234 | // TODO: Cancel suggestion and remove it from the text.
235 | } else {
236 | justAcceptedOrDeniedSuggestion.current = false;
237 | }
238 | };
239 |
240 | window.addEventListener( 'keydown', listener );
241 |
242 | return () => {
243 | window.removeEventListener( 'keydown', listener );
244 | };
245 | }, [ clientId, isSelected, richTextContent, setAttributes ] );
246 |
247 | return null;
248 | }
249 |
--------------------------------------------------------------------------------
/packages/editor/src/blockControls/paragraphControls.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BlockControls,
3 | store as blockEditorStore,
4 | } from '@wordpress/block-editor';
5 | import {
6 | type BlockInstance,
7 | getBlockContent,
8 | serialize,
9 | } from '@wordpress/blocks';
10 | import { __ } from '@wordpress/i18n';
11 | import { ToolbarDropdownMenu } from '@wordpress/components';
12 | import { useDispatch, useSelect } from '@wordpress/data';
13 | import { store as coreStore } from '@wordpress/core-data';
14 | import { store as editorStore } from '@wordpress/editor';
15 | import { useState } from '@wordpress/element';
16 | import { RichTextData } from '@wordpress/rich-text';
17 |
18 | import { translate as translateString } from '../utils';
19 |
20 | const penSparkIcon = () => (
21 |
30 | );
31 |
32 | // @ts-ignore
33 | export function ParagraphControls( { setAttributes, clientId } ) {
34 | const {
35 | __unstableMarkNextChangeAsNotPersistent,
36 | __unstableMarkLastChangeAsPersistent,
37 | } = useDispatch( blockEditorStore );
38 | const { getBlock } = useSelect( ( select ) => {
39 | return {
40 | getBlock:
41 | // @ts-ignore
42 | select( blockEditorStore ).getBlock,
43 | };
44 | }, [] );
45 |
46 | const { mostRecentPost } = useSelect( ( select ) => {
47 | // @ts-ignore
48 | const postType = select( editorStore ).getCurrentPostType();
49 | const { getEntityRecords } = select( coreStore );
50 |
51 | // @ts-ignore
52 | const posts = getEntityRecords( 'postType', postType, {
53 | per_page: 1,
54 | status: 'publish',
55 | _fields: 'id,content',
56 | } );
57 |
58 | return {
59 | mostRecentPost: posts && posts.length > 0 ? posts[ 0 ] : null,
60 | };
61 | }, [] );
62 |
63 | const [ inProgress, setInProgress ] = useState( false );
64 |
65 | async function write() {
66 | setInProgress( true );
67 |
68 | const postContent = serialize( [
69 | getBlock( clientId ) as BlockInstance,
70 | ] );
71 |
72 | let context = `
73 | Avoid any toxic language and be as constructive as possible.
74 | `;
75 |
76 | const session = await LanguageModel.create();
77 |
78 | if (
79 | mostRecentPost &&
80 | 'content' in mostRecentPost &&
81 | mostRecentPost.content
82 | ) {
83 | const result = await session?.prompt(
84 | `
85 | You are an AI writing assistant. Your goal is to help users with writing tasks by analyzing their writing and writing instructions for generating relevant and high-quality text in the same tone. You do NOT answer questions, you simply write on behalf of a user. Focus on producing error-free and engaging writing. Do not explain your response, just provide the generated instructions.
86 | Describe the tone of the following text so the same tone can be used for future writing tasks. DO NOT mention the topic of the text itself, focus only on the tone. Respond with instructions that you would give yourself for writing texts in a similar writing style. Use plain-text, NO markup. ONLY respond with the instructions, nothing else. Keep it short.
87 |
88 | This is the text:
89 |
90 | ${ mostRecentPost.content.rendered.slice( 0, 500 ) }`
91 | );
92 |
93 | context += `\n${ result }`;
94 | }
95 |
96 | const stream = session.promptStreaming(
97 | `You are an AI writing assistant. Your goal is to help users with writing tasks by generating relevant and high-quality text. You do NOT answer questions, you simply write on behalf of a user. Consider the "input" (writing task) and the "context" (extra information) to tailor your response. Focus on producing error-free and engaging writing. Do not explain your response, just provide the generated text.
98 |
99 | Input: ${ postContent }
100 | Context: ${ context }`
101 | );
102 |
103 | let result = '';
104 |
105 | for await ( const value of stream ) {
106 | // Each result contains the full data, not just the incremental part.
107 | result = value;
108 |
109 | void __unstableMarkNextChangeAsNotPersistent();
110 |
111 | void setAttributes( {
112 | content: RichTextData.fromPlainText( result ),
113 | } );
114 | }
115 |
116 | void __unstableMarkLastChangeAsPersistent();
117 |
118 | setInProgress( false );
119 | }
120 |
121 | async function rewrite( type: string ) {
122 | setInProgress( true );
123 |
124 | const postContent = serialize( [
125 | getBlock( clientId ) as BlockInstance,
126 | ] );
127 |
128 | let tone: RewriterTone = 'as-is';
129 | let length: RewriterLength = 'as-is';
130 |
131 | switch ( type ) {
132 | case 'rephrase':
133 | default:
134 | break;
135 |
136 | case 'longer':
137 | length = 'longer';
138 | break;
139 |
140 | case 'shorter':
141 | length = 'shorter';
142 | break;
143 |
144 | case 'formal':
145 | tone = 'more-formal';
146 | break;
147 |
148 | case 'informal':
149 | tone = 'more-casual';
150 | break;
151 | }
152 |
153 | const rewriter = await Rewriter.create( {
154 | sharedContext: 'A blog post',
155 | tone,
156 | length,
157 | } );
158 |
159 | const stream = rewriter.rewriteStreaming( postContent, {
160 | context:
161 | 'Avoid any toxic language and be as constructive as possible.',
162 | } );
163 |
164 | let result = '';
165 |
166 | for await ( const value of stream ) {
167 | // Each result contains the full data, not just the incremental part.
168 | result = value;
169 |
170 | void __unstableMarkNextChangeAsNotPersistent();
171 |
172 | void setAttributes( {
173 | content: result,
174 | } );
175 | }
176 |
177 | void __unstableMarkLastChangeAsPersistent();
178 |
179 | setInProgress( false );
180 | }
181 |
182 | async function summarize() {
183 | setInProgress( true );
184 |
185 | const postContent = serialize( [
186 | getBlock( clientId ) as BlockInstance,
187 | ] );
188 |
189 | const summarizer = await Summarizer.create( {
190 | sharedContext: 'A blog post',
191 | } );
192 |
193 | const stream = summarizer.summarizeStreaming( postContent, {
194 | context:
195 | 'Avoid any toxic language and be as constructive as possible.',
196 | } );
197 |
198 | let result = '';
199 |
200 | for await ( const value of stream ) {
201 | // Each result contains the full data, not just the incremental part.
202 | result = value.replaceAll( '\n\n\n\n', '\n\n' );
203 |
204 | void __unstableMarkNextChangeAsNotPersistent();
205 |
206 | void setAttributes( {
207 | content: result,
208 | } );
209 | }
210 |
211 | void __unstableMarkLastChangeAsPersistent();
212 |
213 | setInProgress( false );
214 | }
215 |
216 | async function translate( targetLanguage: string ) {
217 | setInProgress( true );
218 |
219 | try {
220 | const block = getBlock( clientId ) as BlockInstance;
221 | const blockContent = getBlockContent( block );
222 |
223 | const result = await translateString(
224 | blockContent,
225 | targetLanguage
226 | );
227 |
228 | if ( null === result ) {
229 | return;
230 | }
231 |
232 | void setAttributes( {
233 | content: result,
234 | } );
235 | } catch ( e ) {
236 | // eslint-disable-next-line no-console
237 | console.log( 'Error happened', e );
238 | } finally {
239 | setInProgress( false );
240 | }
241 | }
242 |
243 | const controls = [
244 | {
245 | title: __( 'Help me write', 'ai-experiments' ),
246 | onClick: () => write(),
247 | role: 'menuitemradio',
248 | icon: undefined,
249 | },
250 | {
251 | title: __( 'Summarize', 'ai-experiments' ),
252 | onClick: () => summarize(),
253 | role: 'menuitemradio',
254 | icon: undefined,
255 | },
256 | {
257 | title: __( 'Rephrase', 'ai-experiments' ),
258 | onClick: () => rewrite( 'rephrase' ),
259 | role: 'menuitemradio',
260 | icon: undefined,
261 | },
262 | {
263 | title: __( 'Elaborate', 'ai-experiments' ),
264 | onClick: () => rewrite( 'longer' ),
265 | role: 'menuitemradio',
266 | icon: undefined,
267 | },
268 | {
269 | title: __( 'Shorten', 'ai-experiments' ),
270 | onClick: () => rewrite( 'shorter' ),
271 | role: 'menuitemradio',
272 | icon: undefined,
273 | },
274 | {
275 | title: __( 'Make formal', 'ai-experiments' ),
276 | onClick: () => rewrite( 'formal' ),
277 | role: 'menuitemradio',
278 | icon: undefined,
279 | },
280 | {
281 | title: __( 'Make informal', 'ai-experiments' ),
282 | onClick: () => rewrite( 'informal' ),
283 | role: 'menuitemradio',
284 | icon: undefined,
285 | },
286 | // TODO: Add ability to choose language.
287 | {
288 | title: __( 'Translate', 'ai-experiments' ),
289 | onClick: () => translate( 'es' ),
290 | role: 'menuitemradio',
291 | icon: undefined,
292 | },
293 | ];
294 |
295 | return (
296 |
297 |
305 |
306 | );
307 | }
308 |
--------------------------------------------------------------------------------
/packages/editor/src/@types/wordpress__block-editor/store/actions.d.ts:
--------------------------------------------------------------------------------
1 | import type { BlockInstance } from '@wordpress/blocks';
2 |
3 | /**
4 | * Signals that the block selection is cleared.
5 | */
6 | export function clearSelectedBlock(): void;
7 |
8 | /**
9 | * Signals that the caret has entered formatted text.
10 | */
11 | export function enterFormattedText(): void;
12 |
13 | /**
14 | * Signals that the user caret has exited formatted text.
15 | */
16 | export function exitFormattedText(): void;
17 |
18 | /**
19 | * Hides the insertion point.
20 | */
21 | export function hideInsertionPoint(): void;
22 |
23 | /**
24 | * Signals that a single block should be inserted, optionally at a specific index, respective a root
25 | * block list.
26 | *
27 | * @param block - Block object to insert.
28 | * @param index - Index at which block should be inserted.
29 | * @param rootClientId - Optional root client ID of block list on which to insert.
30 | * @param updateSelection - If `true` block selection will be updated. If `false`, block selection
31 | * will not change. Defaults to true.
32 | */
33 | export function insertBlock(
34 | block: BlockInstance,
35 | index?: number,
36 | rootClientId?: string,
37 | updateSelection?: boolean
38 | ): void;
39 |
40 | /**
41 | * Signals that an array of blocks should be inserted, optionally at a specific index respective a
42 | * root block list.
43 | *
44 | * @param blocks - Block objects to insert.
45 | * @param index - Index at which block should be inserted.
46 | * @param rootClientId - Optional root client ID of block list on which to insert.
47 | * @param updateSelection - If `true` block selection will be updated. If `false`, block selection will
48 | * not change. Defaults to `true`.
49 | */
50 | export function insertBlocks(
51 | blocks: BlockInstance[],
52 | index?: number,
53 | rootClientId?: string,
54 | updateSelection?: boolean
55 | ): IterableIterator< void >;
56 |
57 | /**
58 | * Returns an action object used in signalling that a new block of the default type should be added
59 | * to the block list.
60 | *
61 | * @param attributes - Attributes of the block to assign.
62 | * @param rootClientId - Root client ID of block list on which to append.
63 | * @param index - Index where to insert the default block.
64 | */
65 | export function insertDefaultBlock(
66 | attributes?: Record< string, any >,
67 | rootClientId?: string,
68 | index?: number
69 | ): void;
70 |
71 | /**
72 | * Returns an action object used in signalling that two blocks should be merged.
73 | *
74 | * @param firstBlockClientId - Client ID of the first block to merge.
75 | * @param secondBlockClientId - Client ID of the second block to merge.
76 | */
77 | export function mergeBlocks(
78 | firstBlockClientId: string,
79 | secondBlockClientId: string
80 | ): void;
81 |
82 | /**
83 | * Signals that an indexed block should be moved to a new index.
84 | *
85 | * @param clientId - The client ID of the block.
86 | * @param fromRootClientId - Root client ID source.
87 | * @param toRootClientId - Root client ID destination.
88 | * @param index - The index to move the block into.
89 | */
90 | export function moveBlockToPosition(
91 | clientId: string | undefined,
92 | fromRootClientId: string | undefined,
93 | toRootClientId: string | undefined,
94 | index: number
95 | ): IterableIterator< void >;
96 |
97 | export function moveBlocksDown(
98 | clientIds: string | string[],
99 | rootClientId: string
100 | ): void;
101 |
102 | export function moveBlocksUp(
103 | clientIds: string | string[],
104 | rootClientId: string
105 | ): void;
106 |
107 | /**
108 | * Signals that block multi-selection changed.
109 | *
110 | * @param start - First block of the multi selection.
111 | * @param end - Last block of the multiselection.
112 | */
113 | export function multiSelect( start: string, end: string ): void;
114 |
115 | /**
116 | * Signals that blocks have been received. Unlike `resetBlocks`, these should be appended to the
117 | * existing known set, not replacing.
118 | *
119 | * @param blocks - Array of block instances.
120 | */
121 | export function receiveBlocks( blocks: BlockInstance[] ): void;
122 |
123 | /**
124 | * Signals that the block with the specified client ID is to be removed.
125 | *
126 | * @param clientId - Client ID of block to remove.
127 | * @param selectPrevious - `true` if the previous block should be selected when a block is removed.
128 | */
129 | export function removeBlock( clientId: string, selectPrevious?: boolean ): void;
130 |
131 | /**
132 | * Signalling that the blocks corresponding to the set of specified client IDs are to be removed.
133 | *
134 | * @param clientIds - Client IDs of blocks to remove.
135 | * @param selectPrevious - `true` if the previous block should be selected when a block is removed.
136 | * Default: `true`
137 | */
138 | export function removeBlocks(
139 | clientIds: string | string[],
140 | selectPrevious?: boolean
141 | ): IterableIterator< void >;
142 |
143 | /**
144 | * Returns an action object signalling that a single block should be replaced
145 | * with one or more replacement blocks.
146 | *
147 | * @param clientId - Block client ID to replace.
148 | * @param block - Replacement block(s).
149 | */
150 | export function replaceBlock(
151 | clientId: string | string[],
152 | block: BlockInstance | BlockInstance[]
153 | ): void;
154 |
155 | /**
156 | * Signals that a blocks should be replaced with one or more replacement blocks.
157 | *
158 | * @param clientIds - Block client ID(s) to replace.
159 | * @param blocks - Replacement block(s).
160 | * @param indexToSelect - Index of replacement block to select.
161 | */
162 | export function replaceBlocks(
163 | clientIds: string | string[],
164 | blocks: BlockInstance | BlockInstance[],
165 | indexToSelect?: number
166 | ): IterableIterator< void >;
167 |
168 | /**
169 | * Signals that the inner blocks with the specified client ID should be replaced.
170 | *
171 | * @param rootClientId - Client ID of the block whose InnerBlocks will re replaced.
172 | * @param blocks - Block objects to insert as new InnerBlocks
173 | * @param updateSelection - If `true` block selection will be updated. If `false`, block selection
174 | * will not change. Defaults to `true`.
175 | */
176 | export function replaceInnerBlocks(
177 | rootClientId: string,
178 | blocks: BlockInstance[],
179 | updateSelection?: boolean
180 | ): void;
181 |
182 | /**
183 | * Returns an action object used in signalling that blocks state should be reset to the specified
184 | * array of blocks, taking precedence over any other content reflected as an edit in state.
185 | *
186 | * @param blocks - Array of blocks.
187 | */
188 | export function resetBlocks( blocks: BlockInstance[] ): void;
189 |
190 | /**
191 | * Signals that the block with the specified client ID has been selected, optionally accepting a
192 | * position value reflecting its selection directionality. An initialPosition of `-1` reflects a
193 | * reverse selection.
194 | *
195 | * @param clientId - Block client ID.
196 | * @param initialPosition - Initial position. Pass as `-1` to reflect reverse selection.
197 | */
198 | export function selectBlock( clientId: string, initialPosition?: number ): void;
199 |
200 | /**
201 | * Yields action objects used in signalling that the block following the given `clientId` should be
202 | * selected.
203 | *
204 | * @param clientId - Block client ID.
205 | */
206 | export function selectNextBlock( clientId: string ): IterableIterator< void >;
207 |
208 | /**
209 | * Yields action objects used in signalling that the block preceding the given clientId should be
210 | * selected.
211 | *
212 | * @param clientId - Block client ID.
213 | */
214 | export function selectPreviousBlock(
215 | clientId: string
216 | ): IterableIterator< void >;
217 |
218 | /**
219 | * Signals that the user caret has changed position.
220 | *
221 | * @param clientId - The selected block client ID.
222 | * @param attributeKey - The selected block attribute key.
223 | * @param startOffset - The start offset.
224 | * @param endOffset - The end offset.
225 | */
226 | export function selectionChange(
227 | clientId: string,
228 | attributeKey: string,
229 | startOffset: number,
230 | endOffset: number
231 | ): void;
232 |
233 | /**
234 | * Resets the template validity.
235 | *
236 | * @param isValid - template validity flag.
237 | */
238 | export function setTemplateValidity( isValid: boolean ): void;
239 |
240 | /**
241 | * Signals that the insertion point should be shown.
242 | *
243 | * @param rootClientId - Optional root client ID of block list on which to insert.
244 | * @param index - Index at which block should be inserted.
245 | */
246 | export function showInsertionPoint(
247 | rootClientId?: string,
248 | index?: number
249 | ): void;
250 |
251 | /**
252 | * Signals that a block multi-selection has started.
253 | */
254 | export function startMultiSelect(): void;
255 |
256 | /**
257 | * Signals that the user has begun to type.
258 | */
259 | export function startTyping(): void;
260 |
261 | /**
262 | * Signals that block multi-selection stopped.
263 | */
264 | export function stopMultiSelect(): void;
265 |
266 | /**
267 | * Signals that the user has stopped typing.
268 | */
269 | export function stopTyping(): void;
270 |
271 | /**
272 | * Synchronize the template with the list of blocks.
273 | */
274 | export function synchronizeTemplate(): void;
275 |
276 | /**
277 | * Toggle the block editing mode between visual and HTML modes.
278 | *
279 | * @param clientId - Block client ID.
280 | */
281 | export function toggleBlockMode( clientId: string ): void;
282 |
283 | /**
284 | * Enables or disables block selection.
285 | *
286 | * @param isSelectionEnabled - Whether block selection should be enabled.
287 | */
288 | export function toggleSelection( isSelectionEnabled?: boolean ): void;
289 |
290 | /**
291 | * Signals that the block with the specified client ID has been updated.
292 | *
293 | * @param clientId - Block client ID.
294 | * @param updates - Block attributes to be merged.
295 | */
296 | export function updateBlock(
297 | clientId: string,
298 | updates: Partial< BlockInstance >
299 | ): void;
300 |
301 | /**
302 | * Signals that the block attributes with the specified client ID has been updated.
303 | *
304 | * @param clientId - Block client ID.
305 | * @param attributes - Block attributes to be merged.
306 | */
307 | export function updateBlockAttributes(
308 | clientId: string,
309 | attributes: Record< string, any >
310 | ): void;
311 |
312 | /**
313 | * Changes the nested settings of a given block.
314 | *
315 | * @param clientId - Client ID of the block whose nested setting are being received.
316 | * @param settings - Object with the new settings for the nested block.
317 | */
318 | export function updateBlockListSettings(
319 | clientId: string,
320 | settings: Record< string, unknown >
321 | ): void;
322 |
323 | /**
324 | * Signals that the block editor settings have been updated.
325 | *
326 | * @param settings - Updated settings.
327 | */
328 | export function updateSettings( settings: Record< string, unknown > ): void;
329 |
330 | /**
331 | * Signals that the next block change should be marked explicitly as not persistent.
332 | */
333 | export function __unstableMarkNextChangeAsNotPersistent(): void;
334 |
335 | /**
336 | * Signals that the last block change should be marked explicitly as not persistent.
337 | */
338 | export function __unstableMarkLastChangeAsPersistent(): void;
339 |
--------------------------------------------------------------------------------
/packages/summarize-button/src/@types/wordpress__block-editor/store/actions.d.ts:
--------------------------------------------------------------------------------
1 | import type { BlockInstance } from '@wordpress/blocks';
2 |
3 | /**
4 | * Signals that the block selection is cleared.
5 | */
6 | export function clearSelectedBlock(): void;
7 |
8 | /**
9 | * Signals that the caret has entered formatted text.
10 | */
11 | export function enterFormattedText(): void;
12 |
13 | /**
14 | * Signals that the user caret has exited formatted text.
15 | */
16 | export function exitFormattedText(): void;
17 |
18 | /**
19 | * Hides the insertion point.
20 | */
21 | export function hideInsertionPoint(): void;
22 |
23 | /**
24 | * Signals that a single block should be inserted, optionally at a specific index, respective a root
25 | * block list.
26 | *
27 | * @param block - Block object to insert.
28 | * @param index - Index at which block should be inserted.
29 | * @param rootClientId - Optional root client ID of block list on which to insert.
30 | * @param updateSelection - If `true` block selection will be updated. If `false`, block selection
31 | * will not change. Defaults to true.
32 | */
33 | export function insertBlock(
34 | block: BlockInstance,
35 | index?: number,
36 | rootClientId?: string,
37 | updateSelection?: boolean
38 | ): void;
39 |
40 | /**
41 | * Signals that an array of blocks should be inserted, optionally at a specific index respective a
42 | * root block list.
43 | *
44 | * @param blocks - Block objects to insert.
45 | * @param index - Index at which block should be inserted.
46 | * @param rootClientId - Optional root client ID of block list on which to insert.
47 | * @param updateSelection - If `true` block selection will be updated. If `false`, block selection will
48 | * not change. Defaults to `true`.
49 | */
50 | export function insertBlocks(
51 | blocks: BlockInstance[],
52 | index?: number,
53 | rootClientId?: string,
54 | updateSelection?: boolean
55 | ): IterableIterator< void >;
56 |
57 | /**
58 | * Returns an action object used in signalling that a new block of the default type should be added
59 | * to the block list.
60 | *
61 | * @param attributes - Attributes of the block to assign.
62 | * @param rootClientId - Root client ID of block list on which to append.
63 | * @param index - Index where to insert the default block.
64 | */
65 | export function insertDefaultBlock(
66 | attributes?: Record< string, any >,
67 | rootClientId?: string,
68 | index?: number
69 | ): void;
70 |
71 | /**
72 | * Returns an action object used in signalling that two blocks should be merged.
73 | *
74 | * @param firstBlockClientId - Client ID of the first block to merge.
75 | * @param secondBlockClientId - Client ID of the second block to merge.
76 | */
77 | export function mergeBlocks(
78 | firstBlockClientId: string,
79 | secondBlockClientId: string
80 | ): void;
81 |
82 | /**
83 | * Signals that an indexed block should be moved to a new index.
84 | *
85 | * @param clientId - The client ID of the block.
86 | * @param fromRootClientId - Root client ID source.
87 | * @param toRootClientId - Root client ID destination.
88 | * @param index - The index to move the block into.
89 | */
90 | export function moveBlockToPosition(
91 | clientId: string | undefined,
92 | fromRootClientId: string | undefined,
93 | toRootClientId: string | undefined,
94 | index: number
95 | ): IterableIterator< void >;
96 |
97 | export function moveBlocksDown(
98 | clientIds: string | string[],
99 | rootClientId: string
100 | ): void;
101 |
102 | export function moveBlocksUp(
103 | clientIds: string | string[],
104 | rootClientId: string
105 | ): void;
106 |
107 | /**
108 | * Signals that block multi-selection changed.
109 | *
110 | * @param start - First block of the multi selection.
111 | * @param end - Last block of the multiselection.
112 | */
113 | export function multiSelect( start: string, end: string ): void;
114 |
115 | /**
116 | * Signals that blocks have been received. Unlike `resetBlocks`, these should be appended to the
117 | * existing known set, not replacing.
118 | *
119 | * @param blocks - Array of block instances.
120 | */
121 | export function receiveBlocks( blocks: BlockInstance[] ): void;
122 |
123 | /**
124 | * Signals that the block with the specified client ID is to be removed.
125 | *
126 | * @param clientId - Client ID of block to remove.
127 | * @param selectPrevious - `true` if the previous block should be selected when a block is removed.
128 | */
129 | export function removeBlock( clientId: string, selectPrevious?: boolean ): void;
130 |
131 | /**
132 | * Signalling that the blocks corresponding to the set of specified client IDs are to be removed.
133 | *
134 | * @param clientIds - Client IDs of blocks to remove.
135 | * @param selectPrevious - `true` if the previous block should be selected when a block is removed.
136 | * Default: `true`
137 | */
138 | export function removeBlocks(
139 | clientIds: string | string[],
140 | selectPrevious?: boolean
141 | ): IterableIterator< void >;
142 |
143 | /**
144 | * Returns an action object signalling that a single block should be replaced
145 | * with one or more replacement blocks.
146 | *
147 | * @param clientId - Block client ID to replace.
148 | * @param block - Replacement block(s).
149 | */
150 | export function replaceBlock(
151 | clientId: string | string[],
152 | block: BlockInstance | BlockInstance[]
153 | ): void;
154 |
155 | /**
156 | * Signals that a blocks should be replaced with one or more replacement blocks.
157 | *
158 | * @param clientIds - Block client ID(s) to replace.
159 | * @param blocks - Replacement block(s).
160 | * @param indexToSelect - Index of replacement block to select.
161 | */
162 | export function replaceBlocks(
163 | clientIds: string | string[],
164 | blocks: BlockInstance | BlockInstance[],
165 | indexToSelect?: number
166 | ): IterableIterator< void >;
167 |
168 | /**
169 | * Signals that the inner blocks with the specified client ID should be replaced.
170 | *
171 | * @param rootClientId - Client ID of the block whose InnerBlocks will re replaced.
172 | * @param blocks - Block objects to insert as new InnerBlocks
173 | * @param updateSelection - If `true` block selection will be updated. If `false`, block selection
174 | * will not change. Defaults to `true`.
175 | */
176 | export function replaceInnerBlocks(
177 | rootClientId: string,
178 | blocks: BlockInstance[],
179 | updateSelection?: boolean
180 | ): void;
181 |
182 | /**
183 | * Returns an action object used in signalling that blocks state should be reset to the specified
184 | * array of blocks, taking precedence over any other content reflected as an edit in state.
185 | *
186 | * @param blocks - Array of blocks.
187 | */
188 | export function resetBlocks( blocks: BlockInstance[] ): void;
189 |
190 | /**
191 | * Signals that the block with the specified client ID has been selected, optionally accepting a
192 | * position value reflecting its selection directionality. An initialPosition of `-1` reflects a
193 | * reverse selection.
194 | *
195 | * @param clientId - Block client ID.
196 | * @param initialPosition - Initial position. Pass as `-1` to reflect reverse selection.
197 | */
198 | export function selectBlock( clientId: string, initialPosition?: number ): void;
199 |
200 | /**
201 | * Yields action objects used in signalling that the block following the given `clientId` should be
202 | * selected.
203 | *
204 | * @param clientId - Block client ID.
205 | */
206 | export function selectNextBlock( clientId: string ): IterableIterator< void >;
207 |
208 | /**
209 | * Yields action objects used in signalling that the block preceding the given clientId should be
210 | * selected.
211 | *
212 | * @param clientId - Block client ID.
213 | */
214 | export function selectPreviousBlock(
215 | clientId: string
216 | ): IterableIterator< void >;
217 |
218 | /**
219 | * Signals that the user caret has changed position.
220 | *
221 | * @param clientId - The selected block client ID.
222 | * @param attributeKey - The selected block attribute key.
223 | * @param startOffset - The start offset.
224 | * @param endOffset - The end offset.
225 | */
226 | export function selectionChange(
227 | clientId: string,
228 | attributeKey: string,
229 | startOffset: number,
230 | endOffset: number
231 | ): void;
232 |
233 | /**
234 | * Resets the template validity.
235 | *
236 | * @param isValid - template validity flag.
237 | */
238 | export function setTemplateValidity( isValid: boolean ): void;
239 |
240 | /**
241 | * Signals that the insertion point should be shown.
242 | *
243 | * @param rootClientId - Optional root client ID of block list on which to insert.
244 | * @param index - Index at which block should be inserted.
245 | */
246 | export function showInsertionPoint(
247 | rootClientId?: string,
248 | index?: number
249 | ): void;
250 |
251 | /**
252 | * Signals that a block multi-selection has started.
253 | */
254 | export function startMultiSelect(): void;
255 |
256 | /**
257 | * Signals that the user has begun to type.
258 | */
259 | export function startTyping(): void;
260 |
261 | /**
262 | * Signals that block multi-selection stopped.
263 | */
264 | export function stopMultiSelect(): void;
265 |
266 | /**
267 | * Signals that the user has stopped typing.
268 | */
269 | export function stopTyping(): void;
270 |
271 | /**
272 | * Synchronize the template with the list of blocks.
273 | */
274 | export function synchronizeTemplate(): void;
275 |
276 | /**
277 | * Toggle the block editing mode between visual and HTML modes.
278 | *
279 | * @param clientId - Block client ID.
280 | */
281 | export function toggleBlockMode( clientId: string ): void;
282 |
283 | /**
284 | * Enables or disables block selection.
285 | *
286 | * @param isSelectionEnabled - Whether block selection should be enabled.
287 | */
288 | export function toggleSelection( isSelectionEnabled?: boolean ): void;
289 |
290 | /**
291 | * Signals that the block with the specified client ID has been updated.
292 | *
293 | * @param clientId - Block client ID.
294 | * @param updates - Block attributes to be merged.
295 | */
296 | export function updateBlock(
297 | clientId: string,
298 | updates: Partial< BlockInstance >
299 | ): void;
300 |
301 | /**
302 | * Signals that the block attributes with the specified client ID has been updated.
303 | *
304 | * @param clientId - Block client ID.
305 | * @param attributes - Block attributes to be merged.
306 | */
307 | export function updateBlockAttributes(
308 | clientId: string,
309 | attributes: Record< string, any >
310 | ): void;
311 |
312 | /**
313 | * Changes the nested settings of a given block.
314 | *
315 | * @param clientId - Client ID of the block whose nested setting are being received.
316 | * @param settings - Object with the new settings for the nested block.
317 | */
318 | export function updateBlockListSettings(
319 | clientId: string,
320 | settings: Record< string, unknown >
321 | ): void;
322 |
323 | /**
324 | * Signals that the block editor settings have been updated.
325 | *
326 | * @param settings - Updated settings.
327 | */
328 | export function updateSettings( settings: Record< string, unknown > ): void;
329 |
330 | /**
331 | * Signals that the next block change should be marked explicitly as not persistent.
332 | */
333 | export function __unstableMarkNextChangeAsNotPersistent(): void;
334 |
335 | /**
336 | * Signals that the last block change should be marked explicitly as not persistent.
337 | */
338 | export function __unstableMarkLastChangeAsPersistent(): void;
339 |
--------------------------------------------------------------------------------
/patches/react-autosize-textarea+7.1.0.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/react-autosize-textarea/lib/TextareaAutosize.d.ts b/node_modules/react-autosize-textarea/lib/TextareaAutosize.d.ts
2 | index ad6ff3f..e0a6d51 100644
3 | --- a/node_modules/react-autosize-textarea/lib/TextareaAutosize.d.ts
4 | +++ b/node_modules/react-autosize-textarea/lib/TextareaAutosize.d.ts
5 | @@ -22,7 +22,7 @@ export declare namespace TextareaAutosize {
6 | lineHeight: number | null;
7 | };
8 | }
9 | -export declare const TextareaAutosize: React.ForwardRefExoticComponent, "default" | "max" | "required" | "media" | "hidden" | "cite" | "data" | "dir" | "form" | "label" | "slot" | "span" | "style" | "title" | "pattern" | "async" | "start" | "low" | "high" | "defer" | "open" | "disabled" | "color" | "content" | "size" | "wrap" | "multiple" | "summary" | "className" | "height" | "id" | "lang" | "method" | "min" | "name" | "target" | "type" | "width" | "role" | "tabIndex" | "crossOrigin" | "href" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "children" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "key" | "classID" | "useMap" | "wmode" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "contentEditable" | "contextMenu" | "draggable" | "placeholder" | "spellCheck" | "translate" | "radioGroup" | "about" | "datatype" | "inlist" | "prefix" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "kind" | "src" | "srcLang" | "value" | "download" | "hrefLang" | "rel" | "alt" | "coords" | "shape" | "autoPlay" | "controls" | "loop" | "mediaGroup" | "muted" | "playsInline" | "preload" | "autoFocus" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "dateTime" | "acceptCharset" | "action" | "autoComplete" | "encType" | "noValidate" | "manifest" | "allowFullScreen" | "allowTransparency" | "frameBorder" | "marginHeight" | "marginWidth" | "sandbox" | "scrolling" | "seamless" | "srcDoc" | "sizes" | "srcSet" | "accept" | "capture" | "checked" | "list" | "maxLength" | "minLength" | "readOnly" | "step" | "challenge" | "keyType" | "keyParams" | "htmlFor" | "as" | "integrity" | "charSet" | "httpEquiv" | "optimum" | "reversed" | "selected" | "nonce" | "scoped" | "cellPadding" | "cellSpacing" | "colSpan" | "headers" | "rowSpan" | "scope" | "cols" | "rows" | "poster"> & {
10 | +export declare const TextareaAutosize: React.ForwardRefExoticComponent, "default" | "max" | "required" | "media" | "hidden" | "cite" | "data" | "dir" | "form" | "label" | "slot" | "span" | "style" | "title" | "pattern" | "async" | "start" | "low" | "high" | "defer" | "open" | "disabled" | "color" | "content" | "size" | "wrap" | "multiple" | "summary" | "className" | "height" | "id" | "lang" | "method" | "min" | "name" | "target" | "type" | "width" | "role" | "tabIndex" | "crossOrigin" | "href" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "children" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "key" | "classID" | "useMap" | "wmode" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "contentEditable" | "contextMenu" | "draggable" | "placeholder" | "spellCheck" | "translate" | "radioGroup" | "about" | "datatype" | "inlist" | "prefix" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "kind" | "src" | "srcLang" | "value" | "download" | "hrefLang" | "rel" | "alt" | "coords" | "shape" | "autoPlay" | "controls" | "loop" | "mediaGroup" | "muted" | "playsInline" | "preload" | "autoFocus" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "dateTime" | "acceptCharset" | "action" | "autoComplete" | "encType" | "noValidate" | "manifest" | "allowFullScreen" | "allowTransparency" | "frameBorder" | "marginHeight" | "marginWidth" | "sandbox" | "scrolling" | "seamless" | "srcDoc" | "sizes" | "srcSet" | "accept" | "capture" | "checked" | "list" | "maxLength" | "minLength" | "readOnly" | "step" | "challenge" | "keyType" | "keyParams" | "htmlFor" | "as" | "integrity" | "charSet" | "httpEquiv" | "optimum" | "reversed" | "selected" | "nonce" | "scoped" | "cellPadding" | "cellSpacing" | "colSpan" | "headers" | "rowSpan" | "scope" | "cols" | "rows" | "poster"> & {
11 | /** Called whenever the textarea resizes */
12 | onResize?: ((e: Event) => void) | undefined;
13 | /** Minimum number of visible rows */
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/inc/functions.php:
--------------------------------------------------------------------------------
1 | __NAMESPACE__ . '\render_summarize_block',
23 | ]
24 | );
25 | }
26 |
27 | add_action( 'init', __NAMESPACE__ . '\register_summarize_button_block' );
28 |
29 | /**
30 | * Render callback for the summarize block.
31 | *
32 | * @param array $attributes Block attributes.
33 | * @param string $content Block content.
34 | * @param WP_Block $block Block instance.
35 | * @return string Rendered block type output.
36 | */
37 | function render_summarize_block( $attributes, string $content, WP_Block $block ) {
38 | wp_enqueue_script_module( '@ai-experiments/summarize-button' );
39 | wp_enqueue_style( 'ai-experiments-summarize-button' );
40 |
41 | // TODO: Figure out why the stylesheet is not enqueued the regular way.
42 | wp_add_inline_style(
43 | 'wp-block-library',
44 | file_get_contents(
45 | plugin_dir_path( __DIR__ ) . 'build/style-summarize-button.css'
46 | )
47 | );
48 |
49 | $id = wp_generate_uuid4();
50 |
51 | $popover_button_id = "ai-summary-button=$id";
52 | $popover_id = "ai-summary-popover=$id";
53 |
54 | $summary_context = $attributes['context'] ?? 'post';
55 |
56 | $context = [
57 | 'summary' => '',
58 | 'summaryContext' => $summary_context,
59 | 'isLoading' => false,
60 | 'isOpen' => false,
61 | 'buttonText' => __( 'Read AI-generated summary', 'ai-experiments' ),
62 | ];
63 |
64 | ob_start();
65 | ?>
66 |
68 |
69 | data-wp-interactive="ai-experiments/summarize-button"
70 | >
71 |
94 |
100 |
108 |
109 |
110 |
111 | 'defer',
181 | )
182 | );
183 |
184 | wp_set_script_translations( 'ai-experiments-editor', 'ai-experiments' );
185 |
186 | wp_enqueue_style(
187 | 'ai-experiments-editor',
188 | plugins_url( 'build/style-editor.css', __DIR__ ),
189 | array( 'wp-components' ),
190 | $asset['version']
191 | );
192 |
193 | wp_style_add_data( 'media-experiments-editor', 'rtl', 'replace' );
194 |
195 | $asset_file = dirname( __DIR__ ) . '/build/summarize-button.asset.php';
196 | $asset = is_readable( $asset_file ) ? require $asset_file : [];
197 |
198 | $asset['dependencies'] = $asset['dependencies'] ?? [];
199 | $asset['version'] = $asset['version'] ?? '';
200 |
201 | wp_enqueue_script(
202 | 'ai-experiments-summarize-button',
203 | plugins_url( 'build/summarize-button.js', __DIR__ ),
204 | $asset['dependencies'],
205 | $asset['version'],
206 | array(
207 | 'strategy' => 'defer',
208 | )
209 | );
210 |
211 | $asset_file = dirname( __DIR__ ) . '/build/translate-button.asset.php';
212 | $asset = is_readable( $asset_file ) ? require $asset_file : [];
213 |
214 | $asset['dependencies'] = $asset['dependencies'] ?? [];
215 | $asset['version'] = $asset['version'] ?? '';
216 |
217 | wp_enqueue_script(
218 | 'ai-experiments-translate-button',
219 | plugins_url( 'build/translate-button.js', __DIR__ ),
220 | $asset['dependencies'],
221 | $asset['version'],
222 | array(
223 | 'strategy' => 'defer',
224 | )
225 | );
226 | }
227 |
228 | add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\enqueue_block_editor_assets' );
229 |
230 | /**
231 | * Enqueues scripts for the block editor, iframed.
232 | *
233 | * @return void
234 | */
235 | function enqueue_block_assets(): void {
236 | if ( ! is_admin() ) {
237 | return;
238 | }
239 |
240 | $asset_file = dirname( __DIR__ ) . '/build/editor.asset.php';
241 | $asset = is_readable( $asset_file ) ? require $asset_file : [];
242 |
243 | $asset['dependencies'] = $asset['dependencies'] ?? [];
244 | $asset['version'] = $asset['version'] ?? '';
245 |
246 | wp_enqueue_style(
247 | 'ai-experiments-editor',
248 | plugins_url( 'build/style-editor.css', __DIR__ ),
249 | array( 'wp-components' ),
250 | $asset['version']
251 | );
252 |
253 | wp_style_add_data( 'media-experiments-editor', 'rtl', 'replace' );
254 | }
255 |
256 | add_action( 'enqueue_block_assets', __NAMESPACE__ . '\enqueue_block_assets' );
257 |
258 | /**
259 | * Registers assets for the frontend.
260 | */
261 | function register_frontend_assets() {
262 | $asset_file = dirname( __DIR__ ) . '/build/summarize-button-view.asset.php';
263 | $asset = is_readable( $asset_file ) ? require $asset_file : [];
264 |
265 | $asset['dependencies'] = $asset['dependencies'] ?? [];
266 | $asset['version'] = $asset['version'] ?? '';
267 |
268 | wp_register_script_module(
269 | '@ai-experiments/summarize-button',
270 | plugins_url( 'build/summarize-button-view.js', __DIR__ ),
271 | array( '@wordpress/interactivity' ),
272 | $asset['version'],
273 | );
274 |
275 | $asset_file = dirname( __DIR__ ) . '/build/summarize-button.asset.php';
276 | $asset = is_readable( $asset_file ) ? require $asset_file : [];
277 |
278 | $asset['dependencies'] = $asset['dependencies'] ?? [];
279 | $asset['version'] = $asset['version'] ?? '';
280 |
281 | wp_register_style(
282 | 'ai-experiments-summarize-button',
283 | plugins_url( 'build/style-summarize-button.css', __DIR__ ),
284 | $asset['dependencies'],
285 | $asset['version'],
286 | );
287 |
288 | $asset_file = dirname( __DIR__ ) . '/build/translate-button-view.asset.php';
289 | $asset = is_readable( $asset_file ) ? require $asset_file : [];
290 |
291 | $asset['dependencies'] = $asset['dependencies'] ?? [];
292 | $asset['version'] = $asset['version'] ?? '';
293 |
294 | wp_register_script_module(
295 | '@ai-experiments/translate-button',
296 | plugins_url( 'build/translate-button-view.js', __DIR__ ),
297 | array( '@wordpress/interactivity' ),
298 | $asset['version'],
299 | );
300 |
301 | $asset_file = dirname( __DIR__ ) . '/build/translate-button.asset.php';
302 | $asset = is_readable( $asset_file ) ? require $asset_file : [];
303 |
304 | $asset['dependencies'] = $asset['dependencies'] ?? [];
305 | $asset['version'] = $asset['version'] ?? '';
306 |
307 | wp_register_style(
308 | 'ai-experiments-translate-button',
309 | plugins_url( 'build/style-translate-button.css', __DIR__ ),
310 | $asset['dependencies'],
311 | $asset['version'],
312 | );
313 | }
314 |
315 | add_action( 'init', __NAMESPACE__ . '\register_frontend_assets' );
316 |
317 |
318 | /**
319 | * Registers the translate button block.
320 | */
321 | function register_translate_button_block() {
322 | register_block_type(
323 | dirname( __DIR__ ) . '/build/translate-button',
324 | [
325 | 'render_callback' => __NAMESPACE__ . '\render_translate_block',
326 | ]
327 | );
328 | }
329 |
330 | add_action( 'init', __NAMESPACE__ . '\register_translate_button_block' );
331 |
332 | /**
333 | * Render callback for the translate block.
334 | *
335 | * @param array $attributes Block attributes.
336 | * @param string $content Block content.
337 | * @param WP_Block $block Block instance.
338 | * @return string Rendered block type output.
339 | */
340 | function render_translate_block( $attributes, string $content, WP_Block $block ) {
341 | wp_enqueue_script_module( '@ai-experiments/translate-button' );
342 | wp_enqueue_style( 'ai-experiments-translate-button' );
343 |
344 | // TODO: Figure out why the stylesheet is not enqueued the regular way.
345 | // TODO: Only load it once.
346 | wp_add_inline_style(
347 | 'wp-block-library',
348 | file_get_contents(
349 | plugin_dir_path( __DIR__ ) . 'build/style-translate-button.css'
350 | )
351 | );
352 |
353 | $context = [
354 | 'isTranslatable' => false,
355 | 'isLoading' => false,
356 | 'isShowingTranslation' => false,
357 | 'translation' => null,
358 | 'commentId' => isset( $block->context['commentId'] ) ? (int) $block->context['commentId'] : null,
359 | 'buttonText' => __( 'Translate', 'ai-experiments' ),
360 | 'sourceLanguage' => explode( '_', get_locale() )[0],
361 | 'targetLanguage' => explode( '_', get_locale() )[0],
362 | ];
363 |
364 | ob_start();
365 | ?>
366 |
368 |
369 | data-wp-interactive="ai-experiments/translate-button"
370 | >
371 |
393 |
394 |
395 | (
24 |
33 | );
34 |
35 | const LabelAutoIcon = () => (
36 |
45 | );
46 |
47 | const LightBulbTipsIcon = () => (
48 |
57 | );
58 |
59 | const MAX_TERMS_SUGGESTIONS = 20;
60 | const DEFAULT_QUERY = {
61 | per_page: MAX_TERMS_SUGGESTIONS,
62 | _fields: 'id,name',
63 | context: 'view',
64 | };
65 |
66 | function useAICommandLoader() {
67 | const { editPost } = useDispatch( editorStore );
68 | const {
69 | replaceBlock,
70 | insertBlock,
71 | __unstableMarkNextChangeAsNotPersistent,
72 | __unstableMarkLastChangeAsPersistent,
73 | } = useDispatch( blockEditorStore );
74 |
75 | const [ isLoading ] = useState( false );
76 |
77 | const { blocks, plainTextContent } = useSelect( ( select ) => {
78 | const allBlocks: BlockInstance[] = select( blockEditorStore )
79 | // @ts-ignore
80 | .getClientIdsWithDescendants()
81 | .map( ( clientId: string ) =>
82 | // @ts-ignore
83 | select( blockEditorStore ).getBlock( clientId )
84 | )
85 | .filter( ( block ) => block !== null );
86 |
87 | // @ts-ignore
88 | const content: string =
89 | new window.DOMParser().parseFromString(
90 | serialize( allBlocks ),
91 | 'text/html'
92 | ).body.textContent || '';
93 | return {
94 | blocks: allBlocks,
95 | plainTextContent: content,
96 | };
97 | }, [] );
98 |
99 | const { termsList } = useSelect( ( select ) => {
100 | // @ts-ignore
101 | const { getEntityRecords, getTaxonomies } = select( coreStore );
102 |
103 | // @ts-ignore
104 | const postType = select( editorStore ).getCurrentPostType();
105 |
106 | // @ts-ignore
107 | const _taxonomies = getTaxonomies( {
108 | per_page: -1,
109 | } );
110 | const visibleTaxonomies = ( _taxonomies ?? [] ).filter(
111 | ( taxonomy: {
112 | types: string[];
113 | visibility?: { show_ui: boolean };
114 | } ) =>
115 | // In some circumstances .visibility can end up as undefined so optional chaining operator required.
116 | // https://github.com/WordPress/gutenberg/issues/40326
117 | taxonomy.types.includes( postType ) &&
118 | taxonomy.visibility?.show_ui
119 | );
120 |
121 | const termsPerTaxonomy = Object.fromEntries(
122 | visibleTaxonomies.map( ( { slug }: { slug: string } ) => {
123 | return [
124 | slug,
125 | // @ts-ignore
126 | getEntityRecords(
127 | 'taxonomy',
128 | slug,
129 | DEFAULT_QUERY
130 | ) as Term[],
131 | ];
132 | } )
133 | );
134 |
135 | // eslint-disable-next-line @typescript-eslint/no-shadow
136 | const termsList = (
137 | visibleTaxonomies?.map( ( { slug }: { slug: string } ) => {
138 | return `Taxonomy: ${ slug }:\n\n${
139 | termsPerTaxonomy[ slug ]
140 | ?.map(
141 | ( {
142 | name: term,
143 | id,
144 | }: {
145 | name: string;
146 | id: number;
147 | } ) => `* ${ term } (ID: ${ id })`
148 | )
149 | .join( '\n' ) || ''
150 | }`;
151 | } ) || []
152 | ).join( '\n\n' );
153 |
154 | return {
155 | termsList,
156 | };
157 | }, [] );
158 |
159 | const commands = [
160 | {
161 | name: 'ai-experiments/write-excerpt',
162 | label: __( 'Write excerpt', 'ai-experiments' ),
163 | icon: ,
164 | // @ts-ignore
165 | callback: async ( { close } ) => {
166 | close();
167 |
168 | const session = await LanguageModel.create();
169 |
170 | const stream = session.promptStreaming(
171 | `Summarise the following text in full sentences in less than 300 characters: ${ plainTextContent }`
172 | );
173 |
174 | let result = '';
175 |
176 | for await ( const value of stream ) {
177 | // Each result contains the full data, not just the incremental part.
178 | result = value;
179 |
180 | editPost( { excerpt: result }, { isCached: true } );
181 | }
182 |
183 | result = result.replaceAll( '\n\n\n\n', '\n\n' );
184 |
185 | editPost( { excerpt: result } );
186 | },
187 | },
188 | {
189 | name: 'ai-experiments/write-title',
190 | label: __( 'Write title', 'ai-experiments' ),
191 | icon: ,
192 | // @ts-ignore
193 | callback: async ( { close } ) => {
194 | close();
195 |
196 | // const summarizer = await wSummarizer.create( {
197 | // sharedContext: 'A blog post',
198 | // type: 'headline',
199 | // format: 'plain-text',
200 | // length: 'medium',
201 | // } );
202 | //
203 | // const stream = summarizer.summarizeStreaming(
204 | // plainTextContent.slice( 0, 1000 ),
205 | // {
206 | // context:
207 | // 'Avoid any toxic language and be as constructive as possible.',
208 | // }
209 | // );
210 |
211 | const session = await LanguageModel.create();
212 |
213 | const stream = session.promptStreaming(
214 | `Write a plain-text headline for the following text in less than 100 characters: ${ plainTextContent }`
215 | );
216 |
217 | let result = '';
218 |
219 | for await ( const value of stream ) {
220 | // Each result contains the full data, not just the incremental part.
221 | result = value;
222 | }
223 |
224 | result = result.trim();
225 | result = result.replaceAll( /\.$/g, '' );
226 |
227 | editPost( { title: result } );
228 | },
229 | },
230 | {
231 | name: 'ai-experiments/generate-permalink',
232 | label: __( 'Generate permalink', 'ai-experiments' ),
233 | icon: ,
234 | // @ts-ignore
235 | callback: async ( { close } ) => {
236 | close();
237 |
238 | const session = await LanguageModel.create( {
239 | initialPrompts: [
240 | {
241 | role: 'system',
242 | content:
243 | 'Write a plain-text URL slug for for the following text in less than 100 character',
244 | },
245 | {
246 | role: 'user',
247 | content:
248 | 'At WordCamp US 2024 I gave a presentation about client-side media processing, which is all about bringing WordPress’ media uploading and editing capabilities from the server to the browser. The recording is not yet available, but in the meantime you can re-watch the livestream or check out the slides. This blog post is a written adaption of this talk.',
249 | },
250 | {
251 | role: 'assistant',
252 | content: 'wordpress-media-processing',
253 | },
254 | {
255 | role: 'user',
256 | content:
257 | 'Learn how to leverage WordPress Playground and Blueprints for automated end-to-end browser and performance testing.',
258 | },
259 | {
260 | role: 'assistant',
261 | content: 'wordpress-playground-testing',
262 | },
263 | ],
264 | } );
265 |
266 | const stream = session.promptStreaming(
267 | ` ${ plainTextContent }`
268 | );
269 |
270 | let result = '';
271 |
272 | for await ( const value of stream ) {
273 | // Each result contains the full data, not just the incremental part.
274 | result = value;
275 | }
276 |
277 | result = result.trim();
278 | result = result.replaceAll( /\.$/g, '' );
279 |
280 | editPost( { slug: result } );
281 | },
282 | },
283 | {
284 | name: 'ai-experiments/assign-tags',
285 | label: __( 'Assign tags and categories', 'ai-experiments' ),
286 | icon: ,
287 | // @ts-ignore
288 | callback: async ( { close } ) => {
289 | close();
290 |
291 | const session = await LanguageModel.create( {
292 | initialPrompts: [
293 | {
294 | role: 'system',
295 | content: `You are a content assistant tasked with categorizing given content with the correct terms.
296 |
297 | The following terms exist on the site:
298 |
299 | ${ termsList }
300 |
301 | Given the provided content, determine the most suitable terms to describe the content.
302 | Do not summarize the content. Do not hallucinate.
303 | Provide the output as a comma-separated list of recommended term IDs.
304 | `,
305 | },
306 | {
307 | role: 'user',
308 | content:
309 | 'This is a presentation about my favorite content management system, WordPress. Go check it out.',
310 | },
311 | {
312 | role: 'assistant',
313 | content: '10,6',
314 | },
315 | {
316 | role: 'user',
317 | content: `I love pizza and Drupal!`,
318 | },
319 | {
320 | role: 'assistant',
321 | content: '1,3,4',
322 | },
323 | ],
324 | } );
325 |
326 | const prompt =
327 | `You are a content assistant tasked with categorizing given content with the correct terms.
328 |
329 | The following terms exist on the site:
330 |
331 | ${ termsList }
332 |
333 | Given these terms and provided content, determine the most suitable terms to describe the content.
334 | Do not summarize. Do not hallucinate.
335 | Provide the output as a comma-separated list of numeric term IDs.
336 |
337 | Content:
338 |
339 | ${ plainTextContent }`
340 | .replaceAll( '\t', '' )
341 | .replaceAll( '\n\n\n\n', '\n\n' );
342 |
343 | const stream = session.promptStreaming( prompt );
344 |
345 | let result = '';
346 |
347 | for await ( const value of stream ) {
348 | // Each result contains the full data, not just the incremental part.
349 | result = value;
350 | }
351 |
352 | result = result.replaceAll( '\n\n\n\n', '\n\n' );
353 |
354 | const newTermIds = result
355 | .split( ',' )
356 | .filter( Boolean )
357 | .map( ( value ) => Number( value.trim() ) )
358 | .filter( ( value ) => ! Number.isNaN( value ) );
359 |
360 | // TODO: Map term IDs back to taxonomy rest_base.
361 | editPost( { tags: newTermIds } );
362 | },
363 | },
364 | {
365 | name: 'ai-experiments/sentiment',
366 | label: __( 'Sentiment analysis', 'ai-experiments' ),
367 | icon: ,
368 | // @ts-ignore
369 | callback: async ( { close } ) => {
370 | close();
371 |
372 | const session = await LanguageModel.create();
373 |
374 | const stream = session.promptStreaming(
375 | `What is the overall vibe of this content? Only respond with "positive" or "negative". Do not provide any explanation for your answer. ${ plainTextContent }`
376 | );
377 |
378 | let result = '';
379 |
380 | for await ( const value of stream ) {
381 | // Each result contains the full data, not just the incremental part.
382 | result = value;
383 | }
384 |
385 | // eslint-disable-next-line no-alert -- For testing only.
386 | alert( result );
387 | },
388 | },
389 | {
390 | name: 'ai-experiments/generate-quote',
391 | label: __( 'Generate memorable quote', 'ai-experiments' ),
392 | icon: ,
393 | // @ts-ignore
394 | callback: async ( { close } ) => {
395 | close();
396 |
397 | const session = await LanguageModel.create();
398 |
399 | const stream = session.promptStreaming(
400 | `You are a writing assistant tasked with providing feedback on content and rephrasing texts to make them more readable and contain less errors. From the following user-provided text, extract a short, memorable quote that can be easily shared in a tweet. The text is in English. Use English (US) grammar. Do not make spelling mistakes. Here is the text:
401 | ${ plainTextContent }`
402 | );
403 |
404 | let result = '';
405 |
406 | const quoteBlock = createBlock( 'core/quote', {}, [
407 | createBlock( 'core/paragraph', {
408 | content: __( 'Generating…', 'ai-experiments' ),
409 | } ),
410 | ] );
411 | let newClientId = quoteBlock.clientId;
412 |
413 | insertBlock( quoteBlock, 0 );
414 |
415 | for await ( const value of stream ) {
416 | // Each result contains the full data, not just the incremental part.
417 | result = value.replaceAll( '\n\n\n\n', '\n\n' );
418 | result = `${ result }
`;
419 |
420 | void __unstableMarkNextChangeAsNotPersistent();
421 |
422 | const parsedBlocks = pasteHandler( {
423 | plainText: result,
424 | mode: 'BLOCKS',
425 | } );
426 | if ( parsedBlocks.length > 0 ) {
427 | replaceBlock( newClientId, parsedBlocks );
428 | newClientId = parsedBlocks[ 0 ].clientId;
429 | }
430 | }
431 |
432 | void __unstableMarkLastChangeAsPersistent();
433 | },
434 | },
435 | {
436 | name: 'ai-experiments/translate',
437 | label: __( 'Translate content', 'ai-experiments' ),
438 | icon: language,
439 | // @ts-ignore
440 | callback: async ( { close } ) => {
441 | close();
442 |
443 | // Translate all blocks separately in parallel.
444 | await Promise.allSettled(
445 | blocks.map( async ( block: BlockInstance ) => {
446 | if ( [ 'core/code' ].includes( block.name ) ) {
447 | return;
448 | }
449 |
450 | const blockContent = getBlockContent( block );
451 |
452 | const result = await translateString(
453 | blockContent,
454 | 'es'
455 | );
456 |
457 | if ( null === result ) {
458 | return;
459 | }
460 |
461 | const parsedBlocks = pasteHandler( {
462 | plainText: result,
463 | mode: 'BLOCKS',
464 | } );
465 |
466 | if ( parsedBlocks.length > 0 ) {
467 | void __unstableMarkNextChangeAsNotPersistent();
468 | replaceBlock( block.clientId, parsedBlocks );
469 | }
470 | } )
471 | );
472 |
473 | void __unstableMarkLastChangeAsPersistent();
474 | },
475 | },
476 | ];
477 |
478 | return {
479 | commands,
480 | isLoading,
481 | };
482 | }
483 |
484 | function RenderPlugin() {
485 | useCommandLoader( {
486 | name: 'ai-experiments/ai-commands',
487 | hook: useAICommandLoader,
488 | } );
489 | return null;
490 | }
491 |
492 | registerPlugin( 'ai-experiments', {
493 | render: RenderPlugin,
494 | } );
495 |
--------------------------------------------------------------------------------
/packages/editor/src/@types/wordpress__block-editor/store/selectors.d.ts:
--------------------------------------------------------------------------------
1 | import type { BlockInstance } from '@wordpress/blocks';
2 |
3 | /**
4 | * Determines if the given block type is allowed to be inserted into the block list.
5 | *
6 | * @param state
7 | * @param blockName - The name of the block type, e.g.' core/paragraph'.
8 | * @param rootClientId - Optional root client ID of block list.
9 | *
10 | * @returns Whether the given block type is allowed to be inserted.
11 | */
12 | export function canInsertBlockType(
13 | state: Record< string, unknown >,
14 | blockName: string,
15 | rootClientId?: string
16 | ): boolean;
17 |
18 | /**
19 | * Returns the client ID of the block adjacent one at the given reference `startClientId` and modifier
20 | * directionality. Defaults start `startClientId` to the selected block, and direction as next block.
21 | * Returns `null` if there is no adjacent block.
22 | *
23 | * @param state
24 | * @param startClientId - Optional client ID of block from which to search.
25 | * @param modifier - Directionality multiplier (1 next, -1 previous).
26 | *
27 | * @returns Return the client ID of the block, or null if none exists.
28 | */
29 | export function getAdjacentBlockClientId(
30 | state: Record< string, unknown >,
31 | startClientId?: string,
32 | modifier?: 1 | -1
33 | ): string | null;
34 |
35 | /**
36 | * Returns a block given its client ID. This is a parsed copy of the block, containing its
37 | * `blockName`, `clientId`, and current `attributes` state. This is not the block's registration
38 | * settings, which must be retrieved from the blocks module registration store.
39 | *
40 | * @param state
41 | * @param clientId - Block client ID.
42 | *
43 | * @returns Parsed block object.
44 | */
45 | export function getBlock(
46 | state: Record< string, unknown >,
47 | clientId: string
48 | ): BlockInstance | null;
49 |
50 | /**
51 | * Returns a block's attributes given its client ID, or null if no block exists with the client ID.
52 | *
53 | * @param state
54 | * @param clientId - Block client ID.
55 | *
56 | * @returns Block attributes.
57 | */
58 | export function getBlockAttributes(
59 | state: Record< string, unknown >,
60 | clientId: string
61 | ): Record< string, any > | null;
62 |
63 | /**
64 | * Returns the number of blocks currently present in the post.
65 | *
66 | * @param state
67 | * @param rootClientId - Optional root client ID of block list.
68 | *
69 | * @returns Number of blocks in the post.
70 | */
71 | export function getBlockCount(
72 | state: Record< string, unknown >,
73 | rootClientId?: string
74 | ): number;
75 |
76 | /**
77 | * Given a block client ID, returns the root of the hierarchy from which the block is nested, return
78 | * the block itself for root level blocks.
79 | *
80 | * @param state
81 | * @param clientId - Block from which to find root client ID.
82 | *
83 | * @returns Root client ID
84 | */
85 | export function getBlockHierarchyRootClientId(
86 | state: Record< string, unknown >,
87 | clientId: string
88 | ): string;
89 |
90 | /**
91 | * Returns the index at which the block corresponding to the specified client ID occurs within the
92 | * block order, or `-1` if the block does not exist.
93 | *
94 | * @param state
95 | * @param clientId - Block client ID.
96 | * @param rootClientId - Optional root client ID of block list.
97 | *
98 | * @returns Index at which block exists in order.
99 | */
100 | export function getBlockIndex(
101 | state: Record< string, unknown >,
102 | clientId: string,
103 | rootClientId?: string
104 | ): number;
105 |
106 | /**
107 | * Returns the insertion point, the index at which the new inserted block would
108 | * be placed. Defaults to the last index.
109 | */
110 | export function getBlockInsertionPoint(): {
111 | state: Record< string, unknown >;
112 | index: number;
113 | rootClientId?: string | undefined;
114 | };
115 |
116 | /**
117 | * Returns the Block List settings of a block, if any exist.
118 | *
119 | * @param state
120 | * @param clientId - Block client ID.
121 | *
122 | * @returns Block settings of the block if set.
123 | */
124 | export function getBlockListSettings(
125 | state: Record< string, unknown >,
126 | clientId?: string
127 | ): Record< string, unknown > | undefined;
128 |
129 | /**
130 | * Returns the block's editing mode, defaulting to `"visual"` if not explicitly assigned.
131 | *
132 | * @param state
133 | * @param clientId - Block client ID.
134 | *
135 | * @returns Block editing mode.
136 | */
137 | export function getBlockMode(
138 | state: Record< string, unknown >,
139 | clientId: string
140 | ): string;
141 |
142 | /**
143 | * Returns a block's name given its client ID, or `null` if no block exists with the client ID.
144 | *
145 | * @param state
146 | * @param clientId - Block client ID.
147 | *
148 | * @returns Block name.
149 | */
150 | export function getBlockName(
151 | state: Record< string, unknown >,
152 | clientId: string
153 | ): string | null;
154 |
155 | /**
156 | * Returns an array containing all block client IDs in the editor in the order they appear.
157 | * Optionally accepts a root client ID of the block list for which the order should be returned,
158 | * defaulting to the top-level block order.
159 | *
160 | * @param state
161 | * @param rootClientId - Optional root client ID of block list.
162 | *
163 | * @returns Ordered client IDs of editor blocks.
164 | */
165 | export function getBlockOrder(
166 | state: Record< string, unknown >,
167 | rootClientId?: string
168 | ): string[];
169 |
170 | /**
171 | * Given a block client ID, returns the root block from which the block is nested, an empty string
172 | * for top-level blocks, or `null` if the block does not exist.
173 | *
174 | * @param state
175 | * @param clientId - Block from which to find root client ID.
176 | *
177 | * @returns Root client ID, if exists
178 | */
179 | export function getBlockRootClientId(
180 | state: Record< string, unknown >,
181 | clientId: string
182 | ): string | null;
183 |
184 | /**
185 | * Returns the current block selection end. This value may be `undefined`, and it may represent either a
186 | * singular block selection or multi-selection end. A selection is singular if its start and end
187 | * match.
188 | *
189 | * @returns Client ID of block selection end.
190 | */
191 | export function getBlockSelectionEnd(
192 | state: Record< string, unknown >
193 | ): string | undefined;
194 |
195 | /**
196 | * Returns the current block selection start. This value may be `undefined`, and it may represent
197 | * either a singular block selection or multi-selection start. A selection is singular if its start
198 | * and end match.
199 | *
200 | * @returns Client ID of block selection start.
201 | */
202 | export function getBlockSelectionStart(
203 | state: Record< string, unknown >
204 | ): string | undefined;
205 |
206 | /**
207 | * Returns all block objects for the current post being edited as an array in the order they appear
208 | * in the post.
209 | *
210 | * Note: It's important to memoize this selector to avoid return a new instance
211 | * on each call
212 | *
213 | * @param state
214 | * @param rootClientId - Optional root client ID of block list.
215 | *
216 | * @returns Post blocks.
217 | */
218 | export function getBlocks(
219 | state: Record< string, unknown >,
220 | rootClientId?: string
221 | ): BlockInstance[];
222 |
223 | /**
224 | * Given an array of block client IDs, returns the corresponding array of block objects or `null`.
225 | *
226 | * @param clientIds - Client IDs for which blocks are to be returned.
227 | */
228 | export function getBlocksByClientId(
229 | clientIds: string | string[]
230 | ): Array< BlockInstance | null >;
231 |
232 | /**
233 | * Returns an array containing the clientIds of all descendants of the blocks given.
234 | *
235 | * @param state
236 | * @param clientIds - Array of block ids to inspect.
237 | *
238 | * @returns ids of descendants.
239 | */
240 | export function getClientIdsOfDescendants(
241 | state: Record< string, unknown >,
242 | clientIds: string[]
243 | ): string[];
244 |
245 | /**
246 | * Returns an array containing the `clientIds` of the top-level blocks and their descendants of any
247 | * depth (for nested blocks).
248 | *
249 | * @returns ids of top-level and descendant blocks.
250 | */
251 | export function getClientIdsWithDescendants(
252 | state: Record< string, unknown >
253 | ): string[];
254 |
255 | /**
256 | * Returns the client ID of the first block in the multi-selection set, or `null` if there is no
257 | * multi-selection.
258 | *
259 | * @returns First block client ID in the multi-selection set.
260 | */
261 | export function getFirstMultiSelectedBlockClientId(
262 | state: Record< string, unknown >
263 | ): string | null;
264 |
265 | /**
266 | * Returns the total number of blocks, or the total number of blocks with a specific name in a post.
267 | * The number returned includes nested blocks.
268 | *
269 | * @param state
270 | * @param blockName - Optional block name, if specified only blocks of that type will be counted.
271 | *
272 | * @returns Number of blocks in the post, or number of blocks with name equal to `blockName`.
273 | */
274 | export function getGlobalBlockCount(
275 | state: Record< string, unknown >,
276 | blockName?: string
277 | ): number;
278 |
279 | /**
280 | * Determines the items that appear in the inserter. Includes both static items (e.g. a regular
281 | * block type) and dynamic items (e.g. a reusable block).
282 | *
283 | * @remarks
284 | * Each item object contains what's necessary to display a button in the inserter and handle its
285 | * selection.
286 | *
287 | * The `utility` property indicates how useful we think an item will be to the user. There are 4
288 | * levels of utility:
289 | *
290 | * 1. Blocks that are contextually useful (utility = 3)
291 | * 2. Blocks that have been previously inserted (utility = 2)
292 | * 3. Blocks that are in the common category (utility = 1)
293 | * 4. All other blocks (utility = 0)
294 | *
295 | * The `frecency` property is a heuristic (https://en.wikipedia.org/wiki/Frecency)
296 | * that combines block usage frequenty and recency.
297 | *
298 | * Items are returned ordered descendingly by their `utility` and `frecency`.
299 | *
300 | * @param state
301 | * @param rootClientId - Optional root client ID of block list.
302 | *
303 | * @returns Items that appear in inserter.
304 | */
305 | export function getInserterItems(
306 | state: Record< string, unknown >,
307 | rootClientId?: string
308 | ): Record< string, unknown >[];
309 |
310 | /**
311 | * Returns the client ID of the last block in the multi-selection set, or `null` if there is no
312 | * multi-selection.
313 | *
314 | * @returns Last block client ID in the multi-selection set.
315 | */
316 | export function getLastMultiSelectedBlockClientId(
317 | state: Record< string, unknown >
318 | ): string | null;
319 |
320 | /**
321 | * Returns the current multi-selection set of block client IDs, or an empty array if there is no
322 | * multi-selection.
323 | *
324 | * @returns Multi-selected block client IDs.
325 | */
326 | export function getMultiSelectedBlockClientIds(
327 | state: Record< string, unknown >
328 | ): string[];
329 |
330 | /**
331 | * Returns the current multi-selection set of blocks, or an empty array if there is no
332 | * multi-selection.
333 | *
334 | * @returns Multi-selected block objects.
335 | */
336 | export function getMultiSelectedBlocks(
337 | state: Record< string, unknown >
338 | ): BlockInstance[];
339 |
340 | /**
341 | * Returns the client ID of the block which ends the multi-selection set, or `null` if there is no
342 | * multi-selection.
343 | *
344 | * This is not necessarily the last client ID in the selection.
345 | *
346 | * @see getLastMultiSelectedBlockClientId
347 | *
348 | * @returns Client ID of block ending multi-selection.
349 | */
350 | export function getMultiSelectedBlocksEndClientId(
351 | state: Record< string, unknown >
352 | ): string | null;
353 |
354 | /**
355 | * Returns the client ID of the block which begins the multi-selection set, or `null` if there is no
356 | * multi-selection.
357 | *
358 | * This is not necessarily the first client ID in the selection.
359 | *
360 | * @see getFirstMultiSelectedBlockClientId
361 | *
362 | * @returns Client ID of block beginning multi-selection.
363 | */
364 | export function getMultiSelectedBlocksStartClientId(
365 | state: Record< string, unknown >
366 | ): string | null;
367 |
368 | /**
369 | * Returns the next block's client ID from the given reference start ID. Defaults start to the
370 | * selected block. Returns `null` if there is no next block.
371 | *
372 | * @param state
373 | * @param startClientId - Optional client ID of block from which to search.
374 | *
375 | * @returns Adjacent block's client ID, or `null` if none exists.
376 | */
377 | export function getNextBlockClientId(
378 | state: Record< string, unknown >,
379 | startClientId?: string
380 | ): string | null;
381 |
382 | /**
383 | * Returns the previous block's client ID from the given reference start ID. Defaults start to the
384 | * selected block. Returns `null` if there is no previous block.
385 | *
386 | * @param state
387 | * @param startClientId - Optional client ID of block from which to search.
388 | *
389 | * @returns Adjacent block's client ID, or `null` if none exists.
390 | */
391 | export function getPreviousBlockClientId(
392 | state: Record< string, unknown >,
393 | startClientId?: string
394 | ): string | null;
395 |
396 | /**
397 | * Returns the currently selected block, or `null` if there is no selected block.
398 | *
399 | * @returns Selected block.
400 | */
401 | export function getSelectedBlock(
402 | state: Record< string, unknown >
403 | ): BlockInstance | null;
404 |
405 | /**
406 | * Returns the currently selected block client ID, or `null` if there is no selected block.
407 | *
408 | * @returns Selected block client ID.
409 | */
410 | export function getSelectedBlockClientId(
411 | state: Record< string, unknown >
412 | ): string | null;
413 |
414 | /**
415 | * Returns the current selection set of block client IDs (multiselection or single selection).
416 | *
417 | * @returns Multi-selected block client IDs.
418 | */
419 | export function getSelectedBlockClientIds(
420 | state: Record< string, unknown >
421 | ): string[];
422 |
423 | /**
424 | * Returns the number of blocks currently selected in the post.
425 | *
426 | * @returns Number of blocks selected in the post.
427 | */
428 | export function getSelectedBlockCount(
429 | state: Record< string, unknown >
430 | ): number;
431 |
432 | /**
433 | * Returns the initial caret position for the selected block. This position is to used to position
434 | * the caret properly when the selected block changes.
435 | */
436 | export function getSelectedBlocksInitialCaretPosition(
437 | state: Record< string, unknown >
438 | ): number | null;
439 |
440 | /**
441 | * Returns the current selection end.
442 | *
443 | * @returns Selection end information.
444 | */
445 | export function getSelectionEnd(
446 | state: Record< string, unknown >
447 | ): Record< string, unknown >;
448 |
449 | /**
450 | * Returns the current selection start.
451 | *
452 | * @returns Selection start information.
453 | */
454 | export function getSelectionStart(
455 | state: Record< string, unknown >
456 | ): Record< string, unknown >;
457 |
458 | /**
459 | * Returns the editor settings.
460 | */
461 | export function getSettings(
462 | state: Record< string, unknown >
463 | ): Record< string, unknown >;
464 |
465 | // FIXME: This is poorly documented. It's not clear what this is.
466 | /**
467 | * Returns the defined block template.
468 | */
469 | export function getTemplate( state: Record< string, unknown > ): any;
470 |
471 | /**
472 | * Returns the defined block template lock. Optionally accepts a root block client ID as context,
473 | * otherwise defaulting to the global context.
474 | *
475 | * @param state
476 | * @param rootClientId - Optional block root client ID.
477 | *
478 | * @returns Block Template Lock
479 | */
480 | export function getTemplateLock(
481 | state: Record< string, unknown >,
482 | rootClientId?: string
483 | ): string | undefined;
484 |
485 | /**
486 | * Determines whether there are items to show in the inserter.
487 | *
488 | * @param state
489 | * @param rootClientId - Optional root client ID of block list.
490 | *
491 | * @returns Items that appear in inserter.
492 | */
493 | export function hasInserterItems(
494 | state: Record< string, unknown >,
495 | rootClientId?: string
496 | ): boolean;
497 |
498 | /**
499 | * Returns `true` if a multi-selection has been made, or `false` otherwise.
500 | *
501 | * @returns Whether multi-selection has been made.
502 | */
503 | export function hasMultiSelection( state: Record< string, unknown > ): boolean;
504 |
505 | /**
506 | * Returns `true` if there is a single selected block, or `false` otherwise.
507 | *
508 | * @returns Whether a single block is selected.
509 | */
510 | export function hasSelectedBlock( state: Record< string, unknown > ): boolean;
511 |
512 | /**
513 | * Returns `true` if one of the block's inner blocks is selected.
514 | *
515 | * @param state
516 | * @param clientId - Block client ID.
517 | * @param deep - Perform a deep check. (default: `true`)
518 | *
519 | * @returns Whether the block as an inner block selected
520 | */
521 | export function hasSelectedInnerBlock(
522 | state: Record< string, unknown >,
523 | clientId: string,
524 | deep?: boolean
525 | ): boolean;
526 |
527 | /**
528 | * Returns `true` if an ancestor of the block is multi-selected, or `false` otherwise.
529 | *
530 | * @param state
531 | * @param clientId - Block client ID.
532 | *
533 | * @returns Whether an ancestor of the block is in multi-selection set.
534 | */
535 | export function isAncestorMultiSelected(
536 | state: Record< string, unknown >,
537 | clientId: string
538 | ): boolean;
539 |
540 | /**
541 | * Returns `true` if we should show the block insertion point.
542 | *
543 | * @returns Whether the insertion point is visible or not.
544 | */
545 | export function isBlockInsertionPointVisible(
546 | state: Record< string, unknown >
547 | ): boolean;
548 |
549 | /**
550 | * Returns `true` if the client ID occurs within the block multi-selection, or `false` otherwise.
551 | *
552 | * @param state
553 | * @param clientId - Block client ID.
554 | *
555 | * @returns Whether block is in multi-selection set.
556 | */
557 | export function isBlockMultiSelected(
558 | state: Record< string, unknown >,
559 | clientId: string
560 | ): boolean;
561 |
562 | /**
563 | * Returns `true` if the block corresponding to the specified client ID is currently selected and no
564 | * multi-selection exists, or `false` otherwise.
565 | *
566 | * @param state
567 | * @param clientId - Block client ID.
568 | *
569 | * @returns Whether block is selected and multi-selection exists.
570 | */
571 | export function isBlockSelected(
572 | state: Record< string, unknown >,
573 | clientId: string
574 | ): boolean;
575 |
576 | /**
577 | * Returns whether a block is valid or not.
578 | *
579 | * @param state
580 | * @param clientId - Block client ID.
581 | *
582 | * @returns Is Valid.
583 | */
584 | export function isBlockValid(
585 | state: Record< string, unknown >,
586 | clientId: string
587 | ): boolean;
588 |
589 | /**
590 | * Returns `true` if the block corresponding to the specified client ID is currently selected but
591 | * isn't the last of the selected blocks. Here "last" refers to the block sequence in the document,
592 | * _not_ the sequence of multi-selection, which is why `state.blockSelection.end` isn't used.
593 | *
594 | * @param state
595 | * @param clientId - Block client ID.
596 | *
597 | * @returns Whether block is selected and not the last in the selection.
598 | */
599 | export function isBlockWithinSelection(
600 | state: Record< string, unknown >,
601 | clientId: string
602 | ): boolean;
603 |
604 | /**
605 | * Returns `true` if the caret is within formatted text, or `false` otherwise.
606 | *
607 | * @returns Whether the caret is within formatted text.
608 | */
609 | export function isCaretWithinFormattedText(
610 | state: Record< string, unknown >
611 | ): boolean;
612 |
613 | /**
614 | * Returns `true` if a multi-selection exists, and the block corresponding to the specified client
615 | * ID is the first block of the multi-selection set, or `false` otherwise.
616 | *
617 | * @param state
618 | * @param clientId - Block client ID.
619 | *
620 | * @returns Whether block is first in multi-selection.
621 | */
622 | export function isFirstMultiSelectedBlock(
623 | state: Record< string, unknown >,
624 | clientId: string
625 | ): boolean;
626 |
627 | /**
628 | * Returns `true` if the most recent block change is be considered persistent, or `false` otherwise.
629 | * A persistent change is one committed by BlockEditorProvider via its `onChange` callback, in
630 | * addition to `onInput`.
631 | *
632 | * @returns Whether the most recent block change was persistent.
633 | */
634 | export function isLastBlockChangePersistent(
635 | state: Record< string, unknown >
636 | ): boolean;
637 |
638 | /**
639 | * Whether in the process of multi-selecting or not. This flag is only `true` while the
640 | * multi-selection is being selected (by mouse move), and is `false` once the multi-selection has
641 | * been settled.
642 | *
643 | * @see hasMultiSelection
644 | *
645 | * @returns `true` if multi-selecting, `false` if not.
646 | */
647 | export function isMultiSelecting( state: Record< string, unknown > ): boolean;
648 |
649 | /**
650 | * Selector that returns if multi-selection is enabled or not.
651 | *
652 | * @returns `true` if it should be possible to multi-select blocks, `false` if multi-selection is
653 | * disabled.
654 | */
655 | export function isSelectionEnabled( state: Record< string, unknown > ): boolean;
656 |
657 | /**
658 | * Returns `true` if the user is typing, or `false` otherwise.
659 | *
660 | * @returns Whether user is typing.
661 | */
662 | export function isTyping( state: Record< string, unknown > ): boolean;
663 |
664 | /**
665 | * Returns whether the blocks matches the template or not.
666 | *
667 | * @returns Whether the template is valid or not.
668 | */
669 | export function isValidTemplate( state: Record< string, unknown > ): boolean;
670 |
--------------------------------------------------------------------------------
/packages/summarize-button/src/@types/wordpress__block-editor/store/selectors.d.ts:
--------------------------------------------------------------------------------
1 | import type { BlockInstance } from '@wordpress/blocks';
2 |
3 | /**
4 | * Determines if the given block type is allowed to be inserted into the block list.
5 | *
6 | * @param state
7 | * @param blockName - The name of the block type, e.g.' core/paragraph'.
8 | * @param rootClientId - Optional root client ID of block list.
9 | *
10 | * @returns Whether the given block type is allowed to be inserted.
11 | */
12 | export function canInsertBlockType(
13 | state: Record< string, unknown >,
14 | blockName: string,
15 | rootClientId?: string
16 | ): boolean;
17 |
18 | /**
19 | * Returns the client ID of the block adjacent one at the given reference `startClientId` and modifier
20 | * directionality. Defaults start `startClientId` to the selected block, and direction as next block.
21 | * Returns `null` if there is no adjacent block.
22 | *
23 | * @param state
24 | * @param startClientId - Optional client ID of block from which to search.
25 | * @param modifier - Directionality multiplier (1 next, -1 previous).
26 | *
27 | * @returns Return the client ID of the block, or null if none exists.
28 | */
29 | export function getAdjacentBlockClientId(
30 | state: Record< string, unknown >,
31 | startClientId?: string,
32 | modifier?: 1 | -1
33 | ): string | null;
34 |
35 | /**
36 | * Returns a block given its client ID. This is a parsed copy of the block, containing its
37 | * `blockName`, `clientId`, and current `attributes` state. This is not the block's registration
38 | * settings, which must be retrieved from the blocks module registration store.
39 | *
40 | * @param state
41 | * @param clientId - Block client ID.
42 | *
43 | * @returns Parsed block object.
44 | */
45 | export function getBlock(
46 | state: Record< string, unknown >,
47 | clientId: string
48 | ): BlockInstance | null;
49 |
50 | /**
51 | * Returns a block's attributes given its client ID, or null if no block exists with the client ID.
52 | *
53 | * @param state
54 | * @param clientId - Block client ID.
55 | *
56 | * @returns Block attributes.
57 | */
58 | export function getBlockAttributes(
59 | state: Record< string, unknown >,
60 | clientId: string
61 | ): Record< string, any > | null;
62 |
63 | /**
64 | * Returns the number of blocks currently present in the post.
65 | *
66 | * @param state
67 | * @param rootClientId - Optional root client ID of block list.
68 | *
69 | * @returns Number of blocks in the post.
70 | */
71 | export function getBlockCount(
72 | state: Record< string, unknown >,
73 | rootClientId?: string
74 | ): number;
75 |
76 | /**
77 | * Given a block client ID, returns the root of the hierarchy from which the block is nested, return
78 | * the block itself for root level blocks.
79 | *
80 | * @param state
81 | * @param clientId - Block from which to find root client ID.
82 | *
83 | * @returns Root client ID
84 | */
85 | export function getBlockHierarchyRootClientId(
86 | state: Record< string, unknown >,
87 | clientId: string
88 | ): string;
89 |
90 | /**
91 | * Returns the index at which the block corresponding to the specified client ID occurs within the
92 | * block order, or `-1` if the block does not exist.
93 | *
94 | * @param state
95 | * @param clientId - Block client ID.
96 | * @param rootClientId - Optional root client ID of block list.
97 | *
98 | * @returns Index at which block exists in order.
99 | */
100 | export function getBlockIndex(
101 | state: Record< string, unknown >,
102 | clientId: string,
103 | rootClientId?: string
104 | ): number;
105 |
106 | /**
107 | * Returns the insertion point, the index at which the new inserted block would
108 | * be placed. Defaults to the last index.
109 | */
110 | export function getBlockInsertionPoint(): {
111 | state: Record< string, unknown >;
112 | index: number;
113 | rootClientId?: string | undefined;
114 | };
115 |
116 | /**
117 | * Returns the Block List settings of a block, if any exist.
118 | *
119 | * @param state
120 | * @param clientId - Block client ID.
121 | *
122 | * @returns Block settings of the block if set.
123 | */
124 | export function getBlockListSettings(
125 | state: Record< string, unknown >,
126 | clientId?: string
127 | ): Record< string, unknown > | undefined;
128 |
129 | /**
130 | * Returns the block's editing mode, defaulting to `"visual"` if not explicitly assigned.
131 | *
132 | * @param state
133 | * @param clientId - Block client ID.
134 | *
135 | * @returns Block editing mode.
136 | */
137 | export function getBlockMode(
138 | state: Record< string, unknown >,
139 | clientId: string
140 | ): string;
141 |
142 | /**
143 | * Returns a block's name given its client ID, or `null` if no block exists with the client ID.
144 | *
145 | * @param state
146 | * @param clientId - Block client ID.
147 | *
148 | * @returns Block name.
149 | */
150 | export function getBlockName(
151 | state: Record< string, unknown >,
152 | clientId: string
153 | ): string | null;
154 |
155 | /**
156 | * Returns an array containing all block client IDs in the editor in the order they appear.
157 | * Optionally accepts a root client ID of the block list for which the order should be returned,
158 | * defaulting to the top-level block order.
159 | *
160 | * @param state
161 | * @param rootClientId - Optional root client ID of block list.
162 | *
163 | * @returns Ordered client IDs of editor blocks.
164 | */
165 | export function getBlockOrder(
166 | state: Record< string, unknown >,
167 | rootClientId?: string
168 | ): string[];
169 |
170 | /**
171 | * Given a block client ID, returns the root block from which the block is nested, an empty string
172 | * for top-level blocks, or `null` if the block does not exist.
173 | *
174 | * @param state
175 | * @param clientId - Block from which to find root client ID.
176 | *
177 | * @returns Root client ID, if exists
178 | */
179 | export function getBlockRootClientId(
180 | state: Record< string, unknown >,
181 | clientId: string
182 | ): string | null;
183 |
184 | /**
185 | * Returns the current block selection end. This value may be `undefined`, and it may represent either a
186 | * singular block selection or multi-selection end. A selection is singular if its start and end
187 | * match.
188 | *
189 | * @returns Client ID of block selection end.
190 | */
191 | export function getBlockSelectionEnd(
192 | state: Record< string, unknown >
193 | ): string | undefined;
194 |
195 | /**
196 | * Returns the current block selection start. This value may be `undefined`, and it may represent
197 | * either a singular block selection or multi-selection start. A selection is singular if its start
198 | * and end match.
199 | *
200 | * @returns Client ID of block selection start.
201 | */
202 | export function getBlockSelectionStart(
203 | state: Record< string, unknown >
204 | ): string | undefined;
205 |
206 | /**
207 | * Returns all block objects for the current post being edited as an array in the order they appear
208 | * in the post.
209 | *
210 | * Note: It's important to memoize this selector to avoid return a new instance
211 | * on each call
212 | *
213 | * @param state
214 | * @param rootClientId - Optional root client ID of block list.
215 | *
216 | * @returns Post blocks.
217 | */
218 | export function getBlocks(
219 | state: Record< string, unknown >,
220 | rootClientId?: string
221 | ): BlockInstance[];
222 |
223 | /**
224 | * Given an array of block client IDs, returns the corresponding array of block objects or `null`.
225 | *
226 | * @param clientIds - Client IDs for which blocks are to be returned.
227 | */
228 | export function getBlocksByClientId(
229 | clientIds: string | string[]
230 | ): Array< BlockInstance | null >;
231 |
232 | /**
233 | * Returns an array containing the clientIds of all descendants of the blocks given.
234 | *
235 | * @param state
236 | * @param clientIds - Array of block ids to inspect.
237 | *
238 | * @returns ids of descendants.
239 | */
240 | export function getClientIdsOfDescendants(
241 | state: Record< string, unknown >,
242 | clientIds: string[]
243 | ): string[];
244 |
245 | /**
246 | * Returns an array containing the `clientIds` of the top-level blocks and their descendants of any
247 | * depth (for nested blocks).
248 | *
249 | * @returns ids of top-level and descendant blocks.
250 | */
251 | export function getClientIdsWithDescendants(
252 | state: Record< string, unknown >
253 | ): string[];
254 |
255 | /**
256 | * Returns the client ID of the first block in the multi-selection set, or `null` if there is no
257 | * multi-selection.
258 | *
259 | * @returns First block client ID in the multi-selection set.
260 | */
261 | export function getFirstMultiSelectedBlockClientId(
262 | state: Record< string, unknown >
263 | ): string | null;
264 |
265 | /**
266 | * Returns the total number of blocks, or the total number of blocks with a specific name in a post.
267 | * The number returned includes nested blocks.
268 | *
269 | * @param state
270 | * @param blockName - Optional block name, if specified only blocks of that type will be counted.
271 | *
272 | * @returns Number of blocks in the post, or number of blocks with name equal to `blockName`.
273 | */
274 | export function getGlobalBlockCount(
275 | state: Record< string, unknown >,
276 | blockName?: string
277 | ): number;
278 |
279 | /**
280 | * Determines the items that appear in the inserter. Includes both static items (e.g. a regular
281 | * block type) and dynamic items (e.g. a reusable block).
282 | *
283 | * @remarks
284 | * Each item object contains what's necessary to display a button in the inserter and handle its
285 | * selection.
286 | *
287 | * The `utility` property indicates how useful we think an item will be to the user. There are 4
288 | * levels of utility:
289 | *
290 | * 1. Blocks that are contextually useful (utility = 3)
291 | * 2. Blocks that have been previously inserted (utility = 2)
292 | * 3. Blocks that are in the common category (utility = 1)
293 | * 4. All other blocks (utility = 0)
294 | *
295 | * The `frecency` property is a heuristic (https://en.wikipedia.org/wiki/Frecency)
296 | * that combines block usage frequenty and recency.
297 | *
298 | * Items are returned ordered descendingly by their `utility` and `frecency`.
299 | *
300 | * @param state
301 | * @param rootClientId - Optional root client ID of block list.
302 | *
303 | * @returns Items that appear in inserter.
304 | */
305 | export function getInserterItems(
306 | state: Record< string, unknown >,
307 | rootClientId?: string
308 | ): Record< string, unknown >[];
309 |
310 | /**
311 | * Returns the client ID of the last block in the multi-selection set, or `null` if there is no
312 | * multi-selection.
313 | *
314 | * @returns Last block client ID in the multi-selection set.
315 | */
316 | export function getLastMultiSelectedBlockClientId(
317 | state: Record< string, unknown >
318 | ): string | null;
319 |
320 | /**
321 | * Returns the current multi-selection set of block client IDs, or an empty array if there is no
322 | * multi-selection.
323 | *
324 | * @returns Multi-selected block client IDs.
325 | */
326 | export function getMultiSelectedBlockClientIds(
327 | state: Record< string, unknown >
328 | ): string[];
329 |
330 | /**
331 | * Returns the current multi-selection set of blocks, or an empty array if there is no
332 | * multi-selection.
333 | *
334 | * @returns Multi-selected block objects.
335 | */
336 | export function getMultiSelectedBlocks(
337 | state: Record< string, unknown >
338 | ): BlockInstance[];
339 |
340 | /**
341 | * Returns the client ID of the block which ends the multi-selection set, or `null` if there is no
342 | * multi-selection.
343 | *
344 | * This is not necessarily the last client ID in the selection.
345 | *
346 | * @see getLastMultiSelectedBlockClientId
347 | *
348 | * @returns Client ID of block ending multi-selection.
349 | */
350 | export function getMultiSelectedBlocksEndClientId(
351 | state: Record< string, unknown >
352 | ): string | null;
353 |
354 | /**
355 | * Returns the client ID of the block which begins the multi-selection set, or `null` if there is no
356 | * multi-selection.
357 | *
358 | * This is not necessarily the first client ID in the selection.
359 | *
360 | * @see getFirstMultiSelectedBlockClientId
361 | *
362 | * @returns Client ID of block beginning multi-selection.
363 | */
364 | export function getMultiSelectedBlocksStartClientId(
365 | state: Record< string, unknown >
366 | ): string | null;
367 |
368 | /**
369 | * Returns the next block's client ID from the given reference start ID. Defaults start to the
370 | * selected block. Returns `null` if there is no next block.
371 | *
372 | * @param state
373 | * @param startClientId - Optional client ID of block from which to search.
374 | *
375 | * @returns Adjacent block's client ID, or `null` if none exists.
376 | */
377 | export function getNextBlockClientId(
378 | state: Record< string, unknown >,
379 | startClientId?: string
380 | ): string | null;
381 |
382 | /**
383 | * Returns the previous block's client ID from the given reference start ID. Defaults start to the
384 | * selected block. Returns `null` if there is no previous block.
385 | *
386 | * @param state
387 | * @param startClientId - Optional client ID of block from which to search.
388 | *
389 | * @returns Adjacent block's client ID, or `null` if none exists.
390 | */
391 | export function getPreviousBlockClientId(
392 | state: Record< string, unknown >,
393 | startClientId?: string
394 | ): string | null;
395 |
396 | /**
397 | * Returns the currently selected block, or `null` if there is no selected block.
398 | *
399 | * @returns Selected block.
400 | */
401 | export function getSelectedBlock(
402 | state: Record< string, unknown >
403 | ): BlockInstance | null;
404 |
405 | /**
406 | * Returns the currently selected block client ID, or `null` if there is no selected block.
407 | *
408 | * @returns Selected block client ID.
409 | */
410 | export function getSelectedBlockClientId(
411 | state: Record< string, unknown >
412 | ): string | null;
413 |
414 | /**
415 | * Returns the current selection set of block client IDs (multiselection or single selection).
416 | *
417 | * @returns Multi-selected block client IDs.
418 | */
419 | export function getSelectedBlockClientIds(
420 | state: Record< string, unknown >
421 | ): string[];
422 |
423 | /**
424 | * Returns the number of blocks currently selected in the post.
425 | *
426 | * @returns Number of blocks selected in the post.
427 | */
428 | export function getSelectedBlockCount(
429 | state: Record< string, unknown >
430 | ): number;
431 |
432 | /**
433 | * Returns the initial caret position for the selected block. This position is to used to position
434 | * the caret properly when the selected block changes.
435 | */
436 | export function getSelectedBlocksInitialCaretPosition(
437 | state: Record< string, unknown >
438 | ): number | null;
439 |
440 | /**
441 | * Returns the current selection end.
442 | *
443 | * @returns Selection end information.
444 | */
445 | export function getSelectionEnd(
446 | state: Record< string, unknown >
447 | ): Record< string, unknown >;
448 |
449 | /**
450 | * Returns the current selection start.
451 | *
452 | * @returns Selection start information.
453 | */
454 | export function getSelectionStart(
455 | state: Record< string, unknown >
456 | ): Record< string, unknown >;
457 |
458 | /**
459 | * Returns the editor settings.
460 | */
461 | export function getSettings(
462 | state: Record< string, unknown >
463 | ): Record< string, unknown >;
464 |
465 | // FIXME: This is poorly documented. It's not clear what this is.
466 | /**
467 | * Returns the defined block template.
468 | */
469 | export function getTemplate( state: Record< string, unknown > ): any;
470 |
471 | /**
472 | * Returns the defined block template lock. Optionally accepts a root block client ID as context,
473 | * otherwise defaulting to the global context.
474 | *
475 | * @param state
476 | * @param rootClientId - Optional block root client ID.
477 | *
478 | * @returns Block Template Lock
479 | */
480 | export function getTemplateLock(
481 | state: Record< string, unknown >,
482 | rootClientId?: string
483 | ): string | undefined;
484 |
485 | /**
486 | * Determines whether there are items to show in the inserter.
487 | *
488 | * @param state
489 | * @param rootClientId - Optional root client ID of block list.
490 | *
491 | * @returns Items that appear in inserter.
492 | */
493 | export function hasInserterItems(
494 | state: Record< string, unknown >,
495 | rootClientId?: string
496 | ): boolean;
497 |
498 | /**
499 | * Returns `true` if a multi-selection has been made, or `false` otherwise.
500 | *
501 | * @returns Whether multi-selection has been made.
502 | */
503 | export function hasMultiSelection( state: Record< string, unknown > ): boolean;
504 |
505 | /**
506 | * Returns `true` if there is a single selected block, or `false` otherwise.
507 | *
508 | * @returns Whether a single block is selected.
509 | */
510 | export function hasSelectedBlock( state: Record< string, unknown > ): boolean;
511 |
512 | /**
513 | * Returns `true` if one of the block's inner blocks is selected.
514 | *
515 | * @param state
516 | * @param clientId - Block client ID.
517 | * @param deep - Perform a deep check. (default: `true`)
518 | *
519 | * @returns Whether the block as an inner block selected
520 | */
521 | export function hasSelectedInnerBlock(
522 | state: Record< string, unknown >,
523 | clientId: string,
524 | deep?: boolean
525 | ): boolean;
526 |
527 | /**
528 | * Returns `true` if an ancestor of the block is multi-selected, or `false` otherwise.
529 | *
530 | * @param state
531 | * @param clientId - Block client ID.
532 | *
533 | * @returns Whether an ancestor of the block is in multi-selection set.
534 | */
535 | export function isAncestorMultiSelected(
536 | state: Record< string, unknown >,
537 | clientId: string
538 | ): boolean;
539 |
540 | /**
541 | * Returns `true` if we should show the block insertion point.
542 | *
543 | * @returns Whether the insertion point is visible or not.
544 | */
545 | export function isBlockInsertionPointVisible(
546 | state: Record< string, unknown >
547 | ): boolean;
548 |
549 | /**
550 | * Returns `true` if the client ID occurs within the block multi-selection, or `false` otherwise.
551 | *
552 | * @param state
553 | * @param clientId - Block client ID.
554 | *
555 | * @returns Whether block is in multi-selection set.
556 | */
557 | export function isBlockMultiSelected(
558 | state: Record< string, unknown >,
559 | clientId: string
560 | ): boolean;
561 |
562 | /**
563 | * Returns `true` if the block corresponding to the specified client ID is currently selected and no
564 | * multi-selection exists, or `false` otherwise.
565 | *
566 | * @param state
567 | * @param clientId - Block client ID.
568 | *
569 | * @returns Whether block is selected and multi-selection exists.
570 | */
571 | export function isBlockSelected(
572 | state: Record< string, unknown >,
573 | clientId: string
574 | ): boolean;
575 |
576 | /**
577 | * Returns whether a block is valid or not.
578 | *
579 | * @param state
580 | * @param clientId - Block client ID.
581 | *
582 | * @returns Is Valid.
583 | */
584 | export function isBlockValid(
585 | state: Record< string, unknown >,
586 | clientId: string
587 | ): boolean;
588 |
589 | /**
590 | * Returns `true` if the block corresponding to the specified client ID is currently selected but
591 | * isn't the last of the selected blocks. Here "last" refers to the block sequence in the document,
592 | * _not_ the sequence of multi-selection, which is why `state.blockSelection.end` isn't used.
593 | *
594 | * @param state
595 | * @param clientId - Block client ID.
596 | *
597 | * @returns Whether block is selected and not the last in the selection.
598 | */
599 | export function isBlockWithinSelection(
600 | state: Record< string, unknown >,
601 | clientId: string
602 | ): boolean;
603 |
604 | /**
605 | * Returns `true` if the caret is within formatted text, or `false` otherwise.
606 | *
607 | * @returns Whether the caret is within formatted text.
608 | */
609 | export function isCaretWithinFormattedText(
610 | state: Record< string, unknown >
611 | ): boolean;
612 |
613 | /**
614 | * Returns `true` if a multi-selection exists, and the block corresponding to the specified client
615 | * ID is the first block of the multi-selection set, or `false` otherwise.
616 | *
617 | * @param state
618 | * @param clientId - Block client ID.
619 | *
620 | * @returns Whether block is first in multi-selection.
621 | */
622 | export function isFirstMultiSelectedBlock(
623 | state: Record< string, unknown >,
624 | clientId: string
625 | ): boolean;
626 |
627 | /**
628 | * Returns `true` if the most recent block change is be considered persistent, or `false` otherwise.
629 | * A persistent change is one committed by BlockEditorProvider via its `onChange` callback, in
630 | * addition to `onInput`.
631 | *
632 | * @returns Whether the most recent block change was persistent.
633 | */
634 | export function isLastBlockChangePersistent(
635 | state: Record< string, unknown >
636 | ): boolean;
637 |
638 | /**
639 | * Whether in the process of multi-selecting or not. This flag is only `true` while the
640 | * multi-selection is being selected (by mouse move), and is `false` once the multi-selection has
641 | * been settled.
642 | *
643 | * @see hasMultiSelection
644 | *
645 | * @returns `true` if multi-selecting, `false` if not.
646 | */
647 | export function isMultiSelecting( state: Record< string, unknown > ): boolean;
648 |
649 | /**
650 | * Selector that returns if multi-selection is enabled or not.
651 | *
652 | * @returns `true` if it should be possible to multi-select blocks, `false` if multi-selection is
653 | * disabled.
654 | */
655 | export function isSelectionEnabled( state: Record< string, unknown > ): boolean;
656 |
657 | /**
658 | * Returns `true` if the user is typing, or `false` otherwise.
659 | *
660 | * @returns Whether user is typing.
661 | */
662 | export function isTyping( state: Record< string, unknown > ): boolean;
663 |
664 | /**
665 | * Returns whether the blocks matches the template or not.
666 | *
667 | * @returns Whether the template is valid or not.
668 | */
669 | export function isValidTemplate( state: Record< string, unknown > ): boolean;
670 |
--------------------------------------------------------------------------------