diff --git a/src/popup/components/content/index.js b/src/popup/components/content/index.js
new file mode 100644
index 0000000..59c36ca
--- /dev/null
+++ b/src/popup/components/content/index.js
@@ -0,0 +1,170 @@
+import './style.scss';
+
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useRef, useEffect, RawHTML } from '@wordpress/element';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { Button } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import LoadingText from '../loading-text';
+import Notice from '../notice';
+import { ReactComponent as PopupPostTitleAboutIcon } from '../../../icons/popup-post-title-about.svg';
+import { ReactComponent as PopupPostAboutIcon } from '../../../icons/popup-post-about.svg';
+import { ReactComponent as PopupOutlineAboutIcon } from '../../../icons/popup-outline-about.svg';
+import { ReactComponent as PopupParagraphAboutIcon } from '../../../icons/popup-paragraph-about.svg';
+import { ReactComponent as PopupListAboutIcon } from '../../../icons/popup-list-about.svg';
+import { ReactComponent as PopupTableAboutIcon } from '../../../icons/popup-table-about.svg';
+
+const commands = [
+ {
+ type: 'category',
+ label: __('Post Presets', 'mind'),
+ },
+ {
+ type: 'request',
+ label: __('Post title about…', 'mind'),
+ request: __('Write a post title about ', 'mind'),
+ icon: ,
+ },
+ {
+ type: 'request',
+ label: __('Post about…', 'mind'),
+ request: __('Write a blog post about ', 'mind'),
+ icon: ,
+ },
+ {
+ type: 'request',
+ label: __('Outline about…', 'mind'),
+ request: __('Write a blog post outline about ', 'mind'),
+ icon: ,
+ },
+
+ {
+ type: 'category',
+ label: __('Content Presets', 'mind'),
+ },
+ {
+ type: 'request',
+ label: __('Paragraph about…', 'mind'),
+ request: __('Create a paragraph about ', 'mind'),
+ icon: ,
+ },
+ {
+ type: 'request',
+ label: __('List about…', 'mind'),
+ request: __('Create a list about ', 'mind'),
+ icon: ,
+ },
+ {
+ type: 'request',
+ label: __('Table about…', 'mind'),
+ request: __('Create a table about ', 'mind'),
+ icon: ,
+ },
+];
+
+export default function Content() {
+ const ref = useRef();
+
+ const { setInput, setScreen } = useDispatch('mind/popup');
+
+ const { isOpen, input, screen, loading, response, error } = useSelect(
+ (select) => {
+ const {
+ isOpen: checkIsOpen,
+ getInput,
+ getContext,
+ getScreen,
+ getLoading,
+ getResponse,
+ getError,
+ } = select('mind/popup');
+
+ return {
+ isOpen: checkIsOpen(),
+ input: getInput(),
+ context: getContext(),
+ screen: getScreen(),
+ loading: getLoading(),
+ response: getResponse(),
+ error: getError(),
+ };
+ }
+ );
+
+ function focusInput() {
+ if (ref?.current) {
+ const inputEl = ref.current.querySelector('input');
+
+ if (inputEl) {
+ inputEl.focus();
+ }
+ }
+ }
+
+ // Set focus on Input.
+ useEffect(() => {
+ if (isOpen && !loading && ref?.current) {
+ focusInput();
+ }
+ }, [isOpen, loading, ref]);
+
+ // Open request page if something is in input.
+ useEffect(() => {
+ if (screen === '' && input) {
+ setScreen('request');
+ }
+ }, [screen, input, setScreen]);
+
+ return (
+
+ {screen === '' ? (
+
+ {commands.map((data) => {
+ if (data.type === 'category') {
+ return (
+
+ {data.label}
+
+ );
+ }
+
+ return (
+
+ );
+ })}
+
+ ) : null}
+
+ {screen === 'request' && (
+
+ {loading && (
+
+ {__('Waiting for AI response', 'mind')}
+
+ )}
+ {!loading && response && {response}}
+ {!loading && error && {error}}
+
+ )}
+
+ );
+}
diff --git a/src/popup/components/content/style.scss b/src/popup/components/content/style.scss
new file mode 100644
index 0000000..095910c
--- /dev/null
+++ b/src/popup/components/content/style.scss
@@ -0,0 +1,82 @@
+$padding: 10px;
+
+.mind-popup-content {
+ overflow: auto;
+ flex: 1;
+ padding: $padding $padding * 2;
+
+ &:empty,
+ &:has(.mind-popup-request:empty) {
+ padding: 0;
+ margin-bottom: -1px;
+ }
+
+ .mind-popup-request {
+ ol,
+ ul {
+ list-style: auto;
+ padding-left: 15px;
+ }
+
+ table {
+ border-collapse: collapse;
+ width: 100%;
+
+ td,
+ th {
+ border: 1px solid;
+ padding: 0.5em;
+ white-space: pre-wrap;
+ min-width: 1px;
+ }
+ }
+ }
+}
+
+// Prompts.
+.mind-popup-commands {
+ display: flex;
+ flex-direction: column;
+ margin-left: -$padding;
+ margin-right: -$padding;
+
+ .mind-popup-commands-category {
+ position: relative;
+ padding: $padding;
+ padding-top: 28px;
+ color: #7f7f7f;
+
+ &:first-child {
+ padding-top: $padding;
+ }
+
+ &:not(:first-child)::before {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 8px;
+ left: -10px;
+ right: -10px;
+ border-top: 1px solid #e8e7e7;
+ }
+ }
+
+ .mind-popup-commands-button {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ background-color: transparent;
+ color: #000;
+ border: none;
+ text-align: left;
+ padding: $padding;
+ border-radius: 5px;
+ height: auto;
+ min-height: 40px;
+
+ &:hover,
+ &:focus {
+ background-color: rgba(#000, 5%);
+ }
+ }
+}
diff --git a/src/popup/components/footer/index.js b/src/popup/components/footer/index.js
new file mode 100644
index 0000000..da4b772
--- /dev/null
+++ b/src/popup/components/footer/index.js
@@ -0,0 +1,75 @@
+import './style.scss';
+
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { Button } from '@wordpress/components';
+import { useSelect, useDispatch } from '@wordpress/data';
+
+export default function Input(props) {
+ const { onInsert } = props;
+
+ const { close, reset, setError, requestAI } = useDispatch('mind/popup');
+
+ const { input, loading, response } = useSelect((select) => {
+ const { getInput, getContext, getScreen, getLoading, getResponse } =
+ select('mind/popup');
+
+ return {
+ input: getInput(),
+ context: getContext(),
+ screen: getScreen(),
+ loading: getLoading(),
+ response: getResponse(),
+ };
+ });
+
+ const showFooter = response || (input && !loading && !response);
+
+ if (!showFooter) {
+ return null;
+ }
+
+ return (
+
+
+ {input && !loading && !response && (
+
+ )}
+ {response && (
+ <>
+
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/src/popup/components/footer/style.scss b/src/popup/components/footer/style.scss
new file mode 100644
index 0000000..2bd1925
--- /dev/null
+++ b/src/popup/components/footer/style.scss
@@ -0,0 +1,40 @@
+$padding: 10px;
+
+.mind-popup-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: $padding $padding * 2;
+ background-color: #f9f9f9;
+ border-top: 1px solid #e8e7e7;
+}
+.mind-popup-footer-actions {
+ display: flex;
+ margin: -5px -15px;
+ margin-left: auto;
+ gap: 5px;
+
+ button {
+ display: flex;
+ gap: 8px;
+ height: 28px;
+ border-radius: 5px;
+ font-weight: 500;
+
+ &:hover,
+ &:focus {
+ background-color: rgba(0, 0, 0, 5%);
+ color: #121212;
+ }
+
+ kbd {
+ font: inherit;
+ font-weight: 400;
+ border-radius: 3px;
+ padding: 3px 4px;
+ margin-right: -8px;
+ color: rgba(0, 0, 0, 50%);
+ background-color: rgba(0, 0, 0, 8%);
+ }
+ }
+}
diff --git a/src/popup/components/input/index.js b/src/popup/components/input/index.js
new file mode 100644
index 0000000..400ba99
--- /dev/null
+++ b/src/popup/components/input/index.js
@@ -0,0 +1,117 @@
+import './style.scss';
+
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useRef, useEffect } from '@wordpress/element';
+import { TextControl } from '@wordpress/components';
+import { useSelect, useDispatch } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import TOOLBAR_ICON from '../../../utils/icon';
+
+export default function Input(props) {
+ const { onInsert } = props;
+
+ const ref = useRef();
+
+ const { reset, setInput, setScreen, requestAI } = useDispatch('mind/popup');
+
+ const { isOpen, input, context, screen, loading, response } = useSelect(
+ (select) => {
+ const {
+ isOpen: checkIsOpen,
+ getInput,
+ getContext,
+ getScreen,
+ getLoading,
+ getResponse,
+ } = select('mind/popup');
+
+ return {
+ isOpen: checkIsOpen(),
+ input: getInput(),
+ context: getContext(),
+ screen: getScreen(),
+ loading: getLoading(),
+ response: getResponse(),
+ };
+ }
+ );
+
+ let contextLabel = context;
+
+ switch (context) {
+ case 'selected-blocks':
+ contextLabel = __('Selected Blocks');
+ break;
+ case 'post-title':
+ contextLabel = __('Post Title');
+ break;
+ // no default
+ }
+
+ function onKeyDown(e) {
+ // Go back to starter screen.
+ if (screen !== '' && e.key === 'Backspace' && !e.target.value) {
+ reset();
+ return;
+ }
+
+ // Insert request to post.
+ if (response && e.key === 'Enter') {
+ onInsert();
+ return;
+ }
+
+ // Send request to AI.
+ if (screen === 'request' && e.key === 'Enter') {
+ requestAI();
+ }
+ }
+
+ function focusInput() {
+ if (ref?.current) {
+ const inputEl = ref.current.querySelector('input');
+
+ if (inputEl) {
+ inputEl.focus();
+ }
+ }
+ }
+
+ // Set focus on Input.
+ useEffect(() => {
+ if (isOpen && !loading && ref?.current) {
+ focusInput();
+ }
+ }, [isOpen, loading, ref]);
+
+ // Open request page if something is in input.
+ useEffect(() => {
+ if (screen === '' && input) {
+ setScreen('request');
+ }
+ }, [screen, input, setScreen]);
+
+ return (
+
+ {TOOLBAR_ICON}
+ {
+ setInput(val);
+ }}
+ onKeyDown={onKeyDown}
+ disabled={loading}
+ />
+ {contextLabel ? (
+ {contextLabel}
+ ) : null}
+
+ );
+}
diff --git a/src/popup/components/input/style.scss b/src/popup/components/input/style.scss
new file mode 100644
index 0000000..9994137
--- /dev/null
+++ b/src/popup/components/input/style.scss
@@ -0,0 +1,43 @@
+$padding: 10px;
+
+.mind-popup-input {
+ display: flex;
+ align-items: center;
+ margin-bottom: 0;
+ border-bottom: 1px solid #e8e7e7;
+
+ > svg {
+ position: absolute;
+ left: $padding * 2;
+ pointer-events: none;
+ }
+
+ > .components-base-control {
+ flex: 1;
+ }
+
+ .components-base-control__field {
+ margin-bottom: 0;
+ }
+ .components-base-control__field input {
+ border: none !important;
+ box-shadow: none !important;
+ padding: $padding * 2;
+ padding-left: $padding * 5;
+ font-size: 1.15em;
+ color: #151515;
+
+ &::placeholder {
+ color: #a3a3a3;
+ }
+ }
+
+ .mind-popup-input-context {
+ font-weight: 400;
+ border-radius: 3px;
+ padding: 3px 10px;
+ margin-right: 10px;
+ color: rgb(0, 0, 0, 60%);
+ background-color: rgba(0, 0, 0, 8%);
+ }
+}
diff --git a/src/popup/index.js b/src/popup/index.js
index 710ac6c..f9dcd7b 100644
--- a/src/popup/index.js
+++ b/src/popup/index.js
@@ -6,9 +6,8 @@ import './style.scss';
/**
* WordPress dependencies
*/
-import { __ } from '@wordpress/i18n';
-import { createRoot, useRef, useEffect, RawHTML } from '@wordpress/element';
-import { Modal, Button, TextControl } from '@wordpress/components';
+import { createRoot } from '@wordpress/element';
+import { Modal } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
import { rawHandler } from '@wordpress/blocks';
import domReady from '@wordpress/dom-ready';
@@ -16,110 +15,35 @@ import domReady from '@wordpress/dom-ready';
/**
* Internal dependencies
*/
-import TOOLBAR_ICON from '../utils/icon';
+import Input from './components/input';
import LoadingLine from './components/loading-line';
-import LoadingText from './components/loading-text';
-import Notice from './components/notice';
-
-import { ReactComponent as PopupPostTitleAboutIcon } from '../icons/popup-post-title-about.svg';
-import { ReactComponent as PopupPostAboutIcon } from '../icons/popup-post-about.svg';
-import { ReactComponent as PopupOutlineAboutIcon } from '../icons/popup-outline-about.svg';
-import { ReactComponent as PopupParagraphAboutIcon } from '../icons/popup-paragraph-about.svg';
-import { ReactComponent as PopupListAboutIcon } from '../icons/popup-list-about.svg';
-import { ReactComponent as PopupTableAboutIcon } from '../icons/popup-table-about.svg';
+import Content from './components/content';
+import Footer from './components/footer';
const POPUP_CONTAINER_CLASS = 'mind-popup-container';
-const commands = [
- {
- type: 'category',
- label: __('Post Presets', 'mind'),
- },
- {
- type: 'request',
- label: __('Post title about…', 'mind'),
- request: __('Write a post title about ', 'mind'),
- icon: ,
- },
- {
- type: 'request',
- label: __('Post about…', 'mind'),
- request: __('Write a blog post about ', 'mind'),
- icon: ,
- },
- {
- type: 'request',
- label: __('Outline about…', 'mind'),
- request: __('Write a blog post outline about ', 'mind'),
- icon: ,
- },
-
- {
- type: 'category',
- label: __('Content Presets', 'mind'),
- },
- {
- type: 'request',
- label: __('Paragraph about…', 'mind'),
- request: __('Create a paragraph about ', 'mind'),
- icon: ,
- },
- {
- type: 'request',
- label: __('List about…', 'mind'),
- request: __('Create a list about ', 'mind'),
- icon: ,
- },
- {
- type: 'request',
- label: __('Table about…', 'mind'),
- request: __('Create a table about ', 'mind'),
- icon: ,
- },
-];
-
-export default function Popup(props) {
- const { onClose } = props;
-
- const ref = useRef();
-
+export default function Popup() {
const { setHighlightBlocks } = useDispatch('mind/blocks');
- const { close, reset, setInput, setScreen, setError, requestAI } =
- useDispatch('mind/popup');
+ const { close, reset } = useDispatch('mind/popup');
- const {
- isOpen,
- input,
- context,
- insertionPlace,
- screen,
- loading,
- response,
- error,
- } = useSelect((select) => {
- const {
- isOpen: checkIsOpen,
- getInput,
- getContext,
- getInsertionPlace,
- getScreen,
- getLoading,
- getResponse,
- getError,
- } = select('mind/popup');
+ const { isOpen, insertionPlace, loading, response } = useSelect(
+ (select) => {
+ const {
+ isOpen: checkIsOpen,
+ getInsertionPlace,
+ getLoading,
+ getResponse,
+ } = select('mind/popup');
- return {
- isOpen: checkIsOpen(),
- input: getInput(),
- context: getContext(),
- insertionPlace: getInsertionPlace(),
- screen: getScreen(),
- loading: getLoading(),
- response: getResponse(),
- error: getError(),
- };
- });
+ return {
+ isOpen: checkIsOpen(),
+ insertionPlace: getInsertionPlace(),
+ loading: getLoading(),
+ response: getResponse(),
+ };
+ }
+ );
const { selectedClientIds } = useSelect((select) => {
const { getSelectedBlockClientIds } = select('core/block-editor');
@@ -131,36 +55,8 @@ export default function Popup(props) {
};
}, []);
- let contextLabel = context;
-
- switch (context) {
- case 'selected-blocks':
- contextLabel = __('Selected Blocks');
- break;
- case 'post-title':
- contextLabel = __('Post Title');
- break;
- // no default
- }
-
const { insertBlocks, replaceBlocks } = useDispatch('core/block-editor');
- function focusInput() {
- if (ref?.current) {
- const inputEl = ref.current.querySelector(
- '.mind-popup-input input'
- );
-
- if (inputEl) {
- inputEl.focus();
- }
- }
- }
-
- function copyToClipboard() {
- window.navigator.clipboard.writeText(response);
- }
-
function insertResponse() {
const parsedBlocks = rawHandler({ HTML: response });
@@ -184,177 +80,27 @@ export default function Popup(props) {
reset();
close();
-
- if (onClose) {
- onClose();
- }
- }
-
- // Set focus on Input.
- useEffect(() => {
- if (isOpen && !loading && ref?.current) {
- focusInput();
- }
- }, [isOpen, loading, ref]);
-
- // Open request page if something is in input.
- useEffect(() => {
- if (screen === '' && input) {
- setScreen('request');
- }
- }, [screen, input, setScreen]);
-
- function onKeyDown(e) {
- // Go back to starter screen.
- if (screen !== '' && e.key === 'Backspace' && !e.target.value) {
- reset();
- return;
- }
-
- // Insert request to post.
- if (response && e.key === 'Enter') {
- onInsert();
- return;
- }
-
- // Send request to AI.
- if (screen === 'request' && e.key === 'Enter') {
- requestAI();
- }
}
if (!isOpen) {
return null;
}
- const showFooter = response || (input && !loading && !response);
-
return (
{
reset();
close();
-
- if (onClose) {
- onClose();
- }
}}
__experimentalHideHeader
>
-
- {TOOLBAR_ICON}
- {
- setInput(val);
- }}
- onKeyDown={onKeyDown}
- disabled={loading}
- />
- {contextLabel ? (
-
- {contextLabel}
-
- ) : (
- ''
- )}
-
+
{loading && }
-
- {screen === '' ? (
-
- {commands.map((data) => {
- if (data.type === 'category') {
- return (
-
- {data.label}
-
- );
- }
-
- return (
-
- );
- })}
-
- ) : null}
-
- {screen === 'request' && (
-
- {loading && (
-
- {__('Waiting for AI response', 'mind')}
-
- )}
- {!loading && response && {response}}
- {!loading && error && (
- {error}
- )}
-
- )}
-
- {showFooter && (
-
-
- {input && !loading && !response && (
-
- )}
- {response && (
- <>
-
-
-
- >
- )}
-
-
- )}
+
+
);
}
diff --git a/src/popup/style.scss b/src/popup/style.scss
index a2ab44d..6105b3f 100644
--- a/src/popup/style.scss
+++ b/src/popup/style.scss
@@ -25,165 +25,3 @@ $padding: 10px;
// Upper then dropdown.
z-index: 1000001;
}
-
-.mind-popup-content {
- overflow: auto;
- flex: 1;
- padding: $padding $padding * 2;
-
- &:empty,
- &:has(.mind-popup-request:empty) {
- padding: 0;
- margin-bottom: -1px;
- }
-
- .mind-popup-request {
- ol,
- ul {
- list-style: auto;
- padding-left: 15px;
- }
-
- table {
- border-collapse: collapse;
- width: 100%;
-
- td,
- th {
- border: 1px solid;
- padding: 0.5em;
- white-space: pre-wrap;
- min-width: 1px;
- }
- }
- }
-}
-.mind-popup-footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: $padding $padding * 2;
- background-color: #f9f9f9;
- border-top: 1px solid #e8e7e7;
-}
-.mind-popup-footer-actions {
- display: flex;
- margin: -5px -15px;
- margin-left: auto;
- gap: 5px;
-
- button {
- display: flex;
- gap: 8px;
- height: 28px;
- border-radius: 5px;
- font-weight: 500;
-
- &:hover,
- &:focus {
- background-color: rgba(0, 0, 0, 5%);
- color: #121212;
- }
-
- kbd {
- font: inherit;
- font-weight: 400;
- border-radius: 3px;
- padding: 3px 4px;
- margin-right: -8px;
- color: rgba(0, 0, 0, 50%);
- background-color: rgba(0, 0, 0, 8%);
- }
- }
-}
-
-// Input.
-.mind-popup-input {
- display: flex;
- align-items: center;
- margin-bottom: 0;
- border-bottom: 1px solid #e8e7e7;
-
- > svg {
- position: absolute;
- left: $padding * 2;
- pointer-events: none;
- }
-
- > .components-base-control {
- flex: 1;
- }
-
- .components-base-control__field {
- margin-bottom: 0;
- }
- .components-base-control__field input {
- border: none !important;
- box-shadow: none !important;
- padding: $padding * 2;
- padding-left: $padding * 5;
- font-size: 1.15em;
- color: #151515;
-
- &::placeholder {
- color: #a3a3a3;
- }
- }
-
- .mind-popup-input-context {
- font-weight: 400;
- border-radius: 3px;
- padding: 3px 10px;
- margin-right: 10px;
- color: rgb(0, 0, 0, 60%);
- background-color: rgba(0, 0, 0, 8%);
- }
-}
-
-// Prompts.
-.mind-popup-commands {
- display: flex;
- flex-direction: column;
- margin-left: -$padding;
- margin-right: -$padding;
-
- .mind-popup-commands-category {
- position: relative;
- padding: $padding;
- padding-top: 28px;
- color: #7f7f7f;
-
- &:first-child {
- padding-top: $padding;
- }
-
- &:not(:first-child)::before {
- content: "";
- display: block;
- position: absolute;
- top: 8px;
- left: -10px;
- right: -10px;
- border-top: 1px solid #e8e7e7;
- }
- }
-
- .mind-popup-commands-button {
- display: flex;
- align-items: center;
- gap: 10px;
- background-color: transparent;
- color: #000;
- border: none;
- text-align: left;
- padding: $padding;
- border-radius: 5px;
- height: auto;
- min-height: 40px;
-
- &:hover,
- &:focus {
- background-color: rgba(#000, 5%);
- }
- }
-}