mirror of
https://github.com/WenPai-org/wpmind.git
synced 2025-08-08 06:56:05 +08:00
split popup to multiple components
This commit is contained in:
parent
40c3b76f7b
commit
7e536239b0
8 changed files with 553 additions and 442 deletions
170
src/popup/components/content/index.js
Normal file
170
src/popup/components/content/index.js
Normal file
|
@ -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: <PopupPostTitleAboutIcon />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
label: __('Post about…', 'mind'),
|
||||||
|
request: __('Write a blog post about ', 'mind'),
|
||||||
|
icon: <PopupPostAboutIcon />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
label: __('Outline about…', 'mind'),
|
||||||
|
request: __('Write a blog post outline about ', 'mind'),
|
||||||
|
icon: <PopupOutlineAboutIcon />,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: __('Content Presets', 'mind'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
label: __('Paragraph about…', 'mind'),
|
||||||
|
request: __('Create a paragraph about ', 'mind'),
|
||||||
|
icon: <PopupParagraphAboutIcon />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
label: __('List about…', 'mind'),
|
||||||
|
request: __('Create a list about ', 'mind'),
|
||||||
|
icon: <PopupListAboutIcon />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'request',
|
||||||
|
label: __('Table about…', 'mind'),
|
||||||
|
request: __('Create a table about ', 'mind'),
|
||||||
|
icon: <PopupTableAboutIcon />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="mind-popup-content">
|
||||||
|
{screen === '' ? (
|
||||||
|
<div className="mind-popup-commands">
|
||||||
|
{commands.map((data) => {
|
||||||
|
if (data.type === 'category') {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={data.type + data.label}
|
||||||
|
className="mind-popup-commands-category"
|
||||||
|
>
|
||||||
|
{data.label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={data.type + data.label}
|
||||||
|
className="mind-popup-commands-button"
|
||||||
|
onClick={() => {
|
||||||
|
setInput(data.request);
|
||||||
|
setScreen('request');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.icon || ''}
|
||||||
|
{data.label}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{screen === 'request' && (
|
||||||
|
<div className="mind-popup-request">
|
||||||
|
{loading && (
|
||||||
|
<LoadingText>
|
||||||
|
{__('Waiting for AI response', 'mind')}
|
||||||
|
</LoadingText>
|
||||||
|
)}
|
||||||
|
{!loading && response && <RawHTML>{response}</RawHTML>}
|
||||||
|
{!loading && error && <Notice type="error">{error}</Notice>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
82
src/popup/components/content/style.scss
Normal file
82
src/popup/components/content/style.scss
Normal file
|
@ -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%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/popup/components/footer/index.js
Normal file
75
src/popup/components/footer/index.js
Normal file
|
@ -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 (
|
||||||
|
<div className="mind-popup-footer">
|
||||||
|
<div className="mind-popup-footer-actions">
|
||||||
|
{input && !loading && !response && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
requestAI();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{__('Get Answer', 'mind')} <kbd>⏎</kbd>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{response && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setError('');
|
||||||
|
requestAI();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{__('Regenerate', 'mind')} <kbd>↻</kbd>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
// Copy to clipboard.
|
||||||
|
window.navigator.clipboard.writeText(response);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{__('Copy', 'mind')}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onInsert}>
|
||||||
|
{__('Insert', 'mind')} <kbd>⏎</kbd>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
40
src/popup/components/footer/style.scss
Normal file
40
src/popup/components/footer/style.scss
Normal file
|
@ -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%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
117
src/popup/components/input/index.js
Normal file
117
src/popup/components/input/index.js
Normal file
|
@ -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 (
|
||||||
|
<div className="mind-popup-input" ref={ref}>
|
||||||
|
{TOOLBAR_ICON}
|
||||||
|
<TextControl
|
||||||
|
placeholder={__('Ask AI to write anything…', 'mind')}
|
||||||
|
value={input}
|
||||||
|
onChange={(val) => {
|
||||||
|
setInput(val);
|
||||||
|
}}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
{contextLabel ? (
|
||||||
|
<span className="mind-popup-input-context">{contextLabel}</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
43
src/popup/components/input/style.scss
Normal file
43
src/popup/components/input/style.scss
Normal file
|
@ -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%);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,8 @@ import './style.scss';
|
||||||
/**
|
/**
|
||||||
* WordPress dependencies
|
* WordPress dependencies
|
||||||
*/
|
*/
|
||||||
import { __ } from '@wordpress/i18n';
|
import { createRoot } from '@wordpress/element';
|
||||||
import { createRoot, useRef, useEffect, RawHTML } from '@wordpress/element';
|
import { Modal } from '@wordpress/components';
|
||||||
import { Modal, Button, TextControl } from '@wordpress/components';
|
|
||||||
import { useSelect, useDispatch } from '@wordpress/data';
|
import { useSelect, useDispatch } from '@wordpress/data';
|
||||||
import { rawHandler } from '@wordpress/blocks';
|
import { rawHandler } from '@wordpress/blocks';
|
||||||
import domReady from '@wordpress/dom-ready';
|
import domReady from '@wordpress/dom-ready';
|
||||||
|
@ -16,110 +15,35 @@ import domReady from '@wordpress/dom-ready';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import TOOLBAR_ICON from '../utils/icon';
|
import Input from './components/input';
|
||||||
import LoadingLine from './components/loading-line';
|
import LoadingLine from './components/loading-line';
|
||||||
import LoadingText from './components/loading-text';
|
import Content from './components/content';
|
||||||
import Notice from './components/notice';
|
import Footer from './components/footer';
|
||||||
|
|
||||||
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 POPUP_CONTAINER_CLASS = 'mind-popup-container';
|
const POPUP_CONTAINER_CLASS = 'mind-popup-container';
|
||||||
|
|
||||||
const commands = [
|
export default function Popup() {
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: __('Post Presets', 'mind'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'request',
|
|
||||||
label: __('Post title about…', 'mind'),
|
|
||||||
request: __('Write a post title about ', 'mind'),
|
|
||||||
icon: <PopupPostTitleAboutIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'request',
|
|
||||||
label: __('Post about…', 'mind'),
|
|
||||||
request: __('Write a blog post about ', 'mind'),
|
|
||||||
icon: <PopupPostAboutIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'request',
|
|
||||||
label: __('Outline about…', 'mind'),
|
|
||||||
request: __('Write a blog post outline about ', 'mind'),
|
|
||||||
icon: <PopupOutlineAboutIcon />,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: __('Content Presets', 'mind'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'request',
|
|
||||||
label: __('Paragraph about…', 'mind'),
|
|
||||||
request: __('Create a paragraph about ', 'mind'),
|
|
||||||
icon: <PopupParagraphAboutIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'request',
|
|
||||||
label: __('List about…', 'mind'),
|
|
||||||
request: __('Create a list about ', 'mind'),
|
|
||||||
icon: <PopupListAboutIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'request',
|
|
||||||
label: __('Table about…', 'mind'),
|
|
||||||
request: __('Create a table about ', 'mind'),
|
|
||||||
icon: <PopupTableAboutIcon />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Popup(props) {
|
|
||||||
const { onClose } = props;
|
|
||||||
|
|
||||||
const ref = useRef();
|
|
||||||
|
|
||||||
const { setHighlightBlocks } = useDispatch('mind/blocks');
|
const { setHighlightBlocks } = useDispatch('mind/blocks');
|
||||||
|
|
||||||
const { close, reset, setInput, setScreen, setError, requestAI } =
|
const { close, reset } = useDispatch('mind/popup');
|
||||||
useDispatch('mind/popup');
|
|
||||||
|
|
||||||
const {
|
const { isOpen, insertionPlace, loading, response } = useSelect(
|
||||||
isOpen,
|
(select) => {
|
||||||
input,
|
|
||||||
context,
|
|
||||||
insertionPlace,
|
|
||||||
screen,
|
|
||||||
loading,
|
|
||||||
response,
|
|
||||||
error,
|
|
||||||
} = useSelect((select) => {
|
|
||||||
const {
|
const {
|
||||||
isOpen: checkIsOpen,
|
isOpen: checkIsOpen,
|
||||||
getInput,
|
|
||||||
getContext,
|
|
||||||
getInsertionPlace,
|
getInsertionPlace,
|
||||||
getScreen,
|
|
||||||
getLoading,
|
getLoading,
|
||||||
getResponse,
|
getResponse,
|
||||||
getError,
|
|
||||||
} = select('mind/popup');
|
} = select('mind/popup');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOpen: checkIsOpen(),
|
isOpen: checkIsOpen(),
|
||||||
input: getInput(),
|
|
||||||
context: getContext(),
|
|
||||||
insertionPlace: getInsertionPlace(),
|
insertionPlace: getInsertionPlace(),
|
||||||
screen: getScreen(),
|
|
||||||
loading: getLoading(),
|
loading: getLoading(),
|
||||||
response: getResponse(),
|
response: getResponse(),
|
||||||
error: getError(),
|
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const { selectedClientIds } = useSelect((select) => {
|
const { selectedClientIds } = useSelect((select) => {
|
||||||
const { getSelectedBlockClientIds } = select('core/block-editor');
|
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');
|
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() {
|
function insertResponse() {
|
||||||
const parsedBlocks = rawHandler({ HTML: response });
|
const parsedBlocks = rawHandler({ HTML: response });
|
||||||
|
|
||||||
|
@ -184,177 +80,27 @@ export default function Popup(props) {
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
close();
|
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) {
|
if (!isOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const showFooter = response || (input && !loading && !response);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
ref={ref}
|
|
||||||
title={false}
|
title={false}
|
||||||
className="mind-popup"
|
className="mind-popup"
|
||||||
overlayClassName="mind-popup-overlay"
|
overlayClassName="mind-popup-overlay"
|
||||||
onRequestClose={() => {
|
onRequestClose={() => {
|
||||||
reset();
|
reset();
|
||||||
close();
|
close();
|
||||||
|
|
||||||
if (onClose) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
__experimentalHideHeader
|
__experimentalHideHeader
|
||||||
>
|
>
|
||||||
<div className="mind-popup-input">
|
<Input onInsert={onInsert} />
|
||||||
{TOOLBAR_ICON}
|
|
||||||
<TextControl
|
|
||||||
placeholder={__('Ask AI to write anything…', 'mind')}
|
|
||||||
value={input}
|
|
||||||
onChange={(val) => {
|
|
||||||
setInput(val);
|
|
||||||
}}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
disabled={loading}
|
|
||||||
/>
|
|
||||||
{contextLabel ? (
|
|
||||||
<span className="mind-popup-input-context">
|
|
||||||
{contextLabel}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{loading && <LoadingLine />}
|
{loading && <LoadingLine />}
|
||||||
<div className="mind-popup-content">
|
<Content />
|
||||||
{screen === '' ? (
|
<Footer onInsert={onInsert} />
|
||||||
<div className="mind-popup-commands">
|
|
||||||
{commands.map((data) => {
|
|
||||||
if (data.type === 'category') {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
key={data.type + data.label}
|
|
||||||
className="mind-popup-commands-category"
|
|
||||||
>
|
|
||||||
{data.label}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={data.type + data.label}
|
|
||||||
className="mind-popup-commands-button"
|
|
||||||
onClick={() => {
|
|
||||||
setInput(data.request);
|
|
||||||
setScreen('request');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{data.icon || ''}
|
|
||||||
{data.label}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{screen === 'request' && (
|
|
||||||
<div className="mind-popup-request">
|
|
||||||
{loading && (
|
|
||||||
<LoadingText>
|
|
||||||
{__('Waiting for AI response', 'mind')}
|
|
||||||
</LoadingText>
|
|
||||||
)}
|
|
||||||
{!loading && response && <RawHTML>{response}</RawHTML>}
|
|
||||||
{!loading && error && (
|
|
||||||
<Notice type="error">{error}</Notice>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{showFooter && (
|
|
||||||
<div className="mind-popup-footer">
|
|
||||||
<div className="mind-popup-footer-actions">
|
|
||||||
{input && !loading && !response && (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
requestAI();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{__('Get Answer', 'mind')} <kbd>⏎</kbd>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{response && (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setError('');
|
|
||||||
requestAI();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{__('Regenerate', 'mind')} <kbd>↻</kbd>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
copyToClipboard();
|
|
||||||
|
|
||||||
reset();
|
|
||||||
close();
|
|
||||||
|
|
||||||
if (onClose) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{__('Copy', 'mind')}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onInsert}>
|
|
||||||
{__('Insert', 'mind')} <kbd>⏎</kbd>
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,165 +25,3 @@ $padding: 10px;
|
||||||
// Upper then dropdown.
|
// Upper then dropdown.
|
||||||
z-index: 1000001;
|
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%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue