first
This commit is contained in:
@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 3,
|
||||
"name": "visual-portfolio/saved-editor",
|
||||
"category": "common",
|
||||
"title": "Visual Portfolio Editor",
|
||||
"description": "Edit saved Visual Portfolio layouts.",
|
||||
"textdomain": "visual-portfolio",
|
||||
"supports": {
|
||||
"anchor": false,
|
||||
"className": false,
|
||||
"customClassName": false,
|
||||
"html": false,
|
||||
"inserter": false
|
||||
}
|
||||
}
|
@ -0,0 +1,250 @@
|
||||
import { InspectorControls } from '@wordpress/block-editor';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { Button, PanelBody } from '@wordpress/components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { applyFilters } from '@wordpress/hooks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
const { navigator } = window;
|
||||
|
||||
let copiedTimeout;
|
||||
|
||||
function ShortcodeRender(props) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="vpf-layout-shortcode-copy">
|
||||
<strong>{props.label}:</strong>
|
||||
<div>
|
||||
<pre>{props.content}</pre>
|
||||
<Button
|
||||
isSmall
|
||||
onClick={() => {
|
||||
navigator.clipboard
|
||||
.writeText(props.content)
|
||||
.then(() => {
|
||||
setCopied(true);
|
||||
|
||||
clearTimeout(copiedTimeout);
|
||||
|
||||
copiedTimeout = setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 450);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z" />
|
||||
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z" />
|
||||
</svg>
|
||||
{copied ? (
|
||||
<div className="vpf-layout-shortcode-copied">
|
||||
{__('Copied!', 'visual-portfolio')}
|
||||
</div>
|
||||
) : null}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layouts Editor block
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
function LayoutsEditorBlock(props) {
|
||||
const { clientId } = props;
|
||||
|
||||
const [additionalShortcodes, setAdditionalShortcodes] = useState(false);
|
||||
|
||||
const { postId, blockData, VisualPortfolioBlockEdit } = useSelect(
|
||||
(select) => {
|
||||
const { getBlockData } = select(
|
||||
'visual-portfolio/saved-layout-data'
|
||||
);
|
||||
const { getCurrentPostId } = select('core/editor');
|
||||
const { getBlockType } = select('core/blocks');
|
||||
|
||||
return {
|
||||
postId: getCurrentPostId(),
|
||||
blockData: getBlockData(),
|
||||
VisualPortfolioBlockEdit:
|
||||
getBlockType('visual-portfolio/block')?.edit ||
|
||||
(() => null),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const { updateBlockData } = useDispatch(
|
||||
'visual-portfolio/saved-layout-data'
|
||||
);
|
||||
|
||||
let shortcodes = [
|
||||
{
|
||||
label: __('This Saved Layout', 'visual-portfolio'),
|
||||
content: `[visual_portfolio id="${postId}"]`,
|
||||
},
|
||||
{
|
||||
label: __('Filter', 'visual-portfolio'),
|
||||
content: `[visual_portfolio_filter id="${postId}" type="minimal" align="center" show_count="false" text_all="All"]`,
|
||||
isOptional: true,
|
||||
},
|
||||
{
|
||||
label: __('Sort', 'visual-portfolio'),
|
||||
content: `[visual_portfolio_sort id="${postId}" type="minimal" align="center"]`,
|
||||
isOptional: true,
|
||||
},
|
||||
];
|
||||
|
||||
shortcodes = applyFilters(
|
||||
'vpf.layouts-editor.shortcodes-list',
|
||||
shortcodes,
|
||||
{ props, postId, blockData, updateBlockData, VisualPortfolioBlockEdit }
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<InspectorControls>
|
||||
<PanelBody
|
||||
title={__('Shortcodes', 'visual-portfolio')}
|
||||
scrollAfterOpen
|
||||
>
|
||||
<p>
|
||||
{__(
|
||||
'To output this saved layout and its components you can use the following shortcodes:'
|
||||
)}
|
||||
</p>
|
||||
{shortcodes.map((data) => {
|
||||
if (data.isOptional) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ShortcodeRender
|
||||
key={`shortcode-${data.label}`}
|
||||
{...data}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{additionalShortcodes ? (
|
||||
<>
|
||||
{shortcodes.map((data) => {
|
||||
if (!data.isOptional) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ShortcodeRender
|
||||
key={`shortcode-${data.label}`}
|
||||
{...data}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{applyFilters(
|
||||
'vpf.layouts-editor.shortcodes',
|
||||
'',
|
||||
this
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
isLink
|
||||
onClick={() => {
|
||||
setAdditionalShortcodes(!additionalShortcodes);
|
||||
}}
|
||||
>
|
||||
{__(
|
||||
'Show Additional Shortcodes',
|
||||
'visual-portfolio'
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
<VisualPortfolioBlockEdit
|
||||
attributes={{
|
||||
...blockData,
|
||||
block_id: blockData.id || clientId,
|
||||
}}
|
||||
setAttributes={(data) => {
|
||||
updateBlockData(data);
|
||||
}}
|
||||
clientId={clientId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
registerBlockType('visual-portfolio/saved-editor', {
|
||||
icon: {
|
||||
foreground: '#2540CC',
|
||||
src: (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<mask
|
||||
id="mask0"
|
||||
// eslint-disable-next-line react/no-unknown-property
|
||||
mask-type="alpha"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="9"
|
||||
y="8"
|
||||
width="5"
|
||||
height="6"
|
||||
>
|
||||
<path
|
||||
d="M11.1409 14L13.0565 8.49994H11.2789L9.55397 14H11.1409Z"
|
||||
fill="url(#paint0_linear)"
|
||||
/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<path
|
||||
d="M11.1409 14L13.0565 8.49994H11.2789L9.55397 14H11.1409Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M8.90795 14L6.9923 8.49994H8.76989L10.4948 14H8.90795Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M19 16.2222C19 16.6937 18.8104 17.1459 18.4728 17.4793C18.1352 17.8127 17.6774 18 17.2 18H2.8C2.32261 18 1.86477 17.8127 1.52721 17.4793C1.18964 17.1459 1 16.6937 1 16.2222V3.77778C1 3.30628 1.18964 2.8541 1.52721 2.5207C1.86477 2.1873 2.32261 2 2.8 2H7.3L9.1 4.66667H17.2C17.6774 4.66667 18.1352 4.85397 18.4728 5.18737C18.8104 5.52076 19 5.97295 19 6.44444V16.2222Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="transparent"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear"
|
||||
x1="12.191"
|
||||
y1="8.49994"
|
||||
x2="7.44436"
|
||||
y2="15.1301"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop />
|
||||
<stop offset="1" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
edit: LayoutsEditorBlock,
|
||||
save() {
|
||||
return null;
|
||||
},
|
||||
});
|
@ -0,0 +1,4 @@
|
||||
import './style.scss';
|
||||
import './store';
|
||||
import './block';
|
||||
import './plugin';
|
@ -0,0 +1,183 @@
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { createBlock } from '@wordpress/blocks';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { useEffect, useRef } from '@wordpress/element';
|
||||
import { registerPlugin } from '@wordpress/plugins';
|
||||
|
||||
function UpdateEditor() {
|
||||
const {
|
||||
isSavingPost,
|
||||
isAutosavingPost,
|
||||
selectedBlock,
|
||||
editorSettings,
|
||||
editorMode,
|
||||
blocks,
|
||||
postId,
|
||||
blockData,
|
||||
} = useSelect((select) => {
|
||||
const {
|
||||
isSavingPost: checkIsSavingPost,
|
||||
isAutosavingPost: checkIsAutosavingPost,
|
||||
getCurrentPostId,
|
||||
getEditorSettings,
|
||||
} = select('core/editor');
|
||||
|
||||
const { getSelectedBlock, getBlocks } = select('core/block-editor');
|
||||
|
||||
const { getEditorMode } = select('core/edit-post');
|
||||
|
||||
const { getBlockData } = select('visual-portfolio/saved-layout-data');
|
||||
|
||||
return {
|
||||
isSavingPost: checkIsSavingPost(),
|
||||
isAutosavingPost: checkIsAutosavingPost(),
|
||||
selectedBlock: getSelectedBlock(),
|
||||
editorSettings: getEditorSettings(),
|
||||
editorMode: getEditorMode(),
|
||||
blocks: getBlocks(),
|
||||
postId: getCurrentPostId(),
|
||||
blockData: getBlockData(),
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { selectBlock, insertBlocks, resetBlocks } =
|
||||
useDispatch('core/block-editor');
|
||||
const { editPost } = useDispatch('core/editor');
|
||||
const { switchEditorMode } = useDispatch('core/edit-post');
|
||||
|
||||
/**
|
||||
* Force change gutenberg edit mode to Visual.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (editorSettings.richEditingEnabled && editorMode === 'text') {
|
||||
switchEditorMode();
|
||||
}
|
||||
}, [editorSettings, editorMode, switchEditorMode]);
|
||||
|
||||
/**
|
||||
* Add default block to post if doesn't exist.
|
||||
*/
|
||||
const blocksRestoreBusy = useRef(false);
|
||||
useEffect(() => {
|
||||
if (blocksRestoreBusy.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isValidList =
|
||||
blocks.length === 1 &&
|
||||
blocks[0] &&
|
||||
blocks[0].name === 'visual-portfolio/saved-editor';
|
||||
|
||||
if (!isValidList) {
|
||||
blocksRestoreBusy.current = true;
|
||||
resetBlocks([]);
|
||||
insertBlocks(createBlock('visual-portfolio/saved-editor'));
|
||||
blocksRestoreBusy.current = false;
|
||||
}
|
||||
}, [blocks, blocksRestoreBusy, resetBlocks, insertBlocks]);
|
||||
|
||||
/**
|
||||
* Always select block.
|
||||
* TODO: we actually should check the title block selected inside iframe
|
||||
*/
|
||||
const isBlockSelected = useRef(false);
|
||||
useEffect(() => {
|
||||
if (isBlockSelected.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if selected block, do nothing.
|
||||
if (
|
||||
selectedBlock &&
|
||||
selectedBlock.name === 'visual-portfolio/saved-editor'
|
||||
) {
|
||||
isBlockSelected.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// check if selected post title, also do nothing.
|
||||
if (
|
||||
document.querySelector(
|
||||
'.editor-post-title__block.is-selected, .editor-post-title.is-selected'
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selectBlockId = '';
|
||||
blocks.forEach((thisBlock) => {
|
||||
if (thisBlock.name === 'visual-portfolio/saved-editor') {
|
||||
selectBlockId = thisBlock.clientId;
|
||||
}
|
||||
});
|
||||
|
||||
if (selectBlockId) {
|
||||
selectBlock(selectBlockId);
|
||||
}
|
||||
}, [selectedBlock, blocks, selectBlock]);
|
||||
|
||||
/**
|
||||
* Check if post meta data edited and allow to update the post.
|
||||
*/
|
||||
const defaultBlockData = useRef(false);
|
||||
const editorRefreshTimeout = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!blockData || !Object.keys(blockData).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSavingPost || isAutosavingPost || !defaultBlockData.current) {
|
||||
defaultBlockData.current = JSON.stringify(blockData);
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(editorRefreshTimeout.current);
|
||||
editorRefreshTimeout.current = setTimeout(() => {
|
||||
if (defaultBlockData.current !== JSON.stringify(blockData)) {
|
||||
editPost({ edited: new Date() });
|
||||
}
|
||||
}, 150);
|
||||
}, [isSavingPost, isAutosavingPost, blockData, editPost]);
|
||||
|
||||
/**
|
||||
* Save meta data on post save.
|
||||
*/
|
||||
const wasSavingPost = useRef(false);
|
||||
const wasAutosavingPost = useRef(false);
|
||||
useEffect(() => {
|
||||
const shouldUpdate =
|
||||
wasSavingPost.current &&
|
||||
!isSavingPost &&
|
||||
!wasAutosavingPost.current;
|
||||
|
||||
// Save current state for next inspection.
|
||||
wasSavingPost.current = isSavingPost;
|
||||
wasAutosavingPost.current = isAutosavingPost;
|
||||
|
||||
if (shouldUpdate) {
|
||||
const prefixedBlockData = {};
|
||||
|
||||
Object.keys(blockData).forEach((name) => {
|
||||
prefixedBlockData[`vp_${name}`] = blockData[name];
|
||||
});
|
||||
|
||||
apiFetch({
|
||||
path: '/visual-portfolio/v1/update_layout/',
|
||||
method: 'POST',
|
||||
data: {
|
||||
data: prefixedBlockData,
|
||||
post_id: postId,
|
||||
},
|
||||
}).catch((response) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(response);
|
||||
});
|
||||
}
|
||||
}, [isSavingPost, isAutosavingPost, postId, blockData]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
registerPlugin('vpf-saved-layouts-editor', {
|
||||
render: UpdateEditor,
|
||||
});
|
@ -0,0 +1 @@
|
||||
import './saved-layout-data';
|
@ -0,0 +1,20 @@
|
||||
export function apiFetch(request) {
|
||||
return {
|
||||
type: 'API_FETCH',
|
||||
request,
|
||||
};
|
||||
}
|
||||
|
||||
export function setBlockData(data) {
|
||||
return {
|
||||
type: 'SET_BLOCK_DATA',
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateBlockData(data) {
|
||||
return {
|
||||
type: 'UPDATE_BLOCK_DATA',
|
||||
data,
|
||||
};
|
||||
}
|
10
wp-content/plugins/visual-portfolio/gutenberg/layouts-editor/store/saved-layout-data/controls.js
vendored
Normal file
10
wp-content/plugins/visual-portfolio/gutenberg/layouts-editor/store/saved-layout-data/controls.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
const { apiFetch } = wp;
|
||||
|
||||
export function API_FETCH({ request }) {
|
||||
return apiFetch(request).then((fetchedData) => {
|
||||
if (fetchedData && fetchedData.success && fetchedData.response) {
|
||||
return fetchedData.response;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { createReduxStore, register } from '@wordpress/data';
|
||||
|
||||
import * as actions from './actions';
|
||||
import * as controls from './controls';
|
||||
import reducer from './reducer';
|
||||
import * as selectors from './selectors';
|
||||
|
||||
const store = createReduxStore('visual-portfolio/saved-layout-data', {
|
||||
reducer,
|
||||
selectors,
|
||||
actions,
|
||||
controls,
|
||||
});
|
||||
|
||||
register(store);
|
@ -0,0 +1,35 @@
|
||||
const { VPSavedLayoutVariables } = window;
|
||||
|
||||
function reducer(state = { data: VPSavedLayoutVariables.data }, action = {}) {
|
||||
switch (action.type) {
|
||||
case 'SET_BLOCK_DATA':
|
||||
if (action.data) {
|
||||
if (state) {
|
||||
return {
|
||||
...state,
|
||||
data: action.data,
|
||||
};
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'UPDATE_BLOCK_DATA':
|
||||
if (action.data && state) {
|
||||
return {
|
||||
...state,
|
||||
data: {
|
||||
...state.data,
|
||||
...action.data,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
// no default
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
export default reducer;
|
@ -0,0 +1,5 @@
|
||||
const { VPSavedLayoutVariables } = window;
|
||||
|
||||
export function getBlockData(state) {
|
||||
return state.data || VPSavedLayoutVariables.data;
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Visual Portfolio Layouts Editor styles.
|
||||
*/
|
||||
|
||||
.post-type-vp_lists {
|
||||
.block-editor-block-list__breadcrumb,
|
||||
.edit-post-layout__footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// remove outline from block.
|
||||
.block-editor-block-list__block::before,
|
||||
.block-editor-block-contextual-toolbar,
|
||||
.block-editor-block-list__insertion-point {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Remove writing container.
|
||||
.block-editor-writing-flow__click-redirect {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Shortcodes settings.
|
||||
.vpf-layout-shortcode-copy {
|
||||
> div {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
> strong {
|
||||
display: block;
|
||||
padding-top: 0;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 10px;
|
||||
padding-right: 40px;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 1.1em;
|
||||
overflow: auto;
|
||||
font-size: 12px;
|
||||
background-color: #ebebeb;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.components-button {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 0;
|
||||
background-color: #ebebeb;
|
||||
opacity: 0;
|
||||
transition: 0.2s opacity;
|
||||
}
|
||||
|
||||
.components-button:hover,
|
||||
.components-button:focus,
|
||||
pre:hover + .components-button {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Copied element
|
||||
.vpf-layout-shortcode-copied {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
padding: 4px 6px;
|
||||
margin-left: 50%;
|
||||
color: #fff;
|
||||
background: #000;
|
||||
border-radius: 3px;
|
||||
opacity: 0;
|
||||
transform: translateY(5px) translateX(-50%);
|
||||
animation: 0.4s vpf-layout-shortcode-copied linear;
|
||||
}
|
||||
|
||||
@keyframes vpf-layout-shortcode-copied {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transition-timing-function: ease-out;
|
||||
transform: translateY(5px) translateX(-50%);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: translateY(-5px) translateX(-50%);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transition-timing-function: ease-in;
|
||||
transform: translateY(-15px) translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user