node_modules ignore

This commit is contained in:
2025-05-08 23:43:47 +02:00
parent e19d52f172
commit 4574544c9f
65041 changed files with 10593536 additions and 0 deletions

View File

@@ -0,0 +1,235 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
var collections = require('../../constants/collections.js');
var plugin = require('../../constants/plugin.js');
var DocumentRBAC = require('../../features/DocumentRBAC.js');
var useDocument = require('../../hooks/useDocument.js');
var useDocumentLayout = require('../../hooks/useDocumentLayout.js');
var useLazyComponents = require('../../hooks/useLazyComponents.js');
var useOnce = require('../../hooks/useOnce.js');
var translations = require('../../utils/translations.js');
var validation = require('../../utils/validation.js');
var FormLayout = require('./components/FormLayout.js');
var Header = require('./components/Header.js');
var Panels = require('./components/Panels.js');
/* -------------------------------------------------------------------------------------------------
* EditViewPage
* -----------------------------------------------------------------------------------------------*/ const EditViewPage = ()=>{
const location = reactRouterDom.useLocation();
const [{ query: { status } }, setQuery] = strapiAdmin.useQueryParams({
status: 'draft'
});
const { formatMessage } = reactIntl.useIntl();
const { toggleNotification } = strapiAdmin.useNotification();
const doc = useDocument.useDoc();
const { document, meta, isLoading: isLoadingDocument, schema, components, collectionType, id, model, hasError, getTitle, getInitialFormValues } = doc;
const hasDraftAndPublished = schema?.options?.draftAndPublish ?? false;
useOnce.useOnce(()=>{
/**
* We only ever want to fire the notification once otherwise
* whenever the app re-renders it'll pop up regardless of
* what we do because the state comes from react-router-dom
*/ if (location?.state && 'error' in location.state) {
toggleNotification({
type: 'danger',
message: location.state.error,
timeout: 5000
});
}
});
const isLoadingActionsRBAC = DocumentRBAC.useDocumentRBAC('EditViewPage', (state)=>state.isLoading);
const isSingleType = collectionType === collections.SINGLE_TYPES;
/**
* single-types don't current have an id, but because they're a singleton
* we can simply use the update operation to continuously update the same
* document with varying params.
*/ const isCreatingDocument = !id && !isSingleType;
const { isLoading: isLoadingLayout, edit: { layout, settings: { mainField } } } = useDocumentLayout.useDocumentLayout(model);
const { isLazyLoading } = useLazyComponents.useLazyComponents([]);
const isLoading = isLoadingActionsRBAC || isLoadingDocument || isLoadingLayout || isLazyLoading;
const initialValues = getInitialFormValues(isCreatingDocument);
if (isLoading && !document?.documentId) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {});
}
if (!initialValues || hasError) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Error, {});
}
const handleTabChange = (status)=>{
if (status === 'published' || status === 'draft') {
setQuery({
status
}, 'push', true);
}
};
const validateSync = (values, options)=>{
const yupSchema = validation.createYupSchema(schema?.attributes, components, {
status,
...options
});
return yupSchema.validateSync(values, {
abortEarly: false
});
};
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Main, {
paddingLeft: 10,
paddingRight: 10,
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Title, {
children: getTitle(mainField)
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Form, {
disabled: hasDraftAndPublished && status === 'published',
initialValues: initialValues,
method: isCreatingDocument ? 'POST' : 'PUT',
validate: (values, options)=>{
const yupSchema = validation.createYupSchema(schema?.attributes, components, {
status,
...options
});
return yupSchema.validate(values, {
abortEarly: false
});
},
initialErrors: location?.state?.forceValidation ? validateSync(initialValues, {}) : {},
children: ({ resetForm })=>/*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(Header.Header, {
isCreating: isCreatingDocument,
status: hasDraftAndPublished ? getDocumentStatus(document, meta) : undefined,
title: getTitle(mainField)
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Tabs.Root, {
variant: "simple",
value: status,
onValueChange: handleTabChange,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Tabs.List, {
"aria-label": formatMessage({
id: translations.getTranslation('containers.edit.tabs.label'),
defaultMessage: 'Document status'
}),
children: hasDraftAndPublished ? /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(StatusTab, {
value: "draft",
children: formatMessage({
id: translations.getTranslation('containers.edit.tabs.draft'),
defaultMessage: 'draft'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(StatusTab, {
disabled: !meta || meta.availableStatus.length === 0,
value: "published",
children: formatMessage({
id: translations.getTranslation('containers.edit.tabs.published'),
defaultMessage: 'published'
})
})
]
}) : null
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Grid.Root, {
paddingTop: 8,
gap: 4,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Grid.Item, {
col: 9,
s: 12,
direction: "column",
alignItems: "stretch",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Tabs.Content, {
value: "draft",
children: /*#__PURE__*/ jsxRuntime.jsx(FormLayout.FormLayout, {
layout: layout,
document: doc
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Tabs.Content, {
value: "published",
children: /*#__PURE__*/ jsxRuntime.jsx(FormLayout.FormLayout, {
layout: layout,
document: doc
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 3,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(Panels.Panels, {})
})
]
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Blocker, {
// We reset the form to the published version to avoid errors like https://strapi-inc.atlassian.net/browse/CONTENT-2284
onProceed: resetForm
})
]
})
})
]
});
};
const StatusTab = styledComponents.styled(designSystem.Tabs.Trigger)`
text-transform: uppercase;
`;
/**
* @internal
* @description Returns the status of the document where its latest state takes priority,
* this typically will be "published" unless a user has edited their draft in which we should
* display "modified".
*/ const getDocumentStatus = (document, meta)=>{
const docStatus = document?.status;
const statuses = meta?.availableStatus ?? [];
/**
* Creating an entry
*/ if (!docStatus) {
return 'draft';
}
/**
* We're viewing a draft, but the document could have a published version
*/ if (docStatus === 'draft' && statuses.find((doc)=>doc.publishedAt !== null)) {
return 'published';
}
return docStatus;
};
/* -------------------------------------------------------------------------------------------------
* ProtectedEditViewPage
* -----------------------------------------------------------------------------------------------*/ const ProtectedEditViewPage = ()=>{
const { slug = '' } = reactRouterDom.useParams();
const { permissions = [], isLoading, error } = strapiAdmin.useRBAC(plugin.PERMISSIONS.map((action)=>({
action,
subject: slug
})));
if (isLoading) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {});
}
if (error || !slug) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Error, {});
}
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Protect, {
permissions: permissions,
children: ({ permissions })=>/*#__PURE__*/ jsxRuntime.jsx(DocumentRBAC.DocumentRBAC, {
permissions: permissions,
children: /*#__PURE__*/ jsxRuntime.jsx(EditViewPage, {})
})
});
};
exports.EditViewPage = EditViewPage;
exports.ProtectedEditViewPage = ProtectedEditViewPage;
exports.getDocumentStatus = getDocumentStatus;
//# sourceMappingURL=EditViewPage.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,231 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import 'react';
import { useQueryParams, useNotification, Page, Form, Blocker, useRBAC } from '@strapi/admin/strapi-admin';
import { Tabs, Main, Grid } from '@strapi/design-system';
import { useIntl } from 'react-intl';
import { useLocation, useParams } from 'react-router-dom';
import { styled } from 'styled-components';
import { SINGLE_TYPES } from '../../constants/collections.mjs';
import { PERMISSIONS } from '../../constants/plugin.mjs';
import { useDocumentRBAC, DocumentRBAC } from '../../features/DocumentRBAC.mjs';
import { useDoc } from '../../hooks/useDocument.mjs';
import { useDocumentLayout } from '../../hooks/useDocumentLayout.mjs';
import { useLazyComponents } from '../../hooks/useLazyComponents.mjs';
import { useOnce } from '../../hooks/useOnce.mjs';
import { getTranslation } from '../../utils/translations.mjs';
import { createYupSchema } from '../../utils/validation.mjs';
import { FormLayout } from './components/FormLayout.mjs';
import { Header } from './components/Header.mjs';
import { Panels } from './components/Panels.mjs';
/* -------------------------------------------------------------------------------------------------
* EditViewPage
* -----------------------------------------------------------------------------------------------*/ const EditViewPage = ()=>{
const location = useLocation();
const [{ query: { status } }, setQuery] = useQueryParams({
status: 'draft'
});
const { formatMessage } = useIntl();
const { toggleNotification } = useNotification();
const doc = useDoc();
const { document, meta, isLoading: isLoadingDocument, schema, components, collectionType, id, model, hasError, getTitle, getInitialFormValues } = doc;
const hasDraftAndPublished = schema?.options?.draftAndPublish ?? false;
useOnce(()=>{
/**
* We only ever want to fire the notification once otherwise
* whenever the app re-renders it'll pop up regardless of
* what we do because the state comes from react-router-dom
*/ if (location?.state && 'error' in location.state) {
toggleNotification({
type: 'danger',
message: location.state.error,
timeout: 5000
});
}
});
const isLoadingActionsRBAC = useDocumentRBAC('EditViewPage', (state)=>state.isLoading);
const isSingleType = collectionType === SINGLE_TYPES;
/**
* single-types don't current have an id, but because they're a singleton
* we can simply use the update operation to continuously update the same
* document with varying params.
*/ const isCreatingDocument = !id && !isSingleType;
const { isLoading: isLoadingLayout, edit: { layout, settings: { mainField } } } = useDocumentLayout(model);
const { isLazyLoading } = useLazyComponents([]);
const isLoading = isLoadingActionsRBAC || isLoadingDocument || isLoadingLayout || isLazyLoading;
const initialValues = getInitialFormValues(isCreatingDocument);
if (isLoading && !document?.documentId) {
return /*#__PURE__*/ jsx(Page.Loading, {});
}
if (!initialValues || hasError) {
return /*#__PURE__*/ jsx(Page.Error, {});
}
const handleTabChange = (status)=>{
if (status === 'published' || status === 'draft') {
setQuery({
status
}, 'push', true);
}
};
const validateSync = (values, options)=>{
const yupSchema = createYupSchema(schema?.attributes, components, {
status,
...options
});
return yupSchema.validateSync(values, {
abortEarly: false
});
};
return /*#__PURE__*/ jsxs(Main, {
paddingLeft: 10,
paddingRight: 10,
children: [
/*#__PURE__*/ jsx(Page.Title, {
children: getTitle(mainField)
}),
/*#__PURE__*/ jsx(Form, {
disabled: hasDraftAndPublished && status === 'published',
initialValues: initialValues,
method: isCreatingDocument ? 'POST' : 'PUT',
validate: (values, options)=>{
const yupSchema = createYupSchema(schema?.attributes, components, {
status,
...options
});
return yupSchema.validate(values, {
abortEarly: false
});
},
initialErrors: location?.state?.forceValidation ? validateSync(initialValues, {}) : {},
children: ({ resetForm })=>/*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx(Header, {
isCreating: isCreatingDocument,
status: hasDraftAndPublished ? getDocumentStatus(document, meta) : undefined,
title: getTitle(mainField)
}),
/*#__PURE__*/ jsxs(Tabs.Root, {
variant: "simple",
value: status,
onValueChange: handleTabChange,
children: [
/*#__PURE__*/ jsx(Tabs.List, {
"aria-label": formatMessage({
id: getTranslation('containers.edit.tabs.label'),
defaultMessage: 'Document status'
}),
children: hasDraftAndPublished ? /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx(StatusTab, {
value: "draft",
children: formatMessage({
id: getTranslation('containers.edit.tabs.draft'),
defaultMessage: 'draft'
})
}),
/*#__PURE__*/ jsx(StatusTab, {
disabled: !meta || meta.availableStatus.length === 0,
value: "published",
children: formatMessage({
id: getTranslation('containers.edit.tabs.published'),
defaultMessage: 'published'
})
})
]
}) : null
}),
/*#__PURE__*/ jsxs(Grid.Root, {
paddingTop: 8,
gap: 4,
children: [
/*#__PURE__*/ jsxs(Grid.Item, {
col: 9,
s: 12,
direction: "column",
alignItems: "stretch",
children: [
/*#__PURE__*/ jsx(Tabs.Content, {
value: "draft",
children: /*#__PURE__*/ jsx(FormLayout, {
layout: layout,
document: doc
})
}),
/*#__PURE__*/ jsx(Tabs.Content, {
value: "published",
children: /*#__PURE__*/ jsx(FormLayout, {
layout: layout,
document: doc
})
})
]
}),
/*#__PURE__*/ jsx(Grid.Item, {
col: 3,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(Panels, {})
})
]
})
]
}),
/*#__PURE__*/ jsx(Blocker, {
// We reset the form to the published version to avoid errors like https://strapi-inc.atlassian.net/browse/CONTENT-2284
onProceed: resetForm
})
]
})
})
]
});
};
const StatusTab = styled(Tabs.Trigger)`
text-transform: uppercase;
`;
/**
* @internal
* @description Returns the status of the document where its latest state takes priority,
* this typically will be "published" unless a user has edited their draft in which we should
* display "modified".
*/ const getDocumentStatus = (document, meta)=>{
const docStatus = document?.status;
const statuses = meta?.availableStatus ?? [];
/**
* Creating an entry
*/ if (!docStatus) {
return 'draft';
}
/**
* We're viewing a draft, but the document could have a published version
*/ if (docStatus === 'draft' && statuses.find((doc)=>doc.publishedAt !== null)) {
return 'published';
}
return docStatus;
};
/* -------------------------------------------------------------------------------------------------
* ProtectedEditViewPage
* -----------------------------------------------------------------------------------------------*/ const ProtectedEditViewPage = ()=>{
const { slug = '' } = useParams();
const { permissions = [], isLoading, error } = useRBAC(PERMISSIONS.map((action)=>({
action,
subject: slug
})));
if (isLoading) {
return /*#__PURE__*/ jsx(Page.Loading, {});
}
if (error || !slug) {
return /*#__PURE__*/ jsx(Page.Error, {});
}
return /*#__PURE__*/ jsx(Page.Protect, {
permissions: permissions,
children: ({ permissions })=>/*#__PURE__*/ jsx(DocumentRBAC, {
permissions: permissions,
children: /*#__PURE__*/ jsx(EditViewPage, {})
})
});
};
export { EditViewPage, ProtectedEditViewPage, getDocumentStatus };
//# sourceMappingURL=EditViewPage.mjs.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,34 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var designSystem = require('@strapi/design-system');
var reactIntl = require('react-intl');
var strings = require('../../../utils/strings.js');
/**
* @public
* @description Displays the status of a document (draft, published, etc.)
* and automatically calculates the appropriate variant for the status.
*/ const DocumentStatus = ({ status = 'draft', size = 'S', ...restProps })=>{
const statusVariant = status === 'draft' ? 'secondary' : status === 'published' ? 'success' : 'alternative';
const { formatMessage } = reactIntl.useIntl();
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Status, {
...restProps,
size: size,
variant: statusVariant,
role: "status",
"aria-label": status,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
tag: "span",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: `content-manager.containers.List.${status}`,
defaultMessage: strings.capitalise(status)
})
})
});
};
exports.DocumentStatus = DocumentStatus;
//# sourceMappingURL=DocumentStatus.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DocumentStatus.js","sources":["../../../../../admin/src/pages/EditView/components/DocumentStatus.tsx"],"sourcesContent":["import { Status, StatusProps, Typography } from '@strapi/design-system';\nimport { useIntl } from 'react-intl';\n\nimport { capitalise } from '../../../utils/strings';\n\ninterface DocumentStatusProps extends Omit<StatusProps, 'children' | 'variant'> {\n /**\n * The status of the document (draft, published, etc.)\n * @default 'draft'\n */\n status?: string;\n}\n\n/**\n * @public\n * @description Displays the status of a document (draft, published, etc.)\n * and automatically calculates the appropriate variant for the status.\n */\nconst DocumentStatus = ({ status = 'draft', size = 'S', ...restProps }: DocumentStatusProps) => {\n const statusVariant =\n status === 'draft' ? 'secondary' : status === 'published' ? 'success' : 'alternative';\n\n const { formatMessage } = useIntl();\n\n return (\n <Status {...restProps} size={size} variant={statusVariant} role=\"status\" aria-label={status}>\n <Typography tag=\"span\" variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: `content-manager.containers.List.${status}`,\n defaultMessage: capitalise(status),\n })}\n </Typography>\n </Status>\n );\n};\n\nexport { DocumentStatus };\nexport type { DocumentStatusProps };\n"],"names":["DocumentStatus","status","size","restProps","statusVariant","formatMessage","useIntl","_jsx","Status","variant","role","aria-label","Typography","tag","fontWeight","id","defaultMessage","capitalise"],"mappings":";;;;;;;AAaA;;;;IAKA,MAAMA,cAAiB,GAAA,CAAC,EAAEC,MAAAA,GAAS,OAAO,EAAEC,IAAO,GAAA,GAAG,EAAE,GAAGC,SAAgC,EAAA,GAAA;AACzF,IAAA,MAAMC,gBACJH,MAAW,KAAA,OAAA,GAAU,WAAcA,GAAAA,MAAAA,KAAW,cAAc,SAAY,GAAA,aAAA;IAE1E,MAAM,EAAEI,aAAa,EAAE,GAAGC,iBAAAA,EAAAA;AAE1B,IAAA,qBACEC,cAACC,CAAAA,mBAAAA,EAAAA;AAAQ,QAAA,GAAGL,SAAS;QAAED,IAAMA,EAAAA,IAAAA;QAAMO,OAASL,EAAAA,aAAAA;QAAeM,IAAK,EAAA,QAAA;QAASC,YAAYV,EAAAA,MAAAA;AACnF,QAAA,QAAA,gBAAAM,cAACK,CAAAA,uBAAAA,EAAAA;YAAWC,GAAI,EAAA,MAAA;YAAOJ,OAAQ,EAAA,OAAA;YAAQK,UAAW,EAAA,MAAA;sBAC/CT,aAAc,CAAA;AACbU,gBAAAA,EAAAA,EAAI,CAAC,gCAAgC,EAAEd,MAAAA,CAAO,CAAC;AAC/Ce,gBAAAA,cAAAA,EAAgBC,kBAAWhB,CAAAA,MAAAA;AAC7B,aAAA;;;AAIR;;;;"}

View File

@@ -0,0 +1,32 @@
import { jsx } from 'react/jsx-runtime';
import { Status, Typography } from '@strapi/design-system';
import { useIntl } from 'react-intl';
import { capitalise } from '../../../utils/strings.mjs';
/**
* @public
* @description Displays the status of a document (draft, published, etc.)
* and automatically calculates the appropriate variant for the status.
*/ const DocumentStatus = ({ status = 'draft', size = 'S', ...restProps })=>{
const statusVariant = status === 'draft' ? 'secondary' : status === 'published' ? 'success' : 'alternative';
const { formatMessage } = useIntl();
return /*#__PURE__*/ jsx(Status, {
...restProps,
size: size,
variant: statusVariant,
role: "status",
"aria-label": status,
children: /*#__PURE__*/ jsx(Typography, {
tag: "span",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: `content-manager.containers.List.${status}`,
defaultMessage: capitalise(status)
})
})
});
};
export { DocumentStatus };
//# sourceMappingURL=DocumentStatus.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DocumentStatus.mjs","sources":["../../../../../admin/src/pages/EditView/components/DocumentStatus.tsx"],"sourcesContent":["import { Status, StatusProps, Typography } from '@strapi/design-system';\nimport { useIntl } from 'react-intl';\n\nimport { capitalise } from '../../../utils/strings';\n\ninterface DocumentStatusProps extends Omit<StatusProps, 'children' | 'variant'> {\n /**\n * The status of the document (draft, published, etc.)\n * @default 'draft'\n */\n status?: string;\n}\n\n/**\n * @public\n * @description Displays the status of a document (draft, published, etc.)\n * and automatically calculates the appropriate variant for the status.\n */\nconst DocumentStatus = ({ status = 'draft', size = 'S', ...restProps }: DocumentStatusProps) => {\n const statusVariant =\n status === 'draft' ? 'secondary' : status === 'published' ? 'success' : 'alternative';\n\n const { formatMessage } = useIntl();\n\n return (\n <Status {...restProps} size={size} variant={statusVariant} role=\"status\" aria-label={status}>\n <Typography tag=\"span\" variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: `content-manager.containers.List.${status}`,\n defaultMessage: capitalise(status),\n })}\n </Typography>\n </Status>\n );\n};\n\nexport { DocumentStatus };\nexport type { DocumentStatusProps };\n"],"names":["DocumentStatus","status","size","restProps","statusVariant","formatMessage","useIntl","_jsx","Status","variant","role","aria-label","Typography","tag","fontWeight","id","defaultMessage","capitalise"],"mappings":";;;;;AAaA;;;;IAKA,MAAMA,cAAiB,GAAA,CAAC,EAAEC,MAAAA,GAAS,OAAO,EAAEC,IAAO,GAAA,GAAG,EAAE,GAAGC,SAAgC,EAAA,GAAA;AACzF,IAAA,MAAMC,gBACJH,MAAW,KAAA,OAAA,GAAU,WAAcA,GAAAA,MAAAA,KAAW,cAAc,SAAY,GAAA,aAAA;IAE1E,MAAM,EAAEI,aAAa,EAAE,GAAGC,OAAAA,EAAAA;AAE1B,IAAA,qBACEC,GAACC,CAAAA,MAAAA,EAAAA;AAAQ,QAAA,GAAGL,SAAS;QAAED,IAAMA,EAAAA,IAAAA;QAAMO,OAASL,EAAAA,aAAAA;QAAeM,IAAK,EAAA,QAAA;QAASC,YAAYV,EAAAA,MAAAA;AACnF,QAAA,QAAA,gBAAAM,GAACK,CAAAA,UAAAA,EAAAA;YAAWC,GAAI,EAAA,MAAA;YAAOJ,OAAQ,EAAA,OAAA;YAAQK,UAAW,EAAA,MAAA;sBAC/CT,aAAc,CAAA;AACbU,gBAAAA,EAAAA,EAAI,CAAC,gCAAgC,EAAEd,MAAAA,CAAO,CAAC;AAC/Ce,gBAAAA,cAAAA,EAAgBC,UAAWhB,CAAAA,MAAAA;AAC7B,aAAA;;;AAIR;;;;"}

View File

@@ -0,0 +1,120 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var reactIntl = require('react-intl');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const ObservedToolbarComponent = ({ index, lastVisibleIndex, setLastVisibleIndex, rootRef, children })=>{
const isVisible = index <= lastVisibleIndex;
const containerRef = strapiAdmin.useElementOnScreen((isVisible)=>{
/**
* It's the MoreMenu's job to make an item not visible when there's not room for it.
* But we need to react here to the element becoming visible again.
*/ if (isVisible) {
setLastVisibleIndex((prev)=>Math.max(prev, index));
}
}, {
threshold: 1,
root: rootRef.current
});
return /*#__PURE__*/ jsxRuntime.jsx("div", {
ref: containerRef,
style: {
/**
* Use visibility so that the element occupies the space if requires even when there's not
* enough room for it to be visible. The empty reserved space will be clipped by the
* overflow:hidden rule on the parent, so it doesn't affect the UI.
* This way we can keep observing its visiblity and react to browser resize events.
*/ visibility: isVisible ? 'visible' : 'hidden'
},
children: children
});
};
const EditorToolbarObserver = ({ observedComponents, menuTriggerVariant = 'ghost' })=>{
const { formatMessage } = reactIntl.useIntl();
const toolbarRef = React__namespace.useRef(null);
const [lastVisibleIndex, setLastVisibleIndex] = React__namespace.useState(observedComponents.length - 1);
const hasHiddenItems = lastVisibleIndex < observedComponents.length - 1;
const menuIndex = lastVisibleIndex + 1;
const [open, setOpen] = React__namespace.useState(false);
const isMenuOpenWithContent = open && hasHiddenItems;
const menuTriggerRef = strapiAdmin.useElementOnScreen((isVisible)=>{
// We only react to the menu becoming invisible. When that happens, we hide the last item.
if (!isVisible) {
/**
* If there's no room for any item, the index can be -1.
* This is intentional, in that case only the more menu will be visible.
**/ setLastVisibleIndex((prev)=>prev - 1);
// Maintain the menu state if it has content
setOpen(isMenuOpenWithContent);
}
}, {
threshold: 1,
root: toolbarRef.current
});
return observedComponents.map((component, index)=>{
return /*#__PURE__*/ jsxRuntime.jsx(ObservedToolbarComponent, {
index: index,
lastVisibleIndex: lastVisibleIndex,
setLastVisibleIndex: setLastVisibleIndex,
rootRef: toolbarRef,
children: component.toolbar
}, component.key);
}).toSpliced(menuIndex, 0, /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Menu.Root, {
defaultOpen: false,
open: isMenuOpenWithContent,
onOpenChange: setOpen,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Trigger, {
paddingLeft: 0,
paddingRight: 0,
ref: menuTriggerRef,
variant: menuTriggerVariant,
style: {
visibility: hasHiddenItems ? 'visible' : 'hidden'
},
label: formatMessage({
id: 'global.more',
defaultMessage: 'More'
}),
tag: designSystem.IconButton,
icon: /*#__PURE__*/ jsxRuntime.jsx(Icons.More, {})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Content, {
onCloseAutoFocus: (e)=>e.preventDefault(),
maxHeight: "100%",
minWidth: "256px",
popoverPlacement: "bottom-end",
zIndex: 2,
children: observedComponents.slice(menuIndex).map((component)=>/*#__PURE__*/ jsxRuntime.jsx(React__namespace.Fragment, {
children: component.menu
}, component.key))
})
]
}, "more-menu"));
};
exports.EditorToolbarObserver = EditorToolbarObserver;
//# sourceMappingURL=EditorToolbarObserver.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,99 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import * as React from 'react';
import { useElementOnScreen } from '@strapi/admin/strapi-admin';
import { Menu, IconButton } from '@strapi/design-system';
import { More } from '@strapi/icons';
import { useIntl } from 'react-intl';
const ObservedToolbarComponent = ({ index, lastVisibleIndex, setLastVisibleIndex, rootRef, children })=>{
const isVisible = index <= lastVisibleIndex;
const containerRef = useElementOnScreen((isVisible)=>{
/**
* It's the MoreMenu's job to make an item not visible when there's not room for it.
* But we need to react here to the element becoming visible again.
*/ if (isVisible) {
setLastVisibleIndex((prev)=>Math.max(prev, index));
}
}, {
threshold: 1,
root: rootRef.current
});
return /*#__PURE__*/ jsx("div", {
ref: containerRef,
style: {
/**
* Use visibility so that the element occupies the space if requires even when there's not
* enough room for it to be visible. The empty reserved space will be clipped by the
* overflow:hidden rule on the parent, so it doesn't affect the UI.
* This way we can keep observing its visiblity and react to browser resize events.
*/ visibility: isVisible ? 'visible' : 'hidden'
},
children: children
});
};
const EditorToolbarObserver = ({ observedComponents, menuTriggerVariant = 'ghost' })=>{
const { formatMessage } = useIntl();
const toolbarRef = React.useRef(null);
const [lastVisibleIndex, setLastVisibleIndex] = React.useState(observedComponents.length - 1);
const hasHiddenItems = lastVisibleIndex < observedComponents.length - 1;
const menuIndex = lastVisibleIndex + 1;
const [open, setOpen] = React.useState(false);
const isMenuOpenWithContent = open && hasHiddenItems;
const menuTriggerRef = useElementOnScreen((isVisible)=>{
// We only react to the menu becoming invisible. When that happens, we hide the last item.
if (!isVisible) {
/**
* If there's no room for any item, the index can be -1.
* This is intentional, in that case only the more menu will be visible.
**/ setLastVisibleIndex((prev)=>prev - 1);
// Maintain the menu state if it has content
setOpen(isMenuOpenWithContent);
}
}, {
threshold: 1,
root: toolbarRef.current
});
return observedComponents.map((component, index)=>{
return /*#__PURE__*/ jsx(ObservedToolbarComponent, {
index: index,
lastVisibleIndex: lastVisibleIndex,
setLastVisibleIndex: setLastVisibleIndex,
rootRef: toolbarRef,
children: component.toolbar
}, component.key);
}).toSpliced(menuIndex, 0, /*#__PURE__*/ jsxs(Menu.Root, {
defaultOpen: false,
open: isMenuOpenWithContent,
onOpenChange: setOpen,
children: [
/*#__PURE__*/ jsx(Menu.Trigger, {
paddingLeft: 0,
paddingRight: 0,
ref: menuTriggerRef,
variant: menuTriggerVariant,
style: {
visibility: hasHiddenItems ? 'visible' : 'hidden'
},
label: formatMessage({
id: 'global.more',
defaultMessage: 'More'
}),
tag: IconButton,
icon: /*#__PURE__*/ jsx(More, {})
}),
/*#__PURE__*/ jsx(Menu.Content, {
onCloseAutoFocus: (e)=>e.preventDefault(),
maxHeight: "100%",
minWidth: "256px",
popoverPlacement: "bottom-end",
zIndex: 2,
children: observedComponents.slice(menuIndex).map((component)=>/*#__PURE__*/ jsx(React.Fragment, {
children: component.menu
}, component.key))
})
]
}, "more-menu"));
};
export { EditorToolbarObserver };
//# sourceMappingURL=EditorToolbarObserver.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,224 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var Prism = require('prismjs');
var reactIntl = require('react-intl');
var slate = require('slate');
var slateReact = require('slate-react');
var styledComponents = require('styled-components');
var BlocksEditor = require('../BlocksEditor.js');
var constants = require('../utils/constants.js');
var conversions = require('../utils/conversions.js');
var enterKey = require('../utils/enterKey.js');
require('prismjs/themes/prism-solarizedlight.css');
require('prismjs/components/prism-asmatmel');
require('prismjs/components/prism-bash');
require('prismjs/components/prism-basic');
require('prismjs/components/prism-c');
require('prismjs/components/prism-clojure');
require('prismjs/components/prism-cobol');
require('prismjs/components/prism-cpp');
require('prismjs/components/prism-csharp');
require('prismjs/components/prism-dart');
require('prismjs/components/prism-docker');
require('prismjs/components/prism-elixir');
require('prismjs/components/prism-erlang');
require('prismjs/components/prism-fortran');
require('prismjs/components/prism-fsharp');
require('prismjs/components/prism-go');
require('prismjs/components/prism-graphql');
require('prismjs/components/prism-groovy');
require('prismjs/components/prism-haskell');
require('prismjs/components/prism-haxe');
require('prismjs/components/prism-ini');
require('prismjs/components/prism-java');
require('prismjs/components/prism-javascript');
require('prismjs/components/prism-jsx');
require('prismjs/components/prism-json');
require('prismjs/components/prism-julia');
require('prismjs/components/prism-kotlin');
require('prismjs/components/prism-latex');
require('prismjs/components/prism-lua');
require('prismjs/components/prism-markdown');
require('prismjs/components/prism-matlab');
require('prismjs/components/prism-makefile');
require('prismjs/components/prism-objectivec');
require('prismjs/components/prism-perl');
require('prismjs/components/prism-php');
require('prismjs/components/prism-powershell');
require('prismjs/components/prism-python');
require('prismjs/components/prism-r');
require('prismjs/components/prism-ruby');
require('prismjs/components/prism-rust');
require('prismjs/components/prism-sas');
require('prismjs/components/prism-scala');
require('prismjs/components/prism-scheme');
require('prismjs/components/prism-sql');
require('prismjs/components/prism-stata');
require('prismjs/components/prism-swift');
require('prismjs/components/prism-typescript');
require('prismjs/components/prism-tsx');
require('prismjs/components/prism-vbnet');
require('prismjs/components/prism-yaml');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
var Prism__namespace = /*#__PURE__*/_interopNamespaceDefault(Prism);
const decorateCode = ([node, path])=>{
const ranges = [];
// make sure it is an Slate Element
if (!slate.Element.isElement(node) || node.type !== 'code') return ranges;
// transform the Element into a string
const text = slate.Node.string(node);
const language = constants.codeLanguages.find((lang)=>lang.value === node.language);
const decorateKey = language?.decorate ?? language?.value;
const selectedLanguage = Prism__namespace.languages[decorateKey || 'plaintext'];
// create "tokens" with "prismjs" and put them in "ranges"
const tokens = Prism__namespace.tokenize(text, selectedLanguage);
let start = 0;
for (const token of tokens){
const length = token.length;
const end = start + length;
if (typeof token !== 'string') {
ranges.push({
anchor: {
path,
offset: start
},
focus: {
path,
offset: end
},
className: `token ${token.type}`
});
}
start = end;
}
// these will be found in "renderLeaf" in "leaf" and their "className" will be applied
return ranges;
};
const CodeBlock = styledComponents.styled.pre`
border-radius: ${({ theme })=>theme.borderRadius};
background-color: ${({ theme })=>theme.colors.neutral100};
max-width: 100%;
overflow: auto;
padding: ${({ theme })=>`${theme.spaces[3]} ${theme.spaces[4]}`};
flex-shrink: 1;
& > code {
font-family: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas,
monospace;
color: ${({ theme })=>theme.colors.neutral800};
overflow: auto;
max-width: 100%;
}
`;
const CodeEditor = (props)=>{
const { editor } = BlocksEditor.useBlocksEditorContext('ImageDialog');
const editorIsFocused = slateReact.useFocused();
const imageIsSelected = slateReact.useSelected();
const { formatMessage } = reactIntl.useIntl();
const [isSelectOpen, setIsSelectOpen] = React__namespace.useState(false);
const shouldDisplayLanguageSelect = editorIsFocused && imageIsSelected || isSelectOpen;
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Box, {
position: "relative",
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(CodeBlock, {
...props.attributes,
children: /*#__PURE__*/ jsxRuntime.jsx("code", {
children: props.children
})
}),
shouldDisplayLanguageSelect && /*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
position: "absolute",
background: "neutral0",
borderColor: "neutral150",
borderStyle: "solid",
borderWidth: "0.5px",
shadow: "tableShadow",
top: "100%",
marginTop: 1,
right: 0,
padding: 1,
hasRadius: true,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelect, {
onChange: (open)=>{
slate.Transforms.setNodes(editor, {
language: open.toString()
}, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'code'
});
},
value: props.element.type === 'code' && props.element.language || 'plaintext',
onOpenChange: (open)=>{
setIsSelectOpen(open);
// Focus the editor again when closing the select so the user can continue typing
if (!open) {
slateReact.ReactEditor.focus(editor);
}
},
onCloseAutoFocus: (e)=>e.preventDefault(),
"aria-label": formatMessage({
id: 'components.Blocks.blocks.code.languageLabel',
defaultMessage: 'Select a language'
}),
children: constants.codeLanguages.map(({ value, label })=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelectOption, {
value: value,
children: label
}, value))
})
})
]
});
};
const codeBlocks = {
code: {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(CodeEditor, {
...props
}),
icon: Icons.CodeBlock,
label: {
id: 'components.Blocks.blocks.code',
defaultMessage: 'Code block'
},
matchNode: (node)=>node.type === 'code',
isInBlocksSelector: true,
handleConvert (editor) {
conversions.baseHandleConvert(editor, {
type: 'code',
language: 'plaintext'
});
},
handleEnterKey (editor) {
enterKey.pressEnterTwiceToExit(editor);
},
snippets: [
'```'
]
}
};
exports.codeBlocks = codeBlocks;
exports.decorateCode = decorateCode;
//# sourceMappingURL=Code.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,201 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import * as React from 'react';
import { Box, SingleSelect, SingleSelectOption } from '@strapi/design-system';
import { CodeBlock as CodeBlock$1 } from '@strapi/icons';
import * as Prism from 'prismjs';
import { useIntl } from 'react-intl';
import { Element, Node, Transforms, Editor } from 'slate';
import { useFocused, useSelected, ReactEditor } from 'slate-react';
import { styled } from 'styled-components';
import { useBlocksEditorContext } from '../BlocksEditor.mjs';
import { codeLanguages } from '../utils/constants.mjs';
import { baseHandleConvert } from '../utils/conversions.mjs';
import { pressEnterTwiceToExit } from '../utils/enterKey.mjs';
import 'prismjs/themes/prism-solarizedlight.css';
import 'prismjs/components/prism-asmatmel';
import 'prismjs/components/prism-bash';
import 'prismjs/components/prism-basic';
import 'prismjs/components/prism-c';
import 'prismjs/components/prism-clojure';
import 'prismjs/components/prism-cobol';
import 'prismjs/components/prism-cpp';
import 'prismjs/components/prism-csharp';
import 'prismjs/components/prism-dart';
import 'prismjs/components/prism-docker';
import 'prismjs/components/prism-elixir';
import 'prismjs/components/prism-erlang';
import 'prismjs/components/prism-fortran';
import 'prismjs/components/prism-fsharp';
import 'prismjs/components/prism-go';
import 'prismjs/components/prism-graphql';
import 'prismjs/components/prism-groovy';
import 'prismjs/components/prism-haskell';
import 'prismjs/components/prism-haxe';
import 'prismjs/components/prism-ini';
import 'prismjs/components/prism-java';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-jsx';
import 'prismjs/components/prism-json';
import 'prismjs/components/prism-julia';
import 'prismjs/components/prism-kotlin';
import 'prismjs/components/prism-latex';
import 'prismjs/components/prism-lua';
import 'prismjs/components/prism-markdown';
import 'prismjs/components/prism-matlab';
import 'prismjs/components/prism-makefile';
import 'prismjs/components/prism-objectivec';
import 'prismjs/components/prism-perl';
import 'prismjs/components/prism-php';
import 'prismjs/components/prism-powershell';
import 'prismjs/components/prism-python';
import 'prismjs/components/prism-r';
import 'prismjs/components/prism-ruby';
import 'prismjs/components/prism-rust';
import 'prismjs/components/prism-sas';
import 'prismjs/components/prism-scala';
import 'prismjs/components/prism-scheme';
import 'prismjs/components/prism-sql';
import 'prismjs/components/prism-stata';
import 'prismjs/components/prism-swift';
import 'prismjs/components/prism-typescript';
import 'prismjs/components/prism-tsx';
import 'prismjs/components/prism-vbnet';
import 'prismjs/components/prism-yaml';
const decorateCode = ([node, path])=>{
const ranges = [];
// make sure it is an Slate Element
if (!Element.isElement(node) || node.type !== 'code') return ranges;
// transform the Element into a string
const text = Node.string(node);
const language = codeLanguages.find((lang)=>lang.value === node.language);
const decorateKey = language?.decorate ?? language?.value;
const selectedLanguage = Prism.languages[decorateKey || 'plaintext'];
// create "tokens" with "prismjs" and put them in "ranges"
const tokens = Prism.tokenize(text, selectedLanguage);
let start = 0;
for (const token of tokens){
const length = token.length;
const end = start + length;
if (typeof token !== 'string') {
ranges.push({
anchor: {
path,
offset: start
},
focus: {
path,
offset: end
},
className: `token ${token.type}`
});
}
start = end;
}
// these will be found in "renderLeaf" in "leaf" and their "className" will be applied
return ranges;
};
const CodeBlock = styled.pre`
border-radius: ${({ theme })=>theme.borderRadius};
background-color: ${({ theme })=>theme.colors.neutral100};
max-width: 100%;
overflow: auto;
padding: ${({ theme })=>`${theme.spaces[3]} ${theme.spaces[4]}`};
flex-shrink: 1;
& > code {
font-family: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas,
monospace;
color: ${({ theme })=>theme.colors.neutral800};
overflow: auto;
max-width: 100%;
}
`;
const CodeEditor = (props)=>{
const { editor } = useBlocksEditorContext('ImageDialog');
const editorIsFocused = useFocused();
const imageIsSelected = useSelected();
const { formatMessage } = useIntl();
const [isSelectOpen, setIsSelectOpen] = React.useState(false);
const shouldDisplayLanguageSelect = editorIsFocused && imageIsSelected || isSelectOpen;
return /*#__PURE__*/ jsxs(Box, {
position: "relative",
width: "100%",
children: [
/*#__PURE__*/ jsx(CodeBlock, {
...props.attributes,
children: /*#__PURE__*/ jsx("code", {
children: props.children
})
}),
shouldDisplayLanguageSelect && /*#__PURE__*/ jsx(Box, {
position: "absolute",
background: "neutral0",
borderColor: "neutral150",
borderStyle: "solid",
borderWidth: "0.5px",
shadow: "tableShadow",
top: "100%",
marginTop: 1,
right: 0,
padding: 1,
hasRadius: true,
children: /*#__PURE__*/ jsx(SingleSelect, {
onChange: (open)=>{
Transforms.setNodes(editor, {
language: open.toString()
}, {
match: (node)=>!Editor.isEditor(node) && node.type === 'code'
});
},
value: props.element.type === 'code' && props.element.language || 'plaintext',
onOpenChange: (open)=>{
setIsSelectOpen(open);
// Focus the editor again when closing the select so the user can continue typing
if (!open) {
ReactEditor.focus(editor);
}
},
onCloseAutoFocus: (e)=>e.preventDefault(),
"aria-label": formatMessage({
id: 'components.Blocks.blocks.code.languageLabel',
defaultMessage: 'Select a language'
}),
children: codeLanguages.map(({ value, label })=>/*#__PURE__*/ jsx(SingleSelectOption, {
value: value,
children: label
}, value))
})
})
]
});
};
const codeBlocks = {
code: {
renderElement: (props)=>/*#__PURE__*/ jsx(CodeEditor, {
...props
}),
icon: CodeBlock$1,
label: {
id: 'components.Blocks.blocks.code',
defaultMessage: 'Code block'
},
matchNode: (node)=>node.type === 'code',
isInBlocksSelector: true,
handleConvert (editor) {
baseHandleConvert(editor, {
type: 'code',
language: 'plaintext'
});
},
handleEnterKey (editor) {
pressEnterTwiceToExit(editor);
},
snippets: [
'```'
]
}
};
export { codeBlocks, decorateCode };
//# sourceMappingURL=Code.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,165 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var styledComponents = require('styled-components');
var conversions = require('../utils/conversions.js');
const H1 = styledComponents.styled(designSystem.Typography).attrs({
tag: 'h1'
})`
font-size: 4.2rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
const H2 = styledComponents.styled(designSystem.Typography).attrs({
tag: 'h2'
})`
font-size: 3.5rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
const H3 = styledComponents.styled(designSystem.Typography).attrs({
tag: 'h3'
})`
font-size: 2.9rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
const H4 = styledComponents.styled(designSystem.Typography).attrs({
tag: 'h4'
})`
font-size: 2.4rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
const H5 = styledComponents.styled(designSystem.Typography).attrs({
tag: 'h5'
})`
font-size: 2rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
const H6 = styledComponents.styled(designSystem.Typography).attrs({
tag: 'h6'
})`
font-size: 1.6rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
/**
* Common handler for converting a node to a heading
*/ const handleConvertToHeading = (editor, level)=>{
conversions.baseHandleConvert(editor, {
type: 'heading',
level
});
};
const headingBlocks = {
'heading-one': {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(H1, {
...props.attributes,
children: props.children
}),
icon: Icons.HeadingOne,
label: {
id: 'components.Blocks.blocks.heading1',
defaultMessage: 'Heading 1'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 1),
matchNode: (node)=>node.type === 'heading' && node.level === 1,
isInBlocksSelector: true,
snippets: [
'#'
],
dragHandleTopMargin: '14px'
},
'heading-two': {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(H2, {
...props.attributes,
children: props.children
}),
icon: Icons.HeadingTwo,
label: {
id: 'components.Blocks.blocks.heading2',
defaultMessage: 'Heading 2'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 2),
matchNode: (node)=>node.type === 'heading' && node.level === 2,
isInBlocksSelector: true,
snippets: [
'##'
],
dragHandleTopMargin: '10px'
},
'heading-three': {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(H3, {
...props.attributes,
children: props.children
}),
icon: Icons.HeadingThree,
label: {
id: 'components.Blocks.blocks.heading3',
defaultMessage: 'Heading 3'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 3),
matchNode: (node)=>node.type === 'heading' && node.level === 3,
isInBlocksSelector: true,
snippets: [
'###'
],
dragHandleTopMargin: '7px'
},
'heading-four': {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(H4, {
...props.attributes,
children: props.children
}),
icon: Icons.HeadingFour,
label: {
id: 'components.Blocks.blocks.heading4',
defaultMessage: 'Heading 4'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 4),
matchNode: (node)=>node.type === 'heading' && node.level === 4,
isInBlocksSelector: true,
snippets: [
'####'
],
dragHandleTopMargin: '4px'
},
'heading-five': {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(H5, {
...props.attributes,
children: props.children
}),
icon: Icons.HeadingFive,
label: {
id: 'components.Blocks.blocks.heading5',
defaultMessage: 'Heading 5'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 5),
matchNode: (node)=>node.type === 'heading' && node.level === 5,
isInBlocksSelector: true,
snippets: [
'#####'
]
},
'heading-six': {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(H6, {
...props.attributes,
children: props.children
}),
icon: Icons.HeadingSix,
label: {
id: 'components.Blocks.blocks.heading6',
defaultMessage: 'Heading 6'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 6),
matchNode: (node)=>node.type === 'heading' && node.level === 6,
isInBlocksSelector: true,
snippets: [
'######'
],
dragHandleTopMargin: '-2px'
}
};
exports.headingBlocks = headingBlocks;
//# sourceMappingURL=Heading.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,163 @@
import { jsx } from 'react/jsx-runtime';
import 'react';
import { Typography } from '@strapi/design-system';
import { HeadingOne, HeadingTwo, HeadingThree, HeadingFour, HeadingFive, HeadingSix } from '@strapi/icons';
import { styled } from 'styled-components';
import { baseHandleConvert } from '../utils/conversions.mjs';
const H1 = styled(Typography).attrs({
tag: 'h1'
})`
font-size: 4.2rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
const H2 = styled(Typography).attrs({
tag: 'h2'
})`
font-size: 3.5rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
const H3 = styled(Typography).attrs({
tag: 'h3'
})`
font-size: 2.9rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
const H4 = styled(Typography).attrs({
tag: 'h4'
})`
font-size: 2.4rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
const H5 = styled(Typography).attrs({
tag: 'h5'
})`
font-size: 2rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
const H6 = styled(Typography).attrs({
tag: 'h6'
})`
font-size: 1.6rem;
line-height: ${({ theme })=>theme.lineHeights[1]};
`;
/**
* Common handler for converting a node to a heading
*/ const handleConvertToHeading = (editor, level)=>{
baseHandleConvert(editor, {
type: 'heading',
level
});
};
const headingBlocks = {
'heading-one': {
renderElement: (props)=>/*#__PURE__*/ jsx(H1, {
...props.attributes,
children: props.children
}),
icon: HeadingOne,
label: {
id: 'components.Blocks.blocks.heading1',
defaultMessage: 'Heading 1'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 1),
matchNode: (node)=>node.type === 'heading' && node.level === 1,
isInBlocksSelector: true,
snippets: [
'#'
],
dragHandleTopMargin: '14px'
},
'heading-two': {
renderElement: (props)=>/*#__PURE__*/ jsx(H2, {
...props.attributes,
children: props.children
}),
icon: HeadingTwo,
label: {
id: 'components.Blocks.blocks.heading2',
defaultMessage: 'Heading 2'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 2),
matchNode: (node)=>node.type === 'heading' && node.level === 2,
isInBlocksSelector: true,
snippets: [
'##'
],
dragHandleTopMargin: '10px'
},
'heading-three': {
renderElement: (props)=>/*#__PURE__*/ jsx(H3, {
...props.attributes,
children: props.children
}),
icon: HeadingThree,
label: {
id: 'components.Blocks.blocks.heading3',
defaultMessage: 'Heading 3'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 3),
matchNode: (node)=>node.type === 'heading' && node.level === 3,
isInBlocksSelector: true,
snippets: [
'###'
],
dragHandleTopMargin: '7px'
},
'heading-four': {
renderElement: (props)=>/*#__PURE__*/ jsx(H4, {
...props.attributes,
children: props.children
}),
icon: HeadingFour,
label: {
id: 'components.Blocks.blocks.heading4',
defaultMessage: 'Heading 4'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 4),
matchNode: (node)=>node.type === 'heading' && node.level === 4,
isInBlocksSelector: true,
snippets: [
'####'
],
dragHandleTopMargin: '4px'
},
'heading-five': {
renderElement: (props)=>/*#__PURE__*/ jsx(H5, {
...props.attributes,
children: props.children
}),
icon: HeadingFive,
label: {
id: 'components.Blocks.blocks.heading5',
defaultMessage: 'Heading 5'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 5),
matchNode: (node)=>node.type === 'heading' && node.level === 5,
isInBlocksSelector: true,
snippets: [
'#####'
]
},
'heading-six': {
renderElement: (props)=>/*#__PURE__*/ jsx(H6, {
...props.attributes,
children: props.children
}),
icon: HeadingSix,
label: {
id: 'components.Blocks.blocks.heading6',
defaultMessage: 'Heading 6'
},
handleConvert: (editor)=>handleConvertToHeading(editor, 6),
matchNode: (node)=>node.type === 'heading' && node.level === 6,
isInBlocksSelector: true,
snippets: [
'######'
],
dragHandleTopMargin: '-2px'
}
};
export { headingBlocks };
//# sourceMappingURL=Heading.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,230 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var slate = require('slate');
var slateReact = require('slate-react');
var styledComponents = require('styled-components');
var urls = require('../../../../../../utils/urls.js');
var BlocksEditor = require('../BlocksEditor.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const ImageWrapper = styledComponents.styled(designSystem.Flex)`
transition-property: box-shadow;
transition-duration: 0.2s;
${(props)=>props.$isFocused && styledComponents.css`
box-shadow: ${props.theme.colors.primary600} 0px 0px 0px 3px;
`}
& > img {
height: auto;
// The max-height is decided with the design team, the 56px is the height of the toolbar
max-height: calc(512px - 56px);
max-width: 100%;
object-fit: contain;
}
`;
const IMAGE_SCHEMA_FIELDS = [
'name',
'alternativeText',
'url',
'caption',
'width',
'height',
'formats',
'hash',
'ext',
'mime',
'size',
'previewUrl',
'provider',
'provider_metadata',
'createdAt',
'updatedAt'
];
const pick = (object, keys)=>{
const entries = keys.map((key)=>[
key,
object[key]
]);
return Object.fromEntries(entries);
};
// Type guard to force TypeScript to narrow the type of the element in Blocks component
const isImage = (element)=>{
return element.type === 'image';
};
// Added a background color to the image wrapper to make it easier to recognize the image block
const Image = ({ attributes, children, element })=>{
const editorIsFocused = slateReact.useFocused();
const imageIsSelected = slateReact.useSelected();
if (!isImage(element)) {
return null;
}
const { url, alternativeText, width, height } = element.image;
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Box, {
...attributes,
children: [
children,
/*#__PURE__*/ jsxRuntime.jsx(ImageWrapper, {
background: "neutral100",
contentEditable: false,
justifyContent: "center",
$isFocused: editorIsFocused && imageIsSelected,
hasRadius: true,
children: /*#__PURE__*/ jsxRuntime.jsx("img", {
src: url,
alt: alternativeText,
width: width,
height: height
})
})
]
});
};
const ImageDialog = ()=>{
const [isOpen, setIsOpen] = React__namespace.useState(true);
const { editor } = BlocksEditor.useBlocksEditorContext('ImageDialog');
const components = strapiAdmin.useStrapiApp('ImageDialog', (state)=>state.components);
if (!components || !isOpen) return null;
const MediaLibraryDialog = components['media-library'];
const insertImages = (images)=>{
// If the selection is inside a list, split the list so that the modified block is outside of it
slate.Transforms.unwrapNodes(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list',
split: true
});
// Save the path of the node that is being replaced by an image to insert the images there later
// It's the closest full block node above the selection
const nodeEntryBeingReplaced = slate.Editor.above(editor, {
match (node) {
if (slate.Editor.isEditor(node)) return false;
const isInlineNode = [
'text',
'link'
].includes(node.type);
return !isInlineNode;
}
});
if (!nodeEntryBeingReplaced) return;
const [, pathToInsert] = nodeEntryBeingReplaced;
// Remove the previous node that is being replaced by an image
slate.Transforms.removeNodes(editor);
// Convert images to nodes and insert them
const nodesToInsert = images.map((image)=>{
const imageNode = {
type: 'image',
image,
children: [
{
type: 'text',
text: ''
}
]
};
return imageNode;
});
slate.Transforms.insertNodes(editor, nodesToInsert, {
at: pathToInsert
});
// Set the selection on the image since it was cleared by calling removeNodes
slate.Transforms.select(editor, pathToInsert);
};
const handleSelectAssets = (images)=>{
const formattedImages = images.map((image)=>{
// Create an object with imageSchema defined and exclude unnecessary props coming from media library config
const expectedImage = pick(image, IMAGE_SCHEMA_FIELDS);
const nodeImage = {
...expectedImage,
alternativeText: expectedImage.alternativeText || expectedImage.name,
url: urls.prefixFileUrlWithBackendUrl(image.url)
};
return nodeImage;
});
insertImages(formattedImages);
setIsOpen(false);
};
return /*#__PURE__*/ jsxRuntime.jsx(MediaLibraryDialog, {
allowedTypes: [
'images'
],
onClose: ()=>setIsOpen(false),
onSelectAssets: handleSelectAssets
});
};
const imageBlocks = {
image: {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(Image, {
...props
}),
icon: Icons.Image,
label: {
id: 'components.Blocks.blocks.image',
defaultMessage: 'Image'
},
matchNode: (node)=>node.type === 'image',
isInBlocksSelector: true,
handleBackspaceKey (editor) {
// Prevent issue where the image remains when it's the only block in the document
if (editor.children.length === 1) {
slate.Transforms.setNodes(editor, {
type: 'paragraph',
// @ts-expect-error we're only setting image as null so that Slate deletes it
image: null,
children: [
{
type: 'text',
text: ''
}
]
});
} else {
slate.Transforms.removeNodes(editor);
}
},
handleEnterKey (editor) {
slate.Transforms.insertNodes(editor, {
type: 'paragraph',
children: [
{
type: 'text',
text: ''
}
]
});
},
handleConvert: ()=>{
/**
* All the logic is managed inside the ImageDialog component,
* because the blocks are only created when the user selects images in the modal and submits
* and if he closes the modal, then no changes are made to the editor
*/ return ()=>/*#__PURE__*/ jsxRuntime.jsx(ImageDialog, {});
},
snippets: [
'!['
]
}
};
exports.imageBlocks = imageBlocks;
//# sourceMappingURL=Image.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,209 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import * as React from 'react';
import { useStrapiApp } from '@strapi/admin/strapi-admin';
import { Flex, Box } from '@strapi/design-system';
import { Image as Image$1 } from '@strapi/icons';
import { Transforms, Editor } from 'slate';
import { useFocused, useSelected } from 'slate-react';
import { styled, css } from 'styled-components';
import { prefixFileUrlWithBackendUrl } from '../../../../../../utils/urls.mjs';
import { useBlocksEditorContext } from '../BlocksEditor.mjs';
const ImageWrapper = styled(Flex)`
transition-property: box-shadow;
transition-duration: 0.2s;
${(props)=>props.$isFocused && css`
box-shadow: ${props.theme.colors.primary600} 0px 0px 0px 3px;
`}
& > img {
height: auto;
// The max-height is decided with the design team, the 56px is the height of the toolbar
max-height: calc(512px - 56px);
max-width: 100%;
object-fit: contain;
}
`;
const IMAGE_SCHEMA_FIELDS = [
'name',
'alternativeText',
'url',
'caption',
'width',
'height',
'formats',
'hash',
'ext',
'mime',
'size',
'previewUrl',
'provider',
'provider_metadata',
'createdAt',
'updatedAt'
];
const pick = (object, keys)=>{
const entries = keys.map((key)=>[
key,
object[key]
]);
return Object.fromEntries(entries);
};
// Type guard to force TypeScript to narrow the type of the element in Blocks component
const isImage = (element)=>{
return element.type === 'image';
};
// Added a background color to the image wrapper to make it easier to recognize the image block
const Image = ({ attributes, children, element })=>{
const editorIsFocused = useFocused();
const imageIsSelected = useSelected();
if (!isImage(element)) {
return null;
}
const { url, alternativeText, width, height } = element.image;
return /*#__PURE__*/ jsxs(Box, {
...attributes,
children: [
children,
/*#__PURE__*/ jsx(ImageWrapper, {
background: "neutral100",
contentEditable: false,
justifyContent: "center",
$isFocused: editorIsFocused && imageIsSelected,
hasRadius: true,
children: /*#__PURE__*/ jsx("img", {
src: url,
alt: alternativeText,
width: width,
height: height
})
})
]
});
};
const ImageDialog = ()=>{
const [isOpen, setIsOpen] = React.useState(true);
const { editor } = useBlocksEditorContext('ImageDialog');
const components = useStrapiApp('ImageDialog', (state)=>state.components);
if (!components || !isOpen) return null;
const MediaLibraryDialog = components['media-library'];
const insertImages = (images)=>{
// If the selection is inside a list, split the list so that the modified block is outside of it
Transforms.unwrapNodes(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'list',
split: true
});
// Save the path of the node that is being replaced by an image to insert the images there later
// It's the closest full block node above the selection
const nodeEntryBeingReplaced = Editor.above(editor, {
match (node) {
if (Editor.isEditor(node)) return false;
const isInlineNode = [
'text',
'link'
].includes(node.type);
return !isInlineNode;
}
});
if (!nodeEntryBeingReplaced) return;
const [, pathToInsert] = nodeEntryBeingReplaced;
// Remove the previous node that is being replaced by an image
Transforms.removeNodes(editor);
// Convert images to nodes and insert them
const nodesToInsert = images.map((image)=>{
const imageNode = {
type: 'image',
image,
children: [
{
type: 'text',
text: ''
}
]
};
return imageNode;
});
Transforms.insertNodes(editor, nodesToInsert, {
at: pathToInsert
});
// Set the selection on the image since it was cleared by calling removeNodes
Transforms.select(editor, pathToInsert);
};
const handleSelectAssets = (images)=>{
const formattedImages = images.map((image)=>{
// Create an object with imageSchema defined and exclude unnecessary props coming from media library config
const expectedImage = pick(image, IMAGE_SCHEMA_FIELDS);
const nodeImage = {
...expectedImage,
alternativeText: expectedImage.alternativeText || expectedImage.name,
url: prefixFileUrlWithBackendUrl(image.url)
};
return nodeImage;
});
insertImages(formattedImages);
setIsOpen(false);
};
return /*#__PURE__*/ jsx(MediaLibraryDialog, {
allowedTypes: [
'images'
],
onClose: ()=>setIsOpen(false),
onSelectAssets: handleSelectAssets
});
};
const imageBlocks = {
image: {
renderElement: (props)=>/*#__PURE__*/ jsx(Image, {
...props
}),
icon: Image$1,
label: {
id: 'components.Blocks.blocks.image',
defaultMessage: 'Image'
},
matchNode: (node)=>node.type === 'image',
isInBlocksSelector: true,
handleBackspaceKey (editor) {
// Prevent issue where the image remains when it's the only block in the document
if (editor.children.length === 1) {
Transforms.setNodes(editor, {
type: 'paragraph',
// @ts-expect-error we're only setting image as null so that Slate deletes it
image: null,
children: [
{
type: 'text',
text: ''
}
]
});
} else {
Transforms.removeNodes(editor);
}
},
handleEnterKey (editor) {
Transforms.insertNodes(editor, {
type: 'paragraph',
children: [
{
type: 'text',
text: ''
}
]
});
},
handleConvert: ()=>{
/**
* All the logic is managed inside the ImageDialog component,
* because the blocks are only created when the user selects images in the modal and submits
* and if he closes the modal, then no changes are made to the editor
*/ return ()=>/*#__PURE__*/ jsx(ImageDialog, {});
},
snippets: [
'!['
]
}
};
export { imageBlocks };
//# sourceMappingURL=Image.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,232 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var designSystem = require('@strapi/design-system');
var reactIntl = require('react-intl');
var slate = require('slate');
var slateReact = require('slate-react');
var styledComponents = require('styled-components');
var BlocksEditor = require('../BlocksEditor.js');
var links = require('../utils/links.js');
var types = require('../utils/types.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const StyledLink = styledComponents.styled(designSystem.Box)`
text-decoration: none;
`;
const RemoveButton = styledComponents.styled(designSystem.Button)`
visibility: ${(props)=>props.$visible ? 'visible' : 'hidden'};
`;
const LinkContent = /*#__PURE__*/ React__namespace.forwardRef(({ link, children, attributes }, forwardedRef)=>{
const { formatMessage } = reactIntl.useIntl();
const { editor } = BlocksEditor.useBlocksEditorContext('Link');
const path = slateReact.ReactEditor.findPath(editor, link);
const [popoverOpen, setPopoverOpen] = React__namespace.useState(editor.lastInsertedLinkPath ? slate.Path.equals(path, editor.lastInsertedLinkPath) : false);
const elementText = link.children.map((child)=>child.text).join('');
const [linkText, setLinkText] = React__namespace.useState(elementText);
const [linkUrl, setLinkUrl] = React__namespace.useState(link.url);
const linkInputRef = React__namespace.useRef(null);
const isLastInsertedLink = editor.lastInsertedLinkPath ? !slate.Path.equals(path, editor.lastInsertedLinkPath) : true;
const [isSaveDisabled, setIsSaveDisabled] = React__namespace.useState(false);
const onLinkChange = (e)=>{
setIsSaveDisabled(false);
setLinkUrl(e.target.value);
try {
// eslint-disable-next-line no-new
new URL(e.target.value?.startsWith('/') ? `https://strapi.io${e.target.value}` : e.target.value);
} catch (error) {
setIsSaveDisabled(true);
}
};
const handleSave = (e)=>{
e.stopPropagation();
// If the selection is collapsed, we select the parent node because we want all the link to be replaced)
if (editor.selection && slate.Range.isCollapsed(editor.selection)) {
const [, parentPath] = slate.Editor.parent(editor, editor.selection.focus?.path);
slate.Transforms.select(editor, parentPath);
}
links.editLink(editor, {
url: linkUrl,
text: linkText
});
setPopoverOpen(false);
editor.lastInsertedLinkPath = null;
slateReact.ReactEditor.focus(editor);
};
const handleClose = ()=>{
if (link.url === '') {
links.removeLink(editor);
}
setPopoverOpen(false);
slateReact.ReactEditor.focus(editor);
};
React__namespace.useEffect(()=>{
// Focus on the link input element when the popover opens
if (popoverOpen) linkInputRef.current?.focus();
}, [
popoverOpen
]);
const inputNotDirty = !linkText || !linkUrl || link.url && link.url === linkUrl && elementText && elementText === linkText;
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Popover.Root, {
open: popoverOpen,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(StyledLink, {
...attributes,
ref: forwardedRef,
tag: "a",
href: link.url,
onClick: ()=>setPopoverOpen(true),
color: "primary600",
children: children
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Content, {
onPointerDownOutside: handleClose,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
padding: 4,
direction: "column",
gap: 4,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Root, {
width: "368px",
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
gap: 1,
alignItems: "stretch",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'components.Blocks.popover.text',
defaultMessage: 'Text'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Input, {
name: "text",
placeholder: formatMessage({
id: 'components.Blocks.popover.text.placeholder',
defaultMessage: 'Enter link text'
}),
value: linkText,
onChange: (e)=>{
setLinkText(e.target.value);
}
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Root, {
width: "368px",
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
gap: 1,
alignItems: "stretch",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'components.Blocks.popover.link',
defaultMessage: 'Link'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Input, {
ref: linkInputRef,
name: "url",
placeholder: formatMessage({
id: 'components.Blocks.popover.link.placeholder',
defaultMessage: 'Paste link'
}),
value: linkUrl,
onChange: onLinkChange
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
justifyContent: "space-between",
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(RemoveButton, {
variant: "danger-light",
onClick: ()=>links.removeLink(editor),
$visible: isLastInsertedLink,
children: formatMessage({
id: 'components.Blocks.popover.remove',
defaultMessage: 'Remove'
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "tertiary",
onClick: handleClose,
children: formatMessage({
id: 'global.cancel',
defaultMessage: 'Cancel'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
disabled: Boolean(inputNotDirty) || isSaveDisabled,
onClick: handleSave,
children: formatMessage({
id: 'global.save',
defaultMessage: 'Save'
})
})
]
})
]
})
]
})
})
]
});
});
const Link = /*#__PURE__*/ React__namespace.forwardRef((props, forwardedRef)=>{
if (!types.isLinkNode(props.element)) {
return null;
}
// LinkContent uses React hooks that rely on props.element being a link. If the type guard above
// doesn't pass, those hooks would be called conditionnally, which is not allowed.
// Hence the need for a separate component.
return /*#__PURE__*/ jsxRuntime.jsx(LinkContent, {
...props,
link: props.element,
ref: forwardedRef
});
});
const linkBlocks = {
link: {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(Link, {
element: props.element,
attributes: props.attributes,
children: props.children
}),
// No handleConvert here, links are created via the link button in the toolbar
matchNode: (node)=>node.type === 'link',
isInBlocksSelector: false
}
};
exports.linkBlocks = linkBlocks;
//# sourceMappingURL=Link.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,211 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import * as React from 'react';
import { Box, Button, Popover, Flex, Field } from '@strapi/design-system';
import { useIntl } from 'react-intl';
import { Path, Range, Editor, Transforms } from 'slate';
import { ReactEditor } from 'slate-react';
import { styled } from 'styled-components';
import { useBlocksEditorContext } from '../BlocksEditor.mjs';
import { removeLink, editLink } from '../utils/links.mjs';
import { isLinkNode } from '../utils/types.mjs';
const StyledLink = styled(Box)`
text-decoration: none;
`;
const RemoveButton = styled(Button)`
visibility: ${(props)=>props.$visible ? 'visible' : 'hidden'};
`;
const LinkContent = /*#__PURE__*/ React.forwardRef(({ link, children, attributes }, forwardedRef)=>{
const { formatMessage } = useIntl();
const { editor } = useBlocksEditorContext('Link');
const path = ReactEditor.findPath(editor, link);
const [popoverOpen, setPopoverOpen] = React.useState(editor.lastInsertedLinkPath ? Path.equals(path, editor.lastInsertedLinkPath) : false);
const elementText = link.children.map((child)=>child.text).join('');
const [linkText, setLinkText] = React.useState(elementText);
const [linkUrl, setLinkUrl] = React.useState(link.url);
const linkInputRef = React.useRef(null);
const isLastInsertedLink = editor.lastInsertedLinkPath ? !Path.equals(path, editor.lastInsertedLinkPath) : true;
const [isSaveDisabled, setIsSaveDisabled] = React.useState(false);
const onLinkChange = (e)=>{
setIsSaveDisabled(false);
setLinkUrl(e.target.value);
try {
// eslint-disable-next-line no-new
new URL(e.target.value?.startsWith('/') ? `https://strapi.io${e.target.value}` : e.target.value);
} catch (error) {
setIsSaveDisabled(true);
}
};
const handleSave = (e)=>{
e.stopPropagation();
// If the selection is collapsed, we select the parent node because we want all the link to be replaced)
if (editor.selection && Range.isCollapsed(editor.selection)) {
const [, parentPath] = Editor.parent(editor, editor.selection.focus?.path);
Transforms.select(editor, parentPath);
}
editLink(editor, {
url: linkUrl,
text: linkText
});
setPopoverOpen(false);
editor.lastInsertedLinkPath = null;
ReactEditor.focus(editor);
};
const handleClose = ()=>{
if (link.url === '') {
removeLink(editor);
}
setPopoverOpen(false);
ReactEditor.focus(editor);
};
React.useEffect(()=>{
// Focus on the link input element when the popover opens
if (popoverOpen) linkInputRef.current?.focus();
}, [
popoverOpen
]);
const inputNotDirty = !linkText || !linkUrl || link.url && link.url === linkUrl && elementText && elementText === linkText;
return /*#__PURE__*/ jsxs(Popover.Root, {
open: popoverOpen,
children: [
/*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(StyledLink, {
...attributes,
ref: forwardedRef,
tag: "a",
href: link.url,
onClick: ()=>setPopoverOpen(true),
color: "primary600",
children: children
})
}),
/*#__PURE__*/ jsx(Popover.Content, {
onPointerDownOutside: handleClose,
children: /*#__PURE__*/ jsxs(Flex, {
padding: 4,
direction: "column",
gap: 4,
children: [
/*#__PURE__*/ jsx(Field.Root, {
width: "368px",
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
gap: 1,
alignItems: "stretch",
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'components.Blocks.popover.text',
defaultMessage: 'Text'
})
}),
/*#__PURE__*/ jsx(Field.Input, {
name: "text",
placeholder: formatMessage({
id: 'components.Blocks.popover.text.placeholder',
defaultMessage: 'Enter link text'
}),
value: linkText,
onChange: (e)=>{
setLinkText(e.target.value);
}
})
]
})
}),
/*#__PURE__*/ jsx(Field.Root, {
width: "368px",
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
gap: 1,
alignItems: "stretch",
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'components.Blocks.popover.link',
defaultMessage: 'Link'
})
}),
/*#__PURE__*/ jsx(Field.Input, {
ref: linkInputRef,
name: "url",
placeholder: formatMessage({
id: 'components.Blocks.popover.link.placeholder',
defaultMessage: 'Paste link'
}),
value: linkUrl,
onChange: onLinkChange
})
]
})
}),
/*#__PURE__*/ jsxs(Flex, {
justifyContent: "space-between",
width: "100%",
children: [
/*#__PURE__*/ jsx(RemoveButton, {
variant: "danger-light",
onClick: ()=>removeLink(editor),
$visible: isLastInsertedLink,
children: formatMessage({
id: 'components.Blocks.popover.remove',
defaultMessage: 'Remove'
})
}),
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Button, {
variant: "tertiary",
onClick: handleClose,
children: formatMessage({
id: 'global.cancel',
defaultMessage: 'Cancel'
})
}),
/*#__PURE__*/ jsx(Button, {
disabled: Boolean(inputNotDirty) || isSaveDisabled,
onClick: handleSave,
children: formatMessage({
id: 'global.save',
defaultMessage: 'Save'
})
})
]
})
]
})
]
})
})
]
});
});
const Link = /*#__PURE__*/ React.forwardRef((props, forwardedRef)=>{
if (!isLinkNode(props.element)) {
return null;
}
// LinkContent uses React hooks that rely on props.element being a link. If the type guard above
// doesn't pass, those hooks would be called conditionnally, which is not allowed.
// Hence the need for a separate component.
return /*#__PURE__*/ jsx(LinkContent, {
...props,
link: props.element,
ref: forwardedRef
});
});
const linkBlocks = {
link: {
renderElement: (props)=>/*#__PURE__*/ jsx(Link, {
element: props.element,
attributes: props.attributes,
children: props.children
}),
// No handleConvert here, links are created via the link button in the toolbar
matchNode: (node)=>node.type === 'link',
isInBlocksSelector: false
}
};
export { linkBlocks };
//# sourceMappingURL=Link.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,360 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var slate = require('slate');
var slateReact = require('slate-react');
var styledComponents = require('styled-components');
var conversions = require('../utils/conversions.js');
var types = require('../utils/types.js');
const listStyle = styledComponents.css`
display: flex;
flex-direction: column;
gap: ${({ theme })=>theme.spaces[2]};
margin-inline-start: ${({ theme })=>theme.spaces[0]};
margin-inline-end: ${({ theme })=>theme.spaces[0]};
padding-inline-start: ${({ theme })=>theme.spaces[2]};
ol,
ul {
margin-block-start: ${({ theme })=>theme.spaces[0]};
margin-block-end: ${({ theme })=>theme.spaces[0]};
}
li {
margin-inline-start: ${({ theme })=>theme.spaces[3]};
}
`;
const Orderedlist = styledComponents.styled.ol`
list-style-type: ${(props)=>props.$listStyleType};
${listStyle}
`;
const Unorderedlist = styledComponents.styled.ul`
list-style-type: ${(props)=>props.$listStyleType};
${listStyle}
`;
const orderedStyles = [
'decimal',
'lower-alpha',
'upper-roman'
];
const unorderedStyles = [
'disc',
'circle',
'square'
];
const List = ({ attributes, children, element })=>{
if (!types.isListNode(element)) {
return null;
}
// Decide the subsequent style by referencing the given styles according to the format,
// allowing for infinite nested lists
const listStyles = element.format === 'ordered' ? orderedStyles : unorderedStyles;
const nextIndex = (element.indentLevel || 0) % listStyles.length;
const listStyleType = listStyles[nextIndex];
if (element.format === 'ordered') {
return /*#__PURE__*/ jsxRuntime.jsx(Orderedlist, {
$listStyleType: listStyleType,
...attributes,
children: children
});
}
return /*#__PURE__*/ jsxRuntime.jsx(Unorderedlist, {
$listStyleType: listStyleType,
...attributes,
children: children
});
};
const replaceListWithEmptyBlock = (editor, currentListPath)=>{
// Delete the empty list
slate.Transforms.removeNodes(editor, {
at: currentListPath
});
if (currentListPath[0] === 0) {
// If the list was the only (or first) block element then insert empty paragraph as editor needs default value
slate.Transforms.insertNodes(editor, {
type: 'paragraph',
children: [
{
type: 'text',
text: ''
}
]
}, {
at: currentListPath
});
slate.Transforms.select(editor, currentListPath);
}
};
const isText = (node)=>{
return slate.Node.isNode(node) && !slate.Editor.isEditor(node) && node.type === 'text';
};
/**
* Common handler for the backspace event on ordered and unordered lists
*/ const handleBackspaceKeyOnList = (editor, event)=>{
if (!editor.selection) return;
const [currentListItem, currentListItemPath] = slate.Editor.parent(editor, editor.selection.anchor);
const [currentList, currentListPath] = slate.Editor.parent(editor, currentListItemPath);
const isListEmpty = currentList.children.length === 1 && isText(currentListItem.children[0]) && currentListItem.children[0].text === '';
const isListItemEmpty = currentListItem.children.length === 1 && isText(currentListItem.children[0]) && currentListItem.children[0].text === '';
const isFocusAtTheBeginningOfAChild = editor.selection.focus.offset === 0 && editor.selection.focus.path.at(-2) === 0;
if (isListEmpty) {
const parentListEntry = slate.Editor.above(editor, {
at: currentListPath,
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list'
});
if (!parentListEntry) {
event.preventDefault();
replaceListWithEmptyBlock(editor, currentListPath);
}
} else if (isFocusAtTheBeginningOfAChild) {
// If the focus is at the beginning of a child node we need to replace it with a paragraph
slate.Transforms.liftNodes(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list-item'
});
slate.Transforms.setNodes(editor, {
type: 'paragraph'
});
} else if (isListItemEmpty) {
const previousEntry = slate.Editor.previous(editor, {
at: currentListItemPath
});
const nextEntry = slate.Editor.next(editor, {
at: currentListItemPath
});
if (previousEntry && nextEntry) {
// If previous and next nodes are lists or list-items, delete empty list item
event.preventDefault();
slate.Transforms.removeNodes(editor, {
at: currentListItemPath
});
// If previous and next nodes are lists with same format and indent Levels, then merge the nodes
const [previousList] = previousEntry;
const [nextList] = nextEntry;
if (!slate.Editor.isEditor(previousList) && !isText(previousList) && types.isListNode(previousList) && !slate.Editor.isEditor(nextList) && !isText(nextList) && types.isListNode(nextList)) {
if (previousList.type === 'list' && nextList.type === 'list' && previousList.format === nextList.format && previousList.indentLevel === nextList.indentLevel) {
slate.Transforms.mergeNodes(editor, {
at: currentListItemPath
});
}
}
}
}
};
/**
* Common handler for the enter key on ordered and unordered lists
*/ const handleEnterKeyOnList = (editor)=>{
const currentListItemEntry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list-item'
});
if (!currentListItemEntry || !editor.selection) {
return;
}
const [currentListItem, currentListItemPath] = currentListItemEntry;
const [currentList, currentListPath] = slate.Editor.parent(editor, currentListItemPath);
const isListEmpty = currentList.children.length === 1 && isText(currentListItem.children[0]) && currentListItem.children[0].text === '';
const isListItemEmpty = currentListItem.children.length === 1 && isText(currentListItem.children[0]) && currentListItem.children[0].text === '';
const isFocusAtTheBeginningOfAChild = editor.selection.focus.offset === 0 && editor.selection.focus.path.at(-1) === 0;
if (isListEmpty) {
replaceListWithEmptyBlock(editor, currentListPath);
} else if (isFocusAtTheBeginningOfAChild && !isListItemEmpty) {
// If the focus is at the beginning of a child node, shift below the list item and create a new list-item
const currentNode = slate.Editor.above(editor, {
at: editor.selection.anchor
});
slate.Transforms.insertNodes(editor, {
type: 'list-item',
children: [
{
type: 'text',
text: ''
}
]
});
if (currentNode) {
const path = currentNode[1];
const updatedPath = [
...path.slice(0, -1),
path[path.length - 1] + 1
];
slate.Transforms.select(editor, {
anchor: {
path: updatedPath.concat(0),
offset: 0
},
focus: {
path: updatedPath.concat(0),
offset: 0
}
});
}
} else if (isListItemEmpty) {
// Check if there is a list above the current list and shift list-item under it
if (!slate.Editor.isEditor(currentList) && types.isListNode(currentList) && currentList?.indentLevel && currentList.indentLevel > 0) {
const previousIndentLevel = currentList.indentLevel - 1;
const parentListNodeEntry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list' && (node.indentLevel || 0) === previousIndentLevel
});
if (parentListNodeEntry) {
// Get the parent list path and add 1 to it to exit from the current list
const modifiedPath = currentListItemPath.slice(0, -1);
if (modifiedPath.length > 0) {
modifiedPath[modifiedPath.length - 1] += 1;
}
// Shift list-item under parent list
slate.Transforms.moveNodes(editor, {
at: currentListItemPath,
to: modifiedPath
});
return;
}
}
// Otherwise delete the empty list item and create a new paragraph below the parent list
slate.Transforms.removeNodes(editor, {
at: currentListItemPath
});
const createdParagraphPath = slate.Path.next(currentListPath);
slate.Transforms.insertNodes(editor, {
type: 'paragraph',
children: [
{
type: 'text',
text: ''
}
]
}, {
at: createdParagraphPath
});
// Move the selection to the newly created paragraph
slate.Transforms.select(editor, createdParagraphPath);
} else {
// Check if the cursor is at the end of the list item
const isNodeEnd = slate.Editor.isEnd(editor, editor.selection.anchor, currentListItemPath);
if (isNodeEnd) {
// If there was nothing after the cursor, create a fresh new list item,
// in order to avoid carrying over the modifiers from the previous list item
slate.Transforms.insertNodes(editor, {
type: 'list-item',
children: [
{
type: 'text',
text: ''
}
]
});
} else {
// If there is something after the cursor, split the current list item,
// so that we keep the content and the modifiers
slate.Transforms.splitNodes(editor);
}
}
};
/**
* Common handler for converting a node to a list
*/ const handleConvertToList = (editor, format)=>{
const convertedPath = conversions.baseHandleConvert(editor, {
type: 'list-item'
});
if (!convertedPath) return;
slate.Transforms.wrapNodes(editor, {
type: 'list',
format,
children: []
}, {
at: convertedPath
});
};
/**
* Common handler for the tab key on ordered and unordered lists
*/ const handleTabOnList = (editor)=>{
const currentListItemEntry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list-item'
});
if (!currentListItemEntry || !editor.selection) {
return;
}
const [currentListItem, currentListItemPath] = currentListItemEntry;
const [currentList] = slate.Editor.parent(editor, currentListItemPath);
// Skip tabbing if list-item is the first item in the list
if (currentListItem === currentList.children[0]) return;
const currentListItemIndex = currentList.children.findIndex((item)=>item === currentListItem);
const previousNode = currentList.children[currentListItemIndex - 1];
// If previous node is a list block then move the list-item under it
if (previousNode.type === 'list') {
const nodePath = slateReact.ReactEditor.findPath(editor, previousNode);
const insertAtPath = previousNode.children.length;
slate.Transforms.moveNodes(editor, {
at: currentListItemPath,
to: nodePath.concat(insertAtPath)
});
return;
}
if (!slate.Editor.isEditor(currentList) && types.isListNode(currentList)) {
// Wrap list-item with list block on tab
slate.Transforms.wrapNodes(editor, {
type: 'list',
format: currentList.format,
indentLevel: (currentList.indentLevel || 0) + 1,
children: []
});
}
};
const listBlocks = {
'list-ordered': {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(List, {
...props
}),
label: {
id: 'components.Blocks.blocks.orderedList',
defaultMessage: 'Numbered list'
},
icon: Icons.NumberList,
matchNode: (node)=>node.type === 'list' && node.format === 'ordered',
isInBlocksSelector: true,
handleConvert: (editor)=>handleConvertToList(editor, 'ordered'),
handleEnterKey: handleEnterKeyOnList,
handleBackspaceKey: handleBackspaceKeyOnList,
handleTab: handleTabOnList,
snippets: [
'1.'
]
},
'list-unordered': {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(List, {
...props
}),
label: {
id: 'components.Blocks.blocks.unorderedList',
defaultMessage: 'Bulleted list'
},
icon: Icons.BulletList,
matchNode: (node)=>node.type === 'list' && node.format === 'unordered',
isInBlocksSelector: true,
handleConvert: (editor)=>handleConvertToList(editor, 'unordered'),
handleEnterKey: handleEnterKeyOnList,
handleBackspaceKey: handleBackspaceKeyOnList,
handleTab: handleTabOnList,
snippets: [
'-',
'*',
'+'
]
},
'list-item': {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
tag: "li",
...props.attributes,
children: props.children
}),
// No handleConvert, list items are created when converting to the parent list
matchNode: (node)=>node.type === 'list-item',
isInBlocksSelector: false,
dragHandleTopMargin: '-2px'
}
};
exports.listBlocks = listBlocks;
//# sourceMappingURL=List.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,358 @@
import { jsx } from 'react/jsx-runtime';
import 'react';
import { Typography } from '@strapi/design-system';
import { NumberList, BulletList } from '@strapi/icons';
import { Editor, Transforms, Path, Node } from 'slate';
import { ReactEditor } from 'slate-react';
import { css, styled } from 'styled-components';
import { baseHandleConvert } from '../utils/conversions.mjs';
import { isListNode } from '../utils/types.mjs';
const listStyle = css`
display: flex;
flex-direction: column;
gap: ${({ theme })=>theme.spaces[2]};
margin-inline-start: ${({ theme })=>theme.spaces[0]};
margin-inline-end: ${({ theme })=>theme.spaces[0]};
padding-inline-start: ${({ theme })=>theme.spaces[2]};
ol,
ul {
margin-block-start: ${({ theme })=>theme.spaces[0]};
margin-block-end: ${({ theme })=>theme.spaces[0]};
}
li {
margin-inline-start: ${({ theme })=>theme.spaces[3]};
}
`;
const Orderedlist = styled.ol`
list-style-type: ${(props)=>props.$listStyleType};
${listStyle}
`;
const Unorderedlist = styled.ul`
list-style-type: ${(props)=>props.$listStyleType};
${listStyle}
`;
const orderedStyles = [
'decimal',
'lower-alpha',
'upper-roman'
];
const unorderedStyles = [
'disc',
'circle',
'square'
];
const List = ({ attributes, children, element })=>{
if (!isListNode(element)) {
return null;
}
// Decide the subsequent style by referencing the given styles according to the format,
// allowing for infinite nested lists
const listStyles = element.format === 'ordered' ? orderedStyles : unorderedStyles;
const nextIndex = (element.indentLevel || 0) % listStyles.length;
const listStyleType = listStyles[nextIndex];
if (element.format === 'ordered') {
return /*#__PURE__*/ jsx(Orderedlist, {
$listStyleType: listStyleType,
...attributes,
children: children
});
}
return /*#__PURE__*/ jsx(Unorderedlist, {
$listStyleType: listStyleType,
...attributes,
children: children
});
};
const replaceListWithEmptyBlock = (editor, currentListPath)=>{
// Delete the empty list
Transforms.removeNodes(editor, {
at: currentListPath
});
if (currentListPath[0] === 0) {
// If the list was the only (or first) block element then insert empty paragraph as editor needs default value
Transforms.insertNodes(editor, {
type: 'paragraph',
children: [
{
type: 'text',
text: ''
}
]
}, {
at: currentListPath
});
Transforms.select(editor, currentListPath);
}
};
const isText = (node)=>{
return Node.isNode(node) && !Editor.isEditor(node) && node.type === 'text';
};
/**
* Common handler for the backspace event on ordered and unordered lists
*/ const handleBackspaceKeyOnList = (editor, event)=>{
if (!editor.selection) return;
const [currentListItem, currentListItemPath] = Editor.parent(editor, editor.selection.anchor);
const [currentList, currentListPath] = Editor.parent(editor, currentListItemPath);
const isListEmpty = currentList.children.length === 1 && isText(currentListItem.children[0]) && currentListItem.children[0].text === '';
const isListItemEmpty = currentListItem.children.length === 1 && isText(currentListItem.children[0]) && currentListItem.children[0].text === '';
const isFocusAtTheBeginningOfAChild = editor.selection.focus.offset === 0 && editor.selection.focus.path.at(-2) === 0;
if (isListEmpty) {
const parentListEntry = Editor.above(editor, {
at: currentListPath,
match: (node)=>!Editor.isEditor(node) && node.type === 'list'
});
if (!parentListEntry) {
event.preventDefault();
replaceListWithEmptyBlock(editor, currentListPath);
}
} else if (isFocusAtTheBeginningOfAChild) {
// If the focus is at the beginning of a child node we need to replace it with a paragraph
Transforms.liftNodes(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'list-item'
});
Transforms.setNodes(editor, {
type: 'paragraph'
});
} else if (isListItemEmpty) {
const previousEntry = Editor.previous(editor, {
at: currentListItemPath
});
const nextEntry = Editor.next(editor, {
at: currentListItemPath
});
if (previousEntry && nextEntry) {
// If previous and next nodes are lists or list-items, delete empty list item
event.preventDefault();
Transforms.removeNodes(editor, {
at: currentListItemPath
});
// If previous and next nodes are lists with same format and indent Levels, then merge the nodes
const [previousList] = previousEntry;
const [nextList] = nextEntry;
if (!Editor.isEditor(previousList) && !isText(previousList) && isListNode(previousList) && !Editor.isEditor(nextList) && !isText(nextList) && isListNode(nextList)) {
if (previousList.type === 'list' && nextList.type === 'list' && previousList.format === nextList.format && previousList.indentLevel === nextList.indentLevel) {
Transforms.mergeNodes(editor, {
at: currentListItemPath
});
}
}
}
}
};
/**
* Common handler for the enter key on ordered and unordered lists
*/ const handleEnterKeyOnList = (editor)=>{
const currentListItemEntry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'list-item'
});
if (!currentListItemEntry || !editor.selection) {
return;
}
const [currentListItem, currentListItemPath] = currentListItemEntry;
const [currentList, currentListPath] = Editor.parent(editor, currentListItemPath);
const isListEmpty = currentList.children.length === 1 && isText(currentListItem.children[0]) && currentListItem.children[0].text === '';
const isListItemEmpty = currentListItem.children.length === 1 && isText(currentListItem.children[0]) && currentListItem.children[0].text === '';
const isFocusAtTheBeginningOfAChild = editor.selection.focus.offset === 0 && editor.selection.focus.path.at(-1) === 0;
if (isListEmpty) {
replaceListWithEmptyBlock(editor, currentListPath);
} else if (isFocusAtTheBeginningOfAChild && !isListItemEmpty) {
// If the focus is at the beginning of a child node, shift below the list item and create a new list-item
const currentNode = Editor.above(editor, {
at: editor.selection.anchor
});
Transforms.insertNodes(editor, {
type: 'list-item',
children: [
{
type: 'text',
text: ''
}
]
});
if (currentNode) {
const path = currentNode[1];
const updatedPath = [
...path.slice(0, -1),
path[path.length - 1] + 1
];
Transforms.select(editor, {
anchor: {
path: updatedPath.concat(0),
offset: 0
},
focus: {
path: updatedPath.concat(0),
offset: 0
}
});
}
} else if (isListItemEmpty) {
// Check if there is a list above the current list and shift list-item under it
if (!Editor.isEditor(currentList) && isListNode(currentList) && currentList?.indentLevel && currentList.indentLevel > 0) {
const previousIndentLevel = currentList.indentLevel - 1;
const parentListNodeEntry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'list' && (node.indentLevel || 0) === previousIndentLevel
});
if (parentListNodeEntry) {
// Get the parent list path and add 1 to it to exit from the current list
const modifiedPath = currentListItemPath.slice(0, -1);
if (modifiedPath.length > 0) {
modifiedPath[modifiedPath.length - 1] += 1;
}
// Shift list-item under parent list
Transforms.moveNodes(editor, {
at: currentListItemPath,
to: modifiedPath
});
return;
}
}
// Otherwise delete the empty list item and create a new paragraph below the parent list
Transforms.removeNodes(editor, {
at: currentListItemPath
});
const createdParagraphPath = Path.next(currentListPath);
Transforms.insertNodes(editor, {
type: 'paragraph',
children: [
{
type: 'text',
text: ''
}
]
}, {
at: createdParagraphPath
});
// Move the selection to the newly created paragraph
Transforms.select(editor, createdParagraphPath);
} else {
// Check if the cursor is at the end of the list item
const isNodeEnd = Editor.isEnd(editor, editor.selection.anchor, currentListItemPath);
if (isNodeEnd) {
// If there was nothing after the cursor, create a fresh new list item,
// in order to avoid carrying over the modifiers from the previous list item
Transforms.insertNodes(editor, {
type: 'list-item',
children: [
{
type: 'text',
text: ''
}
]
});
} else {
// If there is something after the cursor, split the current list item,
// so that we keep the content and the modifiers
Transforms.splitNodes(editor);
}
}
};
/**
* Common handler for converting a node to a list
*/ const handleConvertToList = (editor, format)=>{
const convertedPath = baseHandleConvert(editor, {
type: 'list-item'
});
if (!convertedPath) return;
Transforms.wrapNodes(editor, {
type: 'list',
format,
children: []
}, {
at: convertedPath
});
};
/**
* Common handler for the tab key on ordered and unordered lists
*/ const handleTabOnList = (editor)=>{
const currentListItemEntry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'list-item'
});
if (!currentListItemEntry || !editor.selection) {
return;
}
const [currentListItem, currentListItemPath] = currentListItemEntry;
const [currentList] = Editor.parent(editor, currentListItemPath);
// Skip tabbing if list-item is the first item in the list
if (currentListItem === currentList.children[0]) return;
const currentListItemIndex = currentList.children.findIndex((item)=>item === currentListItem);
const previousNode = currentList.children[currentListItemIndex - 1];
// If previous node is a list block then move the list-item under it
if (previousNode.type === 'list') {
const nodePath = ReactEditor.findPath(editor, previousNode);
const insertAtPath = previousNode.children.length;
Transforms.moveNodes(editor, {
at: currentListItemPath,
to: nodePath.concat(insertAtPath)
});
return;
}
if (!Editor.isEditor(currentList) && isListNode(currentList)) {
// Wrap list-item with list block on tab
Transforms.wrapNodes(editor, {
type: 'list',
format: currentList.format,
indentLevel: (currentList.indentLevel || 0) + 1,
children: []
});
}
};
const listBlocks = {
'list-ordered': {
renderElement: (props)=>/*#__PURE__*/ jsx(List, {
...props
}),
label: {
id: 'components.Blocks.blocks.orderedList',
defaultMessage: 'Numbered list'
},
icon: NumberList,
matchNode: (node)=>node.type === 'list' && node.format === 'ordered',
isInBlocksSelector: true,
handleConvert: (editor)=>handleConvertToList(editor, 'ordered'),
handleEnterKey: handleEnterKeyOnList,
handleBackspaceKey: handleBackspaceKeyOnList,
handleTab: handleTabOnList,
snippets: [
'1.'
]
},
'list-unordered': {
renderElement: (props)=>/*#__PURE__*/ jsx(List, {
...props
}),
label: {
id: 'components.Blocks.blocks.unorderedList',
defaultMessage: 'Bulleted list'
},
icon: BulletList,
matchNode: (node)=>node.type === 'list' && node.format === 'unordered',
isInBlocksSelector: true,
handleConvert: (editor)=>handleConvertToList(editor, 'unordered'),
handleEnterKey: handleEnterKeyOnList,
handleBackspaceKey: handleBackspaceKeyOnList,
handleTab: handleTabOnList,
snippets: [
'-',
'*',
'+'
]
},
'list-item': {
renderElement: (props)=>/*#__PURE__*/ jsx(Typography, {
tag: "li",
...props.attributes,
children: props.children
}),
// No handleConvert, list items are created when converting to the parent list
matchNode: (node)=>node.type === 'list-item',
isInBlocksSelector: false,
dragHandleTopMargin: '-2px'
}
};
export { listBlocks };
//# sourceMappingURL=List.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,95 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var slate = require('slate');
var conversions = require('../utils/conversions.js');
const paragraphBlocks = {
paragraph: {
renderElement: (props)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
tag: "p",
variant: "omega",
...props.attributes,
children: props.children
}),
icon: Icons.Paragraph,
label: {
id: 'components.Blocks.blocks.text',
defaultMessage: 'Text'
},
matchNode: (node)=>node.type === 'paragraph',
isInBlocksSelector: true,
dragHandleTopMargin: '-2px',
handleConvert (editor) {
conversions.baseHandleConvert(editor, {
type: 'paragraph'
});
},
handleEnterKey (editor) {
if (!editor.selection) {
return;
}
// We need to keep track of the initial position of the cursor
const anchorPathInitialPosition = editor.selection.anchor.path;
/**
* Split the nodes where the cursor is. This will create a new paragraph with the content
* after the cursor, while retaining all the children, modifiers etc.
*/ slate.Transforms.splitNodes(editor, {
// Makes sure we always create a new node,
// even if there's nothing to the right of the cursor in the node.
always: true
});
// Check if the created node is empty (if there was no text after the cursor in the node)
// This lets us know if we need to carry over the modifiers from the previous node
const parentBlockEntry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type !== 'text'
});
if (!parentBlockEntry) {
return;
}
const [, parentBlockPath] = parentBlockEntry;
const isNodeEnd = slate.Editor.isEnd(editor, editor.selection.anchor, parentBlockPath);
/**
* Delete and recreate the node that was created at the right of the cursor.
* This is to avoid node pollution
* (e.g. keeping the level attribute when converting a heading to a paragraph).
* Select the parent of the selection because we want the full block, not the leaf.
* And copy its children to make sure we keep the modifiers.
*/ const [fragmentedNode] = slate.Editor.parent(editor, editor.selection.anchor.path);
slate.Transforms.removeNodes(editor);
// Check if after the current position there is another node
const hasNextNode = editor.children.length - anchorPathInitialPosition[0] > 1;
// Insert the new node at the right position.
// The next line after the editor selection if present or otherwise at the end of the editor.
slate.Transforms.insertNodes(editor, {
type: 'paragraph',
// Don't carry over the modifiers from the previous node if there was no text after the cursor
children: isNodeEnd ? [
{
type: 'text',
text: ''
}
] : fragmentedNode.children
}, {
at: hasNextNode ? [
anchorPathInitialPosition[0] + 1
] : [
editor.children.length
]
});
/**
* The new selection will by default be at the end of the created node.
* Instead we manually move it to the start of the created node.
* Make sure to we go to the start of the node and not the start of the leaf.
*/ slate.Transforms.select(editor, editor.start([
anchorPathInitialPosition[0] + 1
]));
}
}
};
exports.paragraphBlocks = paragraphBlocks;
//# sourceMappingURL=Paragraph.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,93 @@
import { jsx } from 'react/jsx-runtime';
import 'react';
import { Typography } from '@strapi/design-system';
import { Paragraph } from '@strapi/icons';
import { Transforms, Editor } from 'slate';
import { baseHandleConvert } from '../utils/conversions.mjs';
const paragraphBlocks = {
paragraph: {
renderElement: (props)=>/*#__PURE__*/ jsx(Typography, {
tag: "p",
variant: "omega",
...props.attributes,
children: props.children
}),
icon: Paragraph,
label: {
id: 'components.Blocks.blocks.text',
defaultMessage: 'Text'
},
matchNode: (node)=>node.type === 'paragraph',
isInBlocksSelector: true,
dragHandleTopMargin: '-2px',
handleConvert (editor) {
baseHandleConvert(editor, {
type: 'paragraph'
});
},
handleEnterKey (editor) {
if (!editor.selection) {
return;
}
// We need to keep track of the initial position of the cursor
const anchorPathInitialPosition = editor.selection.anchor.path;
/**
* Split the nodes where the cursor is. This will create a new paragraph with the content
* after the cursor, while retaining all the children, modifiers etc.
*/ Transforms.splitNodes(editor, {
// Makes sure we always create a new node,
// even if there's nothing to the right of the cursor in the node.
always: true
});
// Check if the created node is empty (if there was no text after the cursor in the node)
// This lets us know if we need to carry over the modifiers from the previous node
const parentBlockEntry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && node.type !== 'text'
});
if (!parentBlockEntry) {
return;
}
const [, parentBlockPath] = parentBlockEntry;
const isNodeEnd = Editor.isEnd(editor, editor.selection.anchor, parentBlockPath);
/**
* Delete and recreate the node that was created at the right of the cursor.
* This is to avoid node pollution
* (e.g. keeping the level attribute when converting a heading to a paragraph).
* Select the parent of the selection because we want the full block, not the leaf.
* And copy its children to make sure we keep the modifiers.
*/ const [fragmentedNode] = Editor.parent(editor, editor.selection.anchor.path);
Transforms.removeNodes(editor);
// Check if after the current position there is another node
const hasNextNode = editor.children.length - anchorPathInitialPosition[0] > 1;
// Insert the new node at the right position.
// The next line after the editor selection if present or otherwise at the end of the editor.
Transforms.insertNodes(editor, {
type: 'paragraph',
// Don't carry over the modifiers from the previous node if there was no text after the cursor
children: isNodeEnd ? [
{
type: 'text',
text: ''
}
] : fragmentedNode.children
}, {
at: hasNextNode ? [
anchorPathInitialPosition[0] + 1
] : [
editor.children.length
]
});
/**
* The new selection will by default be at the end of the created node.
* Instead we manually move it to the start of the created node.
* Make sure to we go to the start of the node and not the start of the leaf.
*/ Transforms.select(editor, editor.start([
anchorPathInitialPosition[0] + 1
]));
}
}
};
export { paragraphBlocks };
//# sourceMappingURL=Paragraph.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,49 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var Icons = require('@strapi/icons');
var styledComponents = require('styled-components');
var conversions = require('../utils/conversions.js');
var enterKey = require('../utils/enterKey.js');
const Blockquote = styledComponents.styled.blockquote.attrs({
role: 'blockquote'
})`
font-weight: ${({ theme })=>theme.fontWeights.regular};
border-left: ${({ theme })=>`${theme.spaces[1]} solid ${theme.colors.neutral200}`};
padding: ${({ theme })=>theme.spaces[2]} ${({ theme })=>theme.spaces[4]};
color: ${({ theme })=>theme.colors.neutral600};
`;
const quoteBlocks = {
quote: {
renderElement: (props)=>// The div is needed to make sure the padding bottom from BlocksContent is applied properly
// when the quote is the last block in the editor
/*#__PURE__*/ jsxRuntime.jsx("div", {
children: /*#__PURE__*/ jsxRuntime.jsx(Blockquote, {
...props.attributes,
children: props.children
})
}),
icon: Icons.Quotes,
label: {
id: 'components.Blocks.blocks.quote',
defaultMessage: 'Quote'
},
matchNode: (node)=>node.type === 'quote',
isInBlocksSelector: true,
handleConvert (editor) {
conversions.baseHandleConvert(editor, {
type: 'quote'
});
},
handleEnterKey (editor) {
enterKey.pressEnterTwiceToExit(editor);
},
snippets: [
'>'
]
}
};
exports.quoteBlocks = quoteBlocks;
//# sourceMappingURL=Quote.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Quote.js","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/Blocks/Quote.tsx"],"sourcesContent":["import { Quotes } from '@strapi/icons';\nimport { styled } from 'styled-components';\n\nimport { type BlocksStore } from '../BlocksEditor';\nimport { baseHandleConvert } from '../utils/conversions';\nimport { pressEnterTwiceToExit } from '../utils/enterKey';\nimport { type Block } from '../utils/types';\n\nconst Blockquote = styled.blockquote.attrs({ role: 'blockquote' })`\n font-weight: ${({ theme }) => theme.fontWeights.regular};\n border-left: ${({ theme }) => `${theme.spaces[1]} solid ${theme.colors.neutral200}`};\n padding: ${({ theme }) => theme.spaces[2]} ${({ theme }) => theme.spaces[4]};\n color: ${({ theme }) => theme.colors.neutral600};\n`;\n\nconst quoteBlocks: Pick<BlocksStore, 'quote'> = {\n quote: {\n renderElement: (props) => (\n // The div is needed to make sure the padding bottom from BlocksContent is applied properly\n // when the quote is the last block in the editor\n <div>\n <Blockquote {...props.attributes}>{props.children}</Blockquote>\n </div>\n ),\n icon: Quotes,\n label: {\n id: 'components.Blocks.blocks.quote',\n defaultMessage: 'Quote',\n },\n matchNode: (node) => node.type === 'quote',\n isInBlocksSelector: true,\n handleConvert(editor) {\n baseHandleConvert<Block<'quote'>>(editor, { type: 'quote' });\n },\n handleEnterKey(editor) {\n pressEnterTwiceToExit(editor);\n },\n snippets: ['>'],\n },\n};\n\nexport { quoteBlocks };\n"],"names":["Blockquote","styled","blockquote","attrs","role","theme","fontWeights","regular","spaces","colors","neutral200","neutral600","quoteBlocks","quote","renderElement","props","_jsx","div","attributes","children","icon","Quotes","label","id","defaultMessage","matchNode","node","type","isInBlocksSelector","handleConvert","editor","baseHandleConvert","handleEnterKey","pressEnterTwiceToExit","snippets"],"mappings":";;;;;;;;AAQA,MAAMA,UAAaC,GAAAA,uBAAAA,CAAOC,UAAU,CAACC,KAAK,CAAC;IAAEC,IAAM,EAAA;AAAa,CAAA,CAAE;eACnD,EAAE,CAAC,EAAEC,KAAK,EAAE,GAAKA,KAAMC,CAAAA,WAAW,CAACC,OAAO,CAAC;eAC3C,EAAE,CAAC,EAAEF,KAAK,EAAE,GAAK,CAAC,EAAEA,MAAMG,MAAM,CAAC,EAAE,CAAC,OAAO,EAAEH,KAAMI,CAAAA,MAAM,CAACC,UAAU,CAAC,CAAC,CAAC;WAC3E,EAAE,CAAC,EAAEL,KAAK,EAAE,GAAKA,KAAMG,CAAAA,MAAM,CAAC,CAAE,CAAA,CAAC,CAAC,EAAE,CAAC,EAAEH,KAAK,EAAE,GAAKA,KAAMG,CAAAA,MAAM,CAAC,CAAA,CAAE,CAAC;SACrE,EAAE,CAAC,EAAEH,KAAK,EAAE,GAAKA,KAAMI,CAAAA,MAAM,CAACE,UAAU,CAAC;AAClD,CAAC;AAED,MAAMC,WAA0C,GAAA;IAC9CC,KAAO,EAAA;QACLC,aAAe,EAAA,CAACC;;0BAGdC,cAACC,CAAAA,KAAAA,EAAAA;AACC,gBAAA,QAAA,gBAAAD,cAAChB,CAAAA,UAAAA,EAAAA;AAAY,oBAAA,GAAGe,MAAMG,UAAU;AAAGH,oBAAAA,QAAAA,EAAAA,KAAAA,CAAMI;;;QAG7CC,IAAMC,EAAAA,YAAAA;QACNC,KAAO,EAAA;YACLC,EAAI,EAAA,gCAAA;YACJC,cAAgB,EAAA;AAClB,SAAA;AACAC,QAAAA,SAAAA,EAAW,CAACC,IAAAA,GAASA,IAAKC,CAAAA,IAAI,KAAK,OAAA;QACnCC,kBAAoB,EAAA,IAAA;AACpBC,QAAAA,aAAAA,CAAAA,CAAcC,MAAM,EAAA;AAClBC,YAAAA,6BAAAA,CAAkCD,MAAQ,EAAA;gBAAEH,IAAM,EAAA;AAAQ,aAAA,CAAA;AAC5D,SAAA;AACAK,QAAAA,cAAAA,CAAAA,CAAeF,MAAM,EAAA;YACnBG,8BAAsBH,CAAAA,MAAAA,CAAAA;AACxB,SAAA;QACAI,QAAU,EAAA;AAAC,YAAA;AAAI;AACjB;AACF;;;;"}

View File

@@ -0,0 +1,47 @@
import { jsx } from 'react/jsx-runtime';
import { Quotes } from '@strapi/icons';
import { styled } from 'styled-components';
import { baseHandleConvert } from '../utils/conversions.mjs';
import { pressEnterTwiceToExit } from '../utils/enterKey.mjs';
const Blockquote = styled.blockquote.attrs({
role: 'blockquote'
})`
font-weight: ${({ theme })=>theme.fontWeights.regular};
border-left: ${({ theme })=>`${theme.spaces[1]} solid ${theme.colors.neutral200}`};
padding: ${({ theme })=>theme.spaces[2]} ${({ theme })=>theme.spaces[4]};
color: ${({ theme })=>theme.colors.neutral600};
`;
const quoteBlocks = {
quote: {
renderElement: (props)=>// The div is needed to make sure the padding bottom from BlocksContent is applied properly
// when the quote is the last block in the editor
/*#__PURE__*/ jsx("div", {
children: /*#__PURE__*/ jsx(Blockquote, {
...props.attributes,
children: props.children
})
}),
icon: Quotes,
label: {
id: 'components.Blocks.blocks.quote',
defaultMessage: 'Quote'
},
matchNode: (node)=>node.type === 'quote',
isInBlocksSelector: true,
handleConvert (editor) {
baseHandleConvert(editor, {
type: 'quote'
});
},
handleEnterKey (editor) {
pressEnterTwiceToExit(editor);
},
snippets: [
'>'
]
}
};
export { quoteBlocks };
//# sourceMappingURL=Quote.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Quote.mjs","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/Blocks/Quote.tsx"],"sourcesContent":["import { Quotes } from '@strapi/icons';\nimport { styled } from 'styled-components';\n\nimport { type BlocksStore } from '../BlocksEditor';\nimport { baseHandleConvert } from '../utils/conversions';\nimport { pressEnterTwiceToExit } from '../utils/enterKey';\nimport { type Block } from '../utils/types';\n\nconst Blockquote = styled.blockquote.attrs({ role: 'blockquote' })`\n font-weight: ${({ theme }) => theme.fontWeights.regular};\n border-left: ${({ theme }) => `${theme.spaces[1]} solid ${theme.colors.neutral200}`};\n padding: ${({ theme }) => theme.spaces[2]} ${({ theme }) => theme.spaces[4]};\n color: ${({ theme }) => theme.colors.neutral600};\n`;\n\nconst quoteBlocks: Pick<BlocksStore, 'quote'> = {\n quote: {\n renderElement: (props) => (\n // The div is needed to make sure the padding bottom from BlocksContent is applied properly\n // when the quote is the last block in the editor\n <div>\n <Blockquote {...props.attributes}>{props.children}</Blockquote>\n </div>\n ),\n icon: Quotes,\n label: {\n id: 'components.Blocks.blocks.quote',\n defaultMessage: 'Quote',\n },\n matchNode: (node) => node.type === 'quote',\n isInBlocksSelector: true,\n handleConvert(editor) {\n baseHandleConvert<Block<'quote'>>(editor, { type: 'quote' });\n },\n handleEnterKey(editor) {\n pressEnterTwiceToExit(editor);\n },\n snippets: ['>'],\n },\n};\n\nexport { quoteBlocks };\n"],"names":["Blockquote","styled","blockquote","attrs","role","theme","fontWeights","regular","spaces","colors","neutral200","neutral600","quoteBlocks","quote","renderElement","props","_jsx","div","attributes","children","icon","Quotes","label","id","defaultMessage","matchNode","node","type","isInBlocksSelector","handleConvert","editor","baseHandleConvert","handleEnterKey","pressEnterTwiceToExit","snippets"],"mappings":";;;;;;AAQA,MAAMA,UAAaC,GAAAA,MAAAA,CAAOC,UAAU,CAACC,KAAK,CAAC;IAAEC,IAAM,EAAA;AAAa,CAAA,CAAE;eACnD,EAAE,CAAC,EAAEC,KAAK,EAAE,GAAKA,KAAMC,CAAAA,WAAW,CAACC,OAAO,CAAC;eAC3C,EAAE,CAAC,EAAEF,KAAK,EAAE,GAAK,CAAC,EAAEA,MAAMG,MAAM,CAAC,EAAE,CAAC,OAAO,EAAEH,KAAMI,CAAAA,MAAM,CAACC,UAAU,CAAC,CAAC,CAAC;WAC3E,EAAE,CAAC,EAAEL,KAAK,EAAE,GAAKA,KAAMG,CAAAA,MAAM,CAAC,CAAE,CAAA,CAAC,CAAC,EAAE,CAAC,EAAEH,KAAK,EAAE,GAAKA,KAAMG,CAAAA,MAAM,CAAC,CAAA,CAAE,CAAC;SACrE,EAAE,CAAC,EAAEH,KAAK,EAAE,GAAKA,KAAMI,CAAAA,MAAM,CAACE,UAAU,CAAC;AAClD,CAAC;AAED,MAAMC,WAA0C,GAAA;IAC9CC,KAAO,EAAA;QACLC,aAAe,EAAA,CAACC;;0BAGdC,GAACC,CAAAA,KAAAA,EAAAA;AACC,gBAAA,QAAA,gBAAAD,GAAChB,CAAAA,UAAAA,EAAAA;AAAY,oBAAA,GAAGe,MAAMG,UAAU;AAAGH,oBAAAA,QAAAA,EAAAA,KAAAA,CAAMI;;;QAG7CC,IAAMC,EAAAA,MAAAA;QACNC,KAAO,EAAA;YACLC,EAAI,EAAA,gCAAA;YACJC,cAAgB,EAAA;AAClB,SAAA;AACAC,QAAAA,SAAAA,EAAW,CAACC,IAAAA,GAASA,IAAKC,CAAAA,IAAI,KAAK,OAAA;QACnCC,kBAAoB,EAAA,IAAA;AACpBC,QAAAA,aAAAA,CAAAA,CAAcC,MAAM,EAAA;AAClBC,YAAAA,iBAAAA,CAAkCD,MAAQ,EAAA;gBAAEH,IAAM,EAAA;AAAQ,aAAA,CAAA;AAC5D,SAAA;AACAK,QAAAA,cAAAA,CAAAA,CAAeF,MAAM,EAAA;YACnBG,qBAAsBH,CAAAA,MAAAA,CAAAA;AACxB,SAAA;QACAI,QAAU,EAAA;AAAC,YAAA;AAAI;AACjB;AACF;;;;"}

View File

@@ -0,0 +1,532 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var slate = require('slate');
var slateReact = require('slate-react');
var styledComponents = require('styled-components');
var dragAndDrop = require('../../../../../constants/dragAndDrop.js');
var useDragAndDrop = require('../../../../../hooks/useDragAndDrop.js');
var translations = require('../../../../../utils/translations.js');
var Code = require('./Blocks/Code.js');
var BlocksEditor = require('./BlocksEditor.js');
var BlocksToolbar = require('./BlocksToolbar.js');
var types = require('./utils/types.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const StyledEditable = styledComponents.styled(slateReact.Editable)`
// The outline style is set on the wrapper with :focus-within
outline: none;
display: flex;
flex-direction: column;
gap: ${({ theme })=>theme.spaces[3]};
height: 100%;
// For fullscreen align input in the center with fixed width
width: ${(props)=>props.isExpandedMode ? '512px' : '100%'};
margin: auto;
> *:last-child {
padding-bottom: ${({ theme })=>theme.spaces[3]};
}
`;
const Wrapper = styledComponents.styled(designSystem.Box)`
position: ${({ isOverDropTarget })=>isOverDropTarget && 'relative'};
`;
const DropPlaceholder = styledComponents.styled(designSystem.Box)`
position: absolute;
right: 0;
// Show drop placeholder 8px above or below the drop target
${({ dragDirection, theme, placeholderMargin })=>styledComponents.css`
top: ${dragDirection === useDragAndDrop.DIRECTIONS.UPWARD && `-${theme.spaces[placeholderMargin]}`};
bottom: ${dragDirection === useDragAndDrop.DIRECTIONS.DOWNWARD && `-${theme.spaces[placeholderMargin]}`};
`}
`;
const DragItem = styledComponents.styled(designSystem.Flex)`
// Style each block rendered using renderElement()
& > [data-slate-node='element'] {
width: 100%;
opacity: inherit;
}
// Set the visibility of drag button
[role='button'] {
visibility: ${(props)=>props.$dragVisibility};
opacity: inherit;
}
&[aria-disabled='true'] {
user-drag: none;
}
`;
const DragIconButton = styledComponents.styled(designSystem.IconButton)`
user-select: none;
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: ${({ theme })=>theme.borderRadius};
padding-left: ${({ theme })=>theme.spaces[0]};
padding-right: ${({ theme })=>theme.spaces[0]};
padding-top: ${({ theme })=>theme.spaces[1]};
padding-bottom: ${({ theme })=>theme.spaces[1]};
visibility: hidden;
cursor: grab;
opacity: inherit;
margin-top: ${(props)=>props.$dragHandleTopMargin ?? 0};
&:hover {
background: ${({ theme })=>theme.colors.neutral100};
}
&:active {
cursor: grabbing;
background: ${({ theme })=>theme.colors.neutral150};
}
&[aria-disabled='true'] {
visibility: hidden;
}
svg {
min-width: ${({ theme })=>theme.spaces[3]};
path {
fill: ${({ theme })=>theme.colors.neutral500};
}
}
`;
const DragAndDropElement = ({ children, index, setDragDirection, dragDirection, dragHandleTopMargin })=>{
const { editor, disabled, name: name1, setLiveText } = BlocksEditor.useBlocksEditorContext('drag-and-drop');
const { formatMessage } = reactIntl.useIntl();
const [dragVisibility, setDragVisibility] = React__namespace.useState('hidden');
const handleMoveBlock = React__namespace.useCallback((newIndex, currentIndex)=>{
slate.Transforms.moveNodes(editor, {
at: currentIndex,
to: newIndex
});
// Add 1 to the index for the live text message
const currentIndexPosition = [
currentIndex[0] + 1,
...currentIndex.slice(1)
];
const newIndexPosition = [
newIndex[0] + 1,
...newIndex.slice(1)
];
setLiveText(formatMessage({
id: translations.getTranslation('components.Blocks.dnd.reorder'),
defaultMessage: '{item}, moved. New position in the editor: {position}.'
}, {
item: `${name1}.${currentIndexPosition.join(',')}`,
position: `${newIndexPosition.join(',')} of ${editor.children.length}`
}));
}, [
editor,
formatMessage,
name1,
setLiveText
]);
const [{ handlerId, isDragging, isOverDropTarget, direction }, blockRef, dropRef, dragRef] = useDragAndDrop.useDragAndDrop(!disabled, {
type: `${dragAndDrop.ItemTypes.BLOCKS}_${name1}`,
index,
item: {
index,
displayedValue: children
},
onDropItem (currentIndex, newIndex) {
if (newIndex) handleMoveBlock(newIndex, currentIndex);
}
});
const composedBoxRefs = designSystem.useComposedRefs(blockRef, dropRef);
// Set Drag direction before loosing state while dragging
React__namespace.useEffect(()=>{
if (direction) {
setDragDirection(direction);
}
}, [
direction,
setDragDirection
]);
// On selection change hide drag handle
React__namespace.useEffect(()=>{
setDragVisibility('hidden');
}, [
editor.selection
]);
return /*#__PURE__*/ jsxRuntime.jsxs(Wrapper, {
ref: composedBoxRefs,
isOverDropTarget: isOverDropTarget,
children: [
isOverDropTarget && /*#__PURE__*/ jsxRuntime.jsx(DropPlaceholder, {
borderStyle: "solid",
borderColor: "secondary200",
borderWidth: "2px",
width: "calc(100% - 24px)",
marginLeft: "auto",
dragDirection: dragDirection,
// For list items placeholder reduce the margin around
placeholderMargin: children.props.as && children.props.as === 'li' ? 1 : 2
}),
isDragging ? /*#__PURE__*/ jsxRuntime.jsx(CloneDragItem, {
dragHandleTopMargin: dragHandleTopMargin,
children: children
}) : /*#__PURE__*/ jsxRuntime.jsxs(DragItem, {
ref: dragRef,
"data-handler-id": handlerId,
gap: 2,
paddingLeft: 2,
alignItems: "start",
onDragStart: (event)=>{
const target = event.target;
const currentTarget = event.currentTarget;
// Dragging action should only trigger drag event when button is dragged, however update styles on the whole dragItem.
if (target.getAttribute('role') !== 'button') {
event.preventDefault();
} else {
// Setting styles using dragging state is not working, so set it on current target element as nodes get dragged
currentTarget.style.opacity = '0.5';
}
},
onDragEnd: (event)=>{
const currentTarget = event.currentTarget;
currentTarget.style.opacity = '1';
},
onMouseMove: ()=>setDragVisibility('visible'),
onSelect: ()=>setDragVisibility('visible'),
onMouseLeave: ()=>setDragVisibility('hidden'),
"aria-disabled": disabled,
$dragVisibility: dragVisibility,
children: [
/*#__PURE__*/ jsxRuntime.jsx(DragIconButton, {
tag: "div",
contentEditable: false,
role: "button",
tabIndex: 0,
withTooltip: false,
label: formatMessage({
id: translations.getTranslation('components.DragHandle-label'),
defaultMessage: 'Drag'
}),
onClick: (e)=>e.stopPropagation(),
"aria-disabled": disabled,
disabled: disabled,
draggable: true,
// For some blocks top margin added to drag handle to align at the text level
$dragHandleTopMargin: dragHandleTopMargin,
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Drag, {
color: "primary500"
})
}),
children
]
})
]
});
};
// To prevent applying opacity to the original item being dragged, display a cloned element without opacity.
const CloneDragItem = ({ children, dragHandleTopMargin })=>{
const { formatMessage } = reactIntl.useIntl();
return /*#__PURE__*/ jsxRuntime.jsxs(DragItem, {
gap: 2,
paddingLeft: 2,
alignItems: "start",
$dragVisibility: "visible",
children: [
/*#__PURE__*/ jsxRuntime.jsx(DragIconButton, {
tag: "div",
role: "button",
withTooltip: false,
label: formatMessage({
id: translations.getTranslation('components.DragHandle-label'),
defaultMessage: 'Drag'
}),
$dragHandleTopMargin: dragHandleTopMargin,
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Drag, {
color: "neutral600"
})
}),
children
]
});
};
const baseRenderLeaf = (props, modifiers)=>{
// Recursively wrap the children for each active modifier
const wrappedChildren = types.getEntries(modifiers).reduce((currentChildren, modifierEntry)=>{
const [name1, modifier] = modifierEntry;
if (props.leaf[name1]) {
return modifier.renderLeaf(currentChildren);
}
return currentChildren;
}, props.children);
return /*#__PURE__*/ jsxRuntime.jsx("span", {
...props.attributes,
className: props.leaf.className,
children: wrappedChildren
});
};
const baseRenderElement = ({ props, blocks, editor, setDragDirection, dragDirection })=>{
const { element } = props;
const blockMatch = Object.values(blocks).find((block)=>block.matchNode(element));
const block = blockMatch || blocks.paragraph;
const nodePath = slateReact.ReactEditor.findPath(editor, element);
// Link is inline block so it cannot be dragged
// List items and nested list blocks i.e. lists with indent level higher than 0 are skipped from dragged items
if (types.isLinkNode(element) || types.isListNode(element) && element.indentLevel && element.indentLevel > 0 || element.type === 'list-item') {
return block.renderElement(props);
}
return /*#__PURE__*/ jsxRuntime.jsx(DragAndDropElement, {
index: nodePath,
setDragDirection: setDragDirection,
dragDirection: dragDirection,
dragHandleTopMargin: block.dragHandleTopMargin,
children: block.renderElement(props)
});
};
const BlocksContent = ({ placeholder, ariaLabelId })=>{
const { editor, disabled, blocks, modifiers, setLiveText, isExpandedMode } = BlocksEditor.useBlocksEditorContext('BlocksContent');
const blocksRef = React__namespace.useRef(null);
const { formatMessage } = reactIntl.useIntl();
const [dragDirection, setDragDirection] = React__namespace.useState(null);
const { modalElement, handleConversionResult } = BlocksToolbar.useConversionModal();
// Create renderLeaf function based on the modifiers store
const renderLeaf = React__namespace.useCallback((props)=>baseRenderLeaf(props, modifiers), [
modifiers
]);
const handleMoveBlocks = (editor, event)=>{
if (!editor.selection) return;
const start = slate.Range.start(editor.selection);
const currentIndex = [
start.path[0]
];
let newIndexPosition = 0;
if (event.key === 'ArrowUp') {
newIndexPosition = currentIndex[0] > 0 ? currentIndex[0] - 1 : currentIndex[0];
} else {
newIndexPosition = currentIndex[0] < editor.children.length - 1 ? currentIndex[0] + 1 : currentIndex[0];
}
const newIndex = [
newIndexPosition
];
if (newIndexPosition !== currentIndex[0]) {
slate.Transforms.moveNodes(editor, {
at: currentIndex,
to: newIndex
});
setLiveText(formatMessage({
id: translations.getTranslation('components.Blocks.dnd.reorder'),
defaultMessage: '{item}, moved. New position in the editor: {position}.'
}, {
item: `${name}.${currentIndex[0] + 1}`,
position: `${newIndex[0] + 1} of ${editor.children.length}`
}));
event.preventDefault();
}
};
// Create renderElement function base on the blocks store
const renderElement = React__namespace.useCallback((props)=>baseRenderElement({
props,
blocks,
editor,
dragDirection,
setDragDirection
}), [
blocks,
editor,
dragDirection,
setDragDirection
]);
const checkSnippet = (event)=>{
// Get current text block
if (!editor.selection) {
return;
}
const [textNode, textNodePath] = slate.Editor.node(editor, editor.selection.anchor.path);
// Narrow the type to a text node
if (slate.Editor.isEditor(textNode) || textNode.type !== 'text') {
return;
}
// Don't check for snippets if we're not at the start of a block
if (textNodePath.at(-1) !== 0) {
return;
}
// Check if the text node starts with a known snippet
const blockMatchingSnippet = Object.values(blocks).find((block)=>{
return block.snippets?.includes(textNode.text);
});
if (blockMatchingSnippet?.handleConvert) {
// Prevent the space from being created and delete the snippet
event.preventDefault();
slate.Transforms.delete(editor, {
distance: textNode.text.length,
unit: 'character',
reverse: true
});
// Convert the selected block
const maybeRenderModal = blockMatchingSnippet.handleConvert(editor);
handleConversionResult(maybeRenderModal);
}
};
const handleEnter = (event)=>{
if (!editor.selection) {
return;
}
const selectedNode = editor.children[editor.selection.anchor.path[0]];
const selectedBlock = Object.values(blocks).find((block)=>block.matchNode(selectedNode));
if (!selectedBlock) {
return;
}
// Allow forced line breaks when shift is pressed
if (event.shiftKey && selectedNode.type !== 'image') {
slate.Transforms.insertText(editor, '\n');
return;
}
// Check if there's an enter handler for the selected block
if (selectedBlock.handleEnterKey) {
selectedBlock.handleEnterKey(editor);
} else {
blocks.paragraph.handleEnterKey(editor);
}
};
const handleBackspaceEvent = (event)=>{
if (!editor.selection) {
return;
}
const selectedNode = editor.children[editor.selection.anchor.path[0]];
const selectedBlock = Object.values(blocks).find((block)=>block.matchNode(selectedNode));
if (!selectedBlock) {
return;
}
if (selectedBlock.handleBackspaceKey) {
selectedBlock.handleBackspaceKey(editor, event);
}
};
const handleTab = (event)=>{
if (!editor.selection) {
return;
}
const selectedNode = editor.children[editor.selection.anchor.path[0]];
const selectedBlock = Object.values(blocks).find((block)=>block.matchNode(selectedNode));
if (!selectedBlock) {
return;
}
if (selectedBlock.handleTab) {
event.preventDefault();
selectedBlock.handleTab(editor);
}
};
const handleKeyboardShortcuts = (event)=>{
const isCtrlOrCmd = event.metaKey || event.ctrlKey;
if (isCtrlOrCmd) {
// Check if there's a modifier to toggle
Object.values(modifiers).forEach((value)=>{
if (value.isValidEventKey(event)) {
value.handleToggle(editor);
return;
}
});
if (event.shiftKey && [
'ArrowUp',
'ArrowDown'
].includes(event.key)) {
handleMoveBlocks(editor, event);
}
}
};
const handleKeyDown = (event)=>{
// Find the right block-specific handlers for enter and backspace key presses
switch(event.key){
case 'Enter':
event.preventDefault();
return handleEnter(event);
case 'Backspace':
return handleBackspaceEvent(event);
case 'Tab':
return handleTab(event);
case 'Escape':
return slateReact.ReactEditor.blur(editor);
}
handleKeyboardShortcuts(event);
// Check if a snippet was triggered
if (event.key === ' ') {
checkSnippet(event);
}
};
/**
* scrollSelectionIntoView : Slate's default method to scroll a DOM selection into the view,
* thats shifting layout for us when there is a overflowY:scroll on the viewport.
* We are overriding it to check if the selection is not fully within the visible area of the editor,
* we use scrollBy one line to the bottom
*/ const handleScrollSelectionIntoView = ()=>{
if (!editor.selection) return;
const domRange = slateReact.ReactEditor.toDOMRange(editor, editor.selection);
const domRect = domRange.getBoundingClientRect();
const blocksInput = blocksRef.current;
if (!blocksInput) {
return;
}
const editorRect = blocksInput.getBoundingClientRect();
// Check if the selection is not fully within the visible area of the editor
if (domRect.top < editorRect.top || domRect.bottom > editorRect.bottom) {
// Scroll by one line to the bottom
blocksInput.scrollBy({
top: 28,
behavior: 'smooth'
});
}
};
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Box, {
ref: blocksRef,
grow: 1,
width: "100%",
overflow: "auto",
fontSize: 2,
background: "neutral0",
color: "neutral800",
lineHeight: 6,
paddingRight: 7,
paddingTop: 6,
paddingBottom: 3,
children: [
/*#__PURE__*/ jsxRuntime.jsx(StyledEditable, {
"aria-labelledby": ariaLabelId,
readOnly: disabled,
placeholder: placeholder,
isExpandedMode: isExpandedMode,
decorate: Code.decorateCode,
renderElement: renderElement,
renderLeaf: renderLeaf,
onKeyDown: handleKeyDown,
scrollSelectionIntoView: handleScrollSelectionIntoView,
// As we have our own handler to drag and drop the elements returing true will skip slate's own event handler
onDrop: ()=>{
return true;
},
onDragStart: ()=>{
return true;
}
}),
modalElement
]
});
};
exports.BlocksContent = BlocksContent;
//# sourceMappingURL=BlocksContent.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,511 @@
import { jsxs, jsx } from 'react/jsx-runtime';
import * as React from 'react';
import { Box, Flex, IconButton, useComposedRefs } from '@strapi/design-system';
import { Drag } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { Transforms, Editor, Range } from 'slate';
import { Editable, ReactEditor } from 'slate-react';
import { styled, css } from 'styled-components';
import { ItemTypes } from '../../../../../constants/dragAndDrop.mjs';
import { DIRECTIONS, useDragAndDrop } from '../../../../../hooks/useDragAndDrop.mjs';
import { getTranslation } from '../../../../../utils/translations.mjs';
import { decorateCode } from './Blocks/Code.mjs';
import { useBlocksEditorContext } from './BlocksEditor.mjs';
import { useConversionModal } from './BlocksToolbar.mjs';
import { getEntries, isLinkNode, isListNode } from './utils/types.mjs';
const StyledEditable = styled(Editable)`
// The outline style is set on the wrapper with :focus-within
outline: none;
display: flex;
flex-direction: column;
gap: ${({ theme })=>theme.spaces[3]};
height: 100%;
// For fullscreen align input in the center with fixed width
width: ${(props)=>props.isExpandedMode ? '512px' : '100%'};
margin: auto;
> *:last-child {
padding-bottom: ${({ theme })=>theme.spaces[3]};
}
`;
const Wrapper = styled(Box)`
position: ${({ isOverDropTarget })=>isOverDropTarget && 'relative'};
`;
const DropPlaceholder = styled(Box)`
position: absolute;
right: 0;
// Show drop placeholder 8px above or below the drop target
${({ dragDirection, theme, placeholderMargin })=>css`
top: ${dragDirection === DIRECTIONS.UPWARD && `-${theme.spaces[placeholderMargin]}`};
bottom: ${dragDirection === DIRECTIONS.DOWNWARD && `-${theme.spaces[placeholderMargin]}`};
`}
`;
const DragItem = styled(Flex)`
// Style each block rendered using renderElement()
& > [data-slate-node='element'] {
width: 100%;
opacity: inherit;
}
// Set the visibility of drag button
[role='button'] {
visibility: ${(props)=>props.$dragVisibility};
opacity: inherit;
}
&[aria-disabled='true'] {
user-drag: none;
}
`;
const DragIconButton = styled(IconButton)`
user-select: none;
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: ${({ theme })=>theme.borderRadius};
padding-left: ${({ theme })=>theme.spaces[0]};
padding-right: ${({ theme })=>theme.spaces[0]};
padding-top: ${({ theme })=>theme.spaces[1]};
padding-bottom: ${({ theme })=>theme.spaces[1]};
visibility: hidden;
cursor: grab;
opacity: inherit;
margin-top: ${(props)=>props.$dragHandleTopMargin ?? 0};
&:hover {
background: ${({ theme })=>theme.colors.neutral100};
}
&:active {
cursor: grabbing;
background: ${({ theme })=>theme.colors.neutral150};
}
&[aria-disabled='true'] {
visibility: hidden;
}
svg {
min-width: ${({ theme })=>theme.spaces[3]};
path {
fill: ${({ theme })=>theme.colors.neutral500};
}
}
`;
const DragAndDropElement = ({ children, index, setDragDirection, dragDirection, dragHandleTopMargin })=>{
const { editor, disabled, name: name1, setLiveText } = useBlocksEditorContext('drag-and-drop');
const { formatMessage } = useIntl();
const [dragVisibility, setDragVisibility] = React.useState('hidden');
const handleMoveBlock = React.useCallback((newIndex, currentIndex)=>{
Transforms.moveNodes(editor, {
at: currentIndex,
to: newIndex
});
// Add 1 to the index for the live text message
const currentIndexPosition = [
currentIndex[0] + 1,
...currentIndex.slice(1)
];
const newIndexPosition = [
newIndex[0] + 1,
...newIndex.slice(1)
];
setLiveText(formatMessage({
id: getTranslation('components.Blocks.dnd.reorder'),
defaultMessage: '{item}, moved. New position in the editor: {position}.'
}, {
item: `${name1}.${currentIndexPosition.join(',')}`,
position: `${newIndexPosition.join(',')} of ${editor.children.length}`
}));
}, [
editor,
formatMessage,
name1,
setLiveText
]);
const [{ handlerId, isDragging, isOverDropTarget, direction }, blockRef, dropRef, dragRef] = useDragAndDrop(!disabled, {
type: `${ItemTypes.BLOCKS}_${name1}`,
index,
item: {
index,
displayedValue: children
},
onDropItem (currentIndex, newIndex) {
if (newIndex) handleMoveBlock(newIndex, currentIndex);
}
});
const composedBoxRefs = useComposedRefs(blockRef, dropRef);
// Set Drag direction before loosing state while dragging
React.useEffect(()=>{
if (direction) {
setDragDirection(direction);
}
}, [
direction,
setDragDirection
]);
// On selection change hide drag handle
React.useEffect(()=>{
setDragVisibility('hidden');
}, [
editor.selection
]);
return /*#__PURE__*/ jsxs(Wrapper, {
ref: composedBoxRefs,
isOverDropTarget: isOverDropTarget,
children: [
isOverDropTarget && /*#__PURE__*/ jsx(DropPlaceholder, {
borderStyle: "solid",
borderColor: "secondary200",
borderWidth: "2px",
width: "calc(100% - 24px)",
marginLeft: "auto",
dragDirection: dragDirection,
// For list items placeholder reduce the margin around
placeholderMargin: children.props.as && children.props.as === 'li' ? 1 : 2
}),
isDragging ? /*#__PURE__*/ jsx(CloneDragItem, {
dragHandleTopMargin: dragHandleTopMargin,
children: children
}) : /*#__PURE__*/ jsxs(DragItem, {
ref: dragRef,
"data-handler-id": handlerId,
gap: 2,
paddingLeft: 2,
alignItems: "start",
onDragStart: (event)=>{
const target = event.target;
const currentTarget = event.currentTarget;
// Dragging action should only trigger drag event when button is dragged, however update styles on the whole dragItem.
if (target.getAttribute('role') !== 'button') {
event.preventDefault();
} else {
// Setting styles using dragging state is not working, so set it on current target element as nodes get dragged
currentTarget.style.opacity = '0.5';
}
},
onDragEnd: (event)=>{
const currentTarget = event.currentTarget;
currentTarget.style.opacity = '1';
},
onMouseMove: ()=>setDragVisibility('visible'),
onSelect: ()=>setDragVisibility('visible'),
onMouseLeave: ()=>setDragVisibility('hidden'),
"aria-disabled": disabled,
$dragVisibility: dragVisibility,
children: [
/*#__PURE__*/ jsx(DragIconButton, {
tag: "div",
contentEditable: false,
role: "button",
tabIndex: 0,
withTooltip: false,
label: formatMessage({
id: getTranslation('components.DragHandle-label'),
defaultMessage: 'Drag'
}),
onClick: (e)=>e.stopPropagation(),
"aria-disabled": disabled,
disabled: disabled,
draggable: true,
// For some blocks top margin added to drag handle to align at the text level
$dragHandleTopMargin: dragHandleTopMargin,
children: /*#__PURE__*/ jsx(Drag, {
color: "primary500"
})
}),
children
]
})
]
});
};
// To prevent applying opacity to the original item being dragged, display a cloned element without opacity.
const CloneDragItem = ({ children, dragHandleTopMargin })=>{
const { formatMessage } = useIntl();
return /*#__PURE__*/ jsxs(DragItem, {
gap: 2,
paddingLeft: 2,
alignItems: "start",
$dragVisibility: "visible",
children: [
/*#__PURE__*/ jsx(DragIconButton, {
tag: "div",
role: "button",
withTooltip: false,
label: formatMessage({
id: getTranslation('components.DragHandle-label'),
defaultMessage: 'Drag'
}),
$dragHandleTopMargin: dragHandleTopMargin,
children: /*#__PURE__*/ jsx(Drag, {
color: "neutral600"
})
}),
children
]
});
};
const baseRenderLeaf = (props, modifiers)=>{
// Recursively wrap the children for each active modifier
const wrappedChildren = getEntries(modifiers).reduce((currentChildren, modifierEntry)=>{
const [name1, modifier] = modifierEntry;
if (props.leaf[name1]) {
return modifier.renderLeaf(currentChildren);
}
return currentChildren;
}, props.children);
return /*#__PURE__*/ jsx("span", {
...props.attributes,
className: props.leaf.className,
children: wrappedChildren
});
};
const baseRenderElement = ({ props, blocks, editor, setDragDirection, dragDirection })=>{
const { element } = props;
const blockMatch = Object.values(blocks).find((block)=>block.matchNode(element));
const block = blockMatch || blocks.paragraph;
const nodePath = ReactEditor.findPath(editor, element);
// Link is inline block so it cannot be dragged
// List items and nested list blocks i.e. lists with indent level higher than 0 are skipped from dragged items
if (isLinkNode(element) || isListNode(element) && element.indentLevel && element.indentLevel > 0 || element.type === 'list-item') {
return block.renderElement(props);
}
return /*#__PURE__*/ jsx(DragAndDropElement, {
index: nodePath,
setDragDirection: setDragDirection,
dragDirection: dragDirection,
dragHandleTopMargin: block.dragHandleTopMargin,
children: block.renderElement(props)
});
};
const BlocksContent = ({ placeholder, ariaLabelId })=>{
const { editor, disabled, blocks, modifiers, setLiveText, isExpandedMode } = useBlocksEditorContext('BlocksContent');
const blocksRef = React.useRef(null);
const { formatMessage } = useIntl();
const [dragDirection, setDragDirection] = React.useState(null);
const { modalElement, handleConversionResult } = useConversionModal();
// Create renderLeaf function based on the modifiers store
const renderLeaf = React.useCallback((props)=>baseRenderLeaf(props, modifiers), [
modifiers
]);
const handleMoveBlocks = (editor, event)=>{
if (!editor.selection) return;
const start = Range.start(editor.selection);
const currentIndex = [
start.path[0]
];
let newIndexPosition = 0;
if (event.key === 'ArrowUp') {
newIndexPosition = currentIndex[0] > 0 ? currentIndex[0] - 1 : currentIndex[0];
} else {
newIndexPosition = currentIndex[0] < editor.children.length - 1 ? currentIndex[0] + 1 : currentIndex[0];
}
const newIndex = [
newIndexPosition
];
if (newIndexPosition !== currentIndex[0]) {
Transforms.moveNodes(editor, {
at: currentIndex,
to: newIndex
});
setLiveText(formatMessage({
id: getTranslation('components.Blocks.dnd.reorder'),
defaultMessage: '{item}, moved. New position in the editor: {position}.'
}, {
item: `${name}.${currentIndex[0] + 1}`,
position: `${newIndex[0] + 1} of ${editor.children.length}`
}));
event.preventDefault();
}
};
// Create renderElement function base on the blocks store
const renderElement = React.useCallback((props)=>baseRenderElement({
props,
blocks,
editor,
dragDirection,
setDragDirection
}), [
blocks,
editor,
dragDirection,
setDragDirection
]);
const checkSnippet = (event)=>{
// Get current text block
if (!editor.selection) {
return;
}
const [textNode, textNodePath] = Editor.node(editor, editor.selection.anchor.path);
// Narrow the type to a text node
if (Editor.isEditor(textNode) || textNode.type !== 'text') {
return;
}
// Don't check for snippets if we're not at the start of a block
if (textNodePath.at(-1) !== 0) {
return;
}
// Check if the text node starts with a known snippet
const blockMatchingSnippet = Object.values(blocks).find((block)=>{
return block.snippets?.includes(textNode.text);
});
if (blockMatchingSnippet?.handleConvert) {
// Prevent the space from being created and delete the snippet
event.preventDefault();
Transforms.delete(editor, {
distance: textNode.text.length,
unit: 'character',
reverse: true
});
// Convert the selected block
const maybeRenderModal = blockMatchingSnippet.handleConvert(editor);
handleConversionResult(maybeRenderModal);
}
};
const handleEnter = (event)=>{
if (!editor.selection) {
return;
}
const selectedNode = editor.children[editor.selection.anchor.path[0]];
const selectedBlock = Object.values(blocks).find((block)=>block.matchNode(selectedNode));
if (!selectedBlock) {
return;
}
// Allow forced line breaks when shift is pressed
if (event.shiftKey && selectedNode.type !== 'image') {
Transforms.insertText(editor, '\n');
return;
}
// Check if there's an enter handler for the selected block
if (selectedBlock.handleEnterKey) {
selectedBlock.handleEnterKey(editor);
} else {
blocks.paragraph.handleEnterKey(editor);
}
};
const handleBackspaceEvent = (event)=>{
if (!editor.selection) {
return;
}
const selectedNode = editor.children[editor.selection.anchor.path[0]];
const selectedBlock = Object.values(blocks).find((block)=>block.matchNode(selectedNode));
if (!selectedBlock) {
return;
}
if (selectedBlock.handleBackspaceKey) {
selectedBlock.handleBackspaceKey(editor, event);
}
};
const handleTab = (event)=>{
if (!editor.selection) {
return;
}
const selectedNode = editor.children[editor.selection.anchor.path[0]];
const selectedBlock = Object.values(blocks).find((block)=>block.matchNode(selectedNode));
if (!selectedBlock) {
return;
}
if (selectedBlock.handleTab) {
event.preventDefault();
selectedBlock.handleTab(editor);
}
};
const handleKeyboardShortcuts = (event)=>{
const isCtrlOrCmd = event.metaKey || event.ctrlKey;
if (isCtrlOrCmd) {
// Check if there's a modifier to toggle
Object.values(modifiers).forEach((value)=>{
if (value.isValidEventKey(event)) {
value.handleToggle(editor);
return;
}
});
if (event.shiftKey && [
'ArrowUp',
'ArrowDown'
].includes(event.key)) {
handleMoveBlocks(editor, event);
}
}
};
const handleKeyDown = (event)=>{
// Find the right block-specific handlers for enter and backspace key presses
switch(event.key){
case 'Enter':
event.preventDefault();
return handleEnter(event);
case 'Backspace':
return handleBackspaceEvent(event);
case 'Tab':
return handleTab(event);
case 'Escape':
return ReactEditor.blur(editor);
}
handleKeyboardShortcuts(event);
// Check if a snippet was triggered
if (event.key === ' ') {
checkSnippet(event);
}
};
/**
* scrollSelectionIntoView : Slate's default method to scroll a DOM selection into the view,
* thats shifting layout for us when there is a overflowY:scroll on the viewport.
* We are overriding it to check if the selection is not fully within the visible area of the editor,
* we use scrollBy one line to the bottom
*/ const handleScrollSelectionIntoView = ()=>{
if (!editor.selection) return;
const domRange = ReactEditor.toDOMRange(editor, editor.selection);
const domRect = domRange.getBoundingClientRect();
const blocksInput = blocksRef.current;
if (!blocksInput) {
return;
}
const editorRect = blocksInput.getBoundingClientRect();
// Check if the selection is not fully within the visible area of the editor
if (domRect.top < editorRect.top || domRect.bottom > editorRect.bottom) {
// Scroll by one line to the bottom
blocksInput.scrollBy({
top: 28,
behavior: 'smooth'
});
}
};
return /*#__PURE__*/ jsxs(Box, {
ref: blocksRef,
grow: 1,
width: "100%",
overflow: "auto",
fontSize: 2,
background: "neutral0",
color: "neutral800",
lineHeight: 6,
paddingRight: 7,
paddingTop: 6,
paddingBottom: 3,
children: [
/*#__PURE__*/ jsx(StyledEditable, {
"aria-labelledby": ariaLabelId,
readOnly: disabled,
placeholder: placeholder,
isExpandedMode: isExpandedMode,
decorate: decorateCode,
renderElement: renderElement,
renderLeaf: renderLeaf,
onKeyDown: handleKeyDown,
scrollSelectionIntoView: handleScrollSelectionIntoView,
// As we have our own handler to drag and drop the elements returing true will skip slate's own event handler
onDrop: ()=>{
return true;
},
onDragStart: ()=>{
return true;
}
}),
modalElement
]
});
};
export { BlocksContent };
//# sourceMappingURL=BlocksContent.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,217 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var slate = require('slate');
var slateHistory = require('slate-history');
var slateReact = require('slate-react');
var styledComponents = require('styled-components');
var translations = require('../../../../../utils/translations.js');
var Code = require('./Blocks/Code.js');
var Heading = require('./Blocks/Heading.js');
var Image = require('./Blocks/Image.js');
var Link = require('./Blocks/Link.js');
var List = require('./Blocks/List.js');
var Paragraph = require('./Blocks/Paragraph.js');
var Quote = require('./Blocks/Quote.js');
var BlocksContent = require('./BlocksContent.js');
var BlocksToolbar = require('./BlocksToolbar.js');
var EditorLayout = require('./EditorLayout.js');
var Modifiers = require('./Modifiers.js');
var withImages = require('./plugins/withImages.js');
var withLinks = require('./plugins/withLinks.js');
var withStrapiSchema = require('./plugins/withStrapiSchema.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const selectorBlockKeys = [
'paragraph',
'heading-one',
'heading-two',
'heading-three',
'heading-four',
'heading-five',
'heading-six',
'list-ordered',
'list-unordered',
'image',
'quote',
'code'
];
const isSelectorBlockKey = (key)=>{
return typeof key === 'string' && selectorBlockKeys.includes(key);
};
const [BlocksEditorProvider, usePartialBlocksEditorContext] = strapiAdmin.createContext('BlocksEditor');
function useBlocksEditorContext(consumerName) {
const context = usePartialBlocksEditorContext(consumerName, (state)=>state);
const editor = slateReact.useSlate();
return {
...context,
editor
};
}
/* -------------------------------------------------------------------------------------------------
* BlocksEditor
* -----------------------------------------------------------------------------------------------*/ const EditorDivider = styledComponents.styled(designSystem.Divider)`
background: ${({ theme })=>theme.colors.neutral200};
`;
/**
* Forces an update of the Slate editor when the value prop changes from outside of Slate.
* The root cause is that Slate is not a controlled component: https://github.com/ianstormtaylor/slate/issues/4612
* Why not use JSON.stringify(value) as the key?
* Because it would force a rerender of the entire editor every time the user types a character.
* Why not use the entity id as the key, since it's unique for each locale?
* Because it would not solve the problem when using the "fill in from other locale" feature
*/ function useResetKey(value) {
// Keep track how many times Slate detected a change from a user interaction in the editor
const slateUpdatesCount = React__namespace.useRef(0);
// Keep track of how many times the value prop was updated, whether from within editor or from outside
const valueUpdatesCount = React__namespace.useRef(0);
// Use a key to force a rerender of the Slate editor when needed
const [key, setKey] = React__namespace.useState(0);
React__namespace.useEffect(()=>{
valueUpdatesCount.current += 1;
// If the 2 refs are not equal, it means the value was updated from outside
if (valueUpdatesCount.current !== slateUpdatesCount.current) {
// So we change the key to force a rerender of the Slate editor,
// which will pick up the new value through its initialValue prop
setKey((previousKey)=>previousKey + 1);
// Then bring the 2 refs back in sync
slateUpdatesCount.current = valueUpdatesCount.current;
}
}, [
value
]);
return {
key,
incrementSlateUpdatesCount: ()=>slateUpdatesCount.current += 1
};
}
const pipe = (...fns)=>(value)=>fns.reduce((prev, fn)=>fn(prev), value);
const BlocksEditor = /*#__PURE__*/ React__namespace.forwardRef(({ disabled = false, name, onChange, value, error, ...contentProps }, forwardedRef)=>{
const { formatMessage } = reactIntl.useIntl();
const [editor] = React__namespace.useState(()=>pipe(slateHistory.withHistory, withImages.withImages, withStrapiSchema.withStrapiSchema, slateReact.withReact, withLinks.withLinks)(slate.createEditor()));
const [liveText, setLiveText] = React__namespace.useState('');
const ariaDescriptionId = React__namespace.useId();
const [isExpandedMode, handleToggleExpand] = React__namespace.useReducer((prev)=>!prev, false);
/**
* Editable is not able to hold the ref, https://github.com/ianstormtaylor/slate/issues/4082
* so with "useImperativeHandle" we can use ReactEditor methods to expose to the parent above
* also not passing forwarded ref here, gives console warning.
*/ React__namespace.useImperativeHandle(forwardedRef, ()=>({
focus () {
slateReact.ReactEditor.focus(editor);
}
}), [
editor
]);
const { key, incrementSlateUpdatesCount } = useResetKey(value);
const handleSlateChange = (state)=>{
const isAstChange = editor.operations.some((op)=>op.type !== 'set_selection');
if (isAstChange) {
incrementSlateUpdatesCount();
onChange(name, state);
}
};
const blocks = {
...Paragraph.paragraphBlocks,
...Heading.headingBlocks,
...List.listBlocks,
...Link.linkBlocks,
...Image.imageBlocks,
...Quote.quoteBlocks,
...Code.codeBlocks
};
return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.VisuallyHidden, {
id: ariaDescriptionId,
children: formatMessage({
id: translations.getTranslation('components.Blocks.dnd.instruction'),
defaultMessage: `To reorder blocks, press Command or Control along with Shift and the Up or Down arrow keys`
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.VisuallyHidden, {
"aria-live": "assertive",
children: liveText
}),
/*#__PURE__*/ jsxRuntime.jsx(slateReact.Slate, {
editor: editor,
initialValue: value || [
{
type: 'paragraph',
children: [
{
type: 'text',
text: ''
}
]
}
],
onChange: handleSlateChange,
children: /*#__PURE__*/ jsxRuntime.jsx(BlocksEditorProvider, {
blocks: blocks,
modifiers: Modifiers.modifiers,
disabled: disabled,
name: name,
setLiveText: setLiveText,
isExpandedMode: isExpandedMode,
children: /*#__PURE__*/ jsxRuntime.jsxs(EditorLayout.EditorLayout, {
error: error,
disabled: disabled,
onToggleExpand: handleToggleExpand,
ariaDescriptionId: ariaDescriptionId,
children: [
/*#__PURE__*/ jsxRuntime.jsx(BlocksToolbar.BlocksToolbar, {}),
/*#__PURE__*/ jsxRuntime.jsx(EditorDivider, {
width: "100%"
}),
/*#__PURE__*/ jsxRuntime.jsx(BlocksContent.BlocksContent, {
...contentProps
}),
!isExpandedMode && /*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
position: "absolute",
bottom: "1.2rem",
right: "1.2rem",
shadow: "filterShadow",
label: formatMessage({
id: translations.getTranslation('components.Blocks.expand'),
defaultMessage: 'Expand'
}),
onClick: handleToggleExpand,
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Expand, {})
})
]
})
})
}, key)
]
});
});
exports.BlocksEditor = BlocksEditor;
exports.BlocksEditorProvider = BlocksEditorProvider;
exports.isSelectorBlockKey = isSelectorBlockKey;
exports.useBlocksEditorContext = useBlocksEditorContext;
//# sourceMappingURL=BlocksEditor.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,193 @@
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
import * as React from 'react';
import { createContext } from '@strapi/admin/strapi-admin';
import { Divider, VisuallyHidden, IconButton } from '@strapi/design-system';
import { Expand } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { createEditor } from 'slate';
import { withHistory } from 'slate-history';
import { ReactEditor, Slate, useSlate, withReact } from 'slate-react';
import { styled } from 'styled-components';
import { getTranslation } from '../../../../../utils/translations.mjs';
import { codeBlocks } from './Blocks/Code.mjs';
import { headingBlocks } from './Blocks/Heading.mjs';
import { imageBlocks } from './Blocks/Image.mjs';
import { linkBlocks } from './Blocks/Link.mjs';
import { listBlocks } from './Blocks/List.mjs';
import { paragraphBlocks } from './Blocks/Paragraph.mjs';
import { quoteBlocks } from './Blocks/Quote.mjs';
import { BlocksContent } from './BlocksContent.mjs';
import { BlocksToolbar } from './BlocksToolbar.mjs';
import { EditorLayout } from './EditorLayout.mjs';
import { modifiers } from './Modifiers.mjs';
import { withImages } from './plugins/withImages.mjs';
import { withLinks } from './plugins/withLinks.mjs';
import { withStrapiSchema } from './plugins/withStrapiSchema.mjs';
const selectorBlockKeys = [
'paragraph',
'heading-one',
'heading-two',
'heading-three',
'heading-four',
'heading-five',
'heading-six',
'list-ordered',
'list-unordered',
'image',
'quote',
'code'
];
const isSelectorBlockKey = (key)=>{
return typeof key === 'string' && selectorBlockKeys.includes(key);
};
const [BlocksEditorProvider, usePartialBlocksEditorContext] = createContext('BlocksEditor');
function useBlocksEditorContext(consumerName) {
const context = usePartialBlocksEditorContext(consumerName, (state)=>state);
const editor = useSlate();
return {
...context,
editor
};
}
/* -------------------------------------------------------------------------------------------------
* BlocksEditor
* -----------------------------------------------------------------------------------------------*/ const EditorDivider = styled(Divider)`
background: ${({ theme })=>theme.colors.neutral200};
`;
/**
* Forces an update of the Slate editor when the value prop changes from outside of Slate.
* The root cause is that Slate is not a controlled component: https://github.com/ianstormtaylor/slate/issues/4612
* Why not use JSON.stringify(value) as the key?
* Because it would force a rerender of the entire editor every time the user types a character.
* Why not use the entity id as the key, since it's unique for each locale?
* Because it would not solve the problem when using the "fill in from other locale" feature
*/ function useResetKey(value) {
// Keep track how many times Slate detected a change from a user interaction in the editor
const slateUpdatesCount = React.useRef(0);
// Keep track of how many times the value prop was updated, whether from within editor or from outside
const valueUpdatesCount = React.useRef(0);
// Use a key to force a rerender of the Slate editor when needed
const [key, setKey] = React.useState(0);
React.useEffect(()=>{
valueUpdatesCount.current += 1;
// If the 2 refs are not equal, it means the value was updated from outside
if (valueUpdatesCount.current !== slateUpdatesCount.current) {
// So we change the key to force a rerender of the Slate editor,
// which will pick up the new value through its initialValue prop
setKey((previousKey)=>previousKey + 1);
// Then bring the 2 refs back in sync
slateUpdatesCount.current = valueUpdatesCount.current;
}
}, [
value
]);
return {
key,
incrementSlateUpdatesCount: ()=>slateUpdatesCount.current += 1
};
}
const pipe = (...fns)=>(value)=>fns.reduce((prev, fn)=>fn(prev), value);
const BlocksEditor = /*#__PURE__*/ React.forwardRef(({ disabled = false, name, onChange, value, error, ...contentProps }, forwardedRef)=>{
const { formatMessage } = useIntl();
const [editor] = React.useState(()=>pipe(withHistory, withImages, withStrapiSchema, withReact, withLinks)(createEditor()));
const [liveText, setLiveText] = React.useState('');
const ariaDescriptionId = React.useId();
const [isExpandedMode, handleToggleExpand] = React.useReducer((prev)=>!prev, false);
/**
* Editable is not able to hold the ref, https://github.com/ianstormtaylor/slate/issues/4082
* so with "useImperativeHandle" we can use ReactEditor methods to expose to the parent above
* also not passing forwarded ref here, gives console warning.
*/ React.useImperativeHandle(forwardedRef, ()=>({
focus () {
ReactEditor.focus(editor);
}
}), [
editor
]);
const { key, incrementSlateUpdatesCount } = useResetKey(value);
const handleSlateChange = (state)=>{
const isAstChange = editor.operations.some((op)=>op.type !== 'set_selection');
if (isAstChange) {
incrementSlateUpdatesCount();
onChange(name, state);
}
};
const blocks = {
...paragraphBlocks,
...headingBlocks,
...listBlocks,
...linkBlocks,
...imageBlocks,
...quoteBlocks,
...codeBlocks
};
return /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx(VisuallyHidden, {
id: ariaDescriptionId,
children: formatMessage({
id: getTranslation('components.Blocks.dnd.instruction'),
defaultMessage: `To reorder blocks, press Command or Control along with Shift and the Up or Down arrow keys`
})
}),
/*#__PURE__*/ jsx(VisuallyHidden, {
"aria-live": "assertive",
children: liveText
}),
/*#__PURE__*/ jsx(Slate, {
editor: editor,
initialValue: value || [
{
type: 'paragraph',
children: [
{
type: 'text',
text: ''
}
]
}
],
onChange: handleSlateChange,
children: /*#__PURE__*/ jsx(BlocksEditorProvider, {
blocks: blocks,
modifiers: modifiers,
disabled: disabled,
name: name,
setLiveText: setLiveText,
isExpandedMode: isExpandedMode,
children: /*#__PURE__*/ jsxs(EditorLayout, {
error: error,
disabled: disabled,
onToggleExpand: handleToggleExpand,
ariaDescriptionId: ariaDescriptionId,
children: [
/*#__PURE__*/ jsx(BlocksToolbar, {}),
/*#__PURE__*/ jsx(EditorDivider, {
width: "100%"
}),
/*#__PURE__*/ jsx(BlocksContent, {
...contentProps
}),
!isExpandedMode && /*#__PURE__*/ jsx(IconButton, {
position: "absolute",
bottom: "1.2rem",
right: "1.2rem",
shadow: "filterShadow",
label: formatMessage({
id: getTranslation('components.Blocks.expand'),
defaultMessage: 'Expand'
}),
onClick: handleToggleExpand,
children: /*#__PURE__*/ jsx(Expand, {})
})
]
})
})
}, key)
]
});
});
export { BlocksEditor, BlocksEditorProvider, isSelectorBlockKey, useBlocksEditorContext };
//# sourceMappingURL=BlocksEditor.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,64 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var BlocksEditor = require('./BlocksEditor.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const BlocksInput = /*#__PURE__*/ React__namespace.forwardRef(({ label, name, required = false, hint, labelAction, ...editorProps }, forwardedRef)=>{
const id = React__namespace.useId();
const field = strapiAdmin.useField(name);
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Root, {
id: id,
name: name,
hint: hint,
error: field.error,
required: required,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 1,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
action: labelAction,
children: label
}),
/*#__PURE__*/ jsxRuntime.jsx(BlocksEditor.BlocksEditor, {
name: name,
error: field.error,
ref: forwardedRef,
value: field.value,
onChange: field.onChange,
ariaLabelId: id,
...editorProps
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Hint, {}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Error, {})
]
})
});
});
const MemoizedBlocksInput = /*#__PURE__*/ React__namespace.memo(BlocksInput);
exports.BlocksInput = MemoizedBlocksInput;
//# sourceMappingURL=BlocksInput.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"BlocksInput.js","sources":["../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksInput.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { useField, type InputProps } from '@strapi/admin/strapi-admin';\nimport { Field, Flex } from '@strapi/design-system';\n\nimport { BlocksEditor } from './BlocksEditor';\n\nimport type { Schema } from '@strapi/types';\n\ninterface BlocksInputProps extends Omit<InputProps, 'type'> {\n labelAction?: React.ReactNode;\n type: Schema.Attribute.Blocks['type'];\n}\n\nconst BlocksInput = React.forwardRef<{ focus: () => void }, BlocksInputProps>(\n ({ label, name, required = false, hint, labelAction, ...editorProps }, forwardedRef) => {\n const id = React.useId();\n const field = useField(name);\n\n return (\n <Field.Root id={id} name={name} hint={hint} error={field.error} required={required}>\n <Flex direction=\"column\" alignItems=\"stretch\" gap={1}>\n <Field.Label action={labelAction}>{label}</Field.Label>\n <BlocksEditor\n name={name}\n error={field.error}\n ref={forwardedRef}\n value={field.value}\n onChange={field.onChange}\n ariaLabelId={id}\n {...editorProps}\n />\n <Field.Hint />\n <Field.Error />\n </Flex>\n </Field.Root>\n );\n }\n);\n\nconst MemoizedBlocksInput = React.memo(BlocksInput);\n\nexport { MemoizedBlocksInput as BlocksInput };\n"],"names":["BlocksInput","React","forwardRef","label","name","required","hint","labelAction","editorProps","forwardedRef","id","useId","field","useField","_jsx","Field","Root","error","_jsxs","Flex","direction","alignItems","gap","Label","action","BlocksEditor","ref","value","onChange","ariaLabelId","Hint","Error","MemoizedBlocksInput","memo"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,MAAMA,4BAAcC,gBAAMC,CAAAA,UAAU,CAClC,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAEC,QAAW,GAAA,KAAK,EAAEC,IAAI,EAAEC,WAAW,EAAE,GAAGC,aAAa,EAAEC,YAAAA,GAAAA;IACrE,MAAMC,EAAAA,GAAKT,iBAAMU,KAAK,EAAA;AACtB,IAAA,MAAMC,QAAQC,oBAAST,CAAAA,IAAAA,CAAAA;IAEvB,qBACEU,cAAA,CAACC,mBAAMC,IAAI,EAAA;QAACN,EAAIA,EAAAA,EAAAA;QAAIN,IAAMA,EAAAA,IAAAA;QAAME,IAAMA,EAAAA,IAAAA;AAAMW,QAAAA,KAAAA,EAAOL,MAAMK,KAAK;QAAEZ,QAAUA,EAAAA,QAAAA;AACxE,QAAA,QAAA,gBAAAa,eAACC,CAAAA,iBAAAA,EAAAA;YAAKC,SAAU,EAAA,QAAA;YAASC,UAAW,EAAA,SAAA;YAAUC,GAAK,EAAA,CAAA;;AACjD,8BAAAR,cAAA,CAACC,mBAAMQ,KAAK,EAAA;oBAACC,MAAQjB,EAAAA,WAAAA;AAAcJ,oBAAAA,QAAAA,EAAAA;;8BACnCW,cAACW,CAAAA,yBAAAA,EAAAA;oBACCrB,IAAMA,EAAAA,IAAAA;AACNa,oBAAAA,KAAAA,EAAOL,MAAMK,KAAK;oBAClBS,GAAKjB,EAAAA,YAAAA;AACLkB,oBAAAA,KAAAA,EAAOf,MAAMe,KAAK;AAClBC,oBAAAA,QAAAA,EAAUhB,MAAMgB,QAAQ;oBACxBC,WAAanB,EAAAA,EAAAA;AACZ,oBAAA,GAAGF;;AAEN,8BAAAM,cAAA,CAACC,mBAAMe,IAAI,EAAA,EAAA,CAAA;AACX,8BAAAhB,cAAA,CAACC,mBAAMgB,KAAK,EAAA,EAAA;;;;AAIpB,CAAA,CAAA;AAGIC,MAAAA,mBAAAA,iBAAsB/B,gBAAMgC,CAAAA,IAAI,CAACjC,WAAAA;;;;"}

View File

@@ -0,0 +1,43 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import * as React from 'react';
import { useField } from '@strapi/admin/strapi-admin';
import { Field, Flex } from '@strapi/design-system';
import { BlocksEditor } from './BlocksEditor.mjs';
const BlocksInput = /*#__PURE__*/ React.forwardRef(({ label, name, required = false, hint, labelAction, ...editorProps }, forwardedRef)=>{
const id = React.useId();
const field = useField(name);
return /*#__PURE__*/ jsx(Field.Root, {
id: id,
name: name,
hint: hint,
error: field.error,
required: required,
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "stretch",
gap: 1,
children: [
/*#__PURE__*/ jsx(Field.Label, {
action: labelAction,
children: label
}),
/*#__PURE__*/ jsx(BlocksEditor, {
name: name,
error: field.error,
ref: forwardedRef,
value: field.value,
onChange: field.onChange,
ariaLabelId: id,
...editorProps
}),
/*#__PURE__*/ jsx(Field.Hint, {}),
/*#__PURE__*/ jsx(Field.Error, {})
]
})
});
});
const MemoizedBlocksInput = /*#__PURE__*/ React.memo(BlocksInput);
export { MemoizedBlocksInput as BlocksInput };
//# sourceMappingURL=BlocksInput.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"BlocksInput.mjs","sources":["../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksInput.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { useField, type InputProps } from '@strapi/admin/strapi-admin';\nimport { Field, Flex } from '@strapi/design-system';\n\nimport { BlocksEditor } from './BlocksEditor';\n\nimport type { Schema } from '@strapi/types';\n\ninterface BlocksInputProps extends Omit<InputProps, 'type'> {\n labelAction?: React.ReactNode;\n type: Schema.Attribute.Blocks['type'];\n}\n\nconst BlocksInput = React.forwardRef<{ focus: () => void }, BlocksInputProps>(\n ({ label, name, required = false, hint, labelAction, ...editorProps }, forwardedRef) => {\n const id = React.useId();\n const field = useField(name);\n\n return (\n <Field.Root id={id} name={name} hint={hint} error={field.error} required={required}>\n <Flex direction=\"column\" alignItems=\"stretch\" gap={1}>\n <Field.Label action={labelAction}>{label}</Field.Label>\n <BlocksEditor\n name={name}\n error={field.error}\n ref={forwardedRef}\n value={field.value}\n onChange={field.onChange}\n ariaLabelId={id}\n {...editorProps}\n />\n <Field.Hint />\n <Field.Error />\n </Flex>\n </Field.Root>\n );\n }\n);\n\nconst MemoizedBlocksInput = React.memo(BlocksInput);\n\nexport { MemoizedBlocksInput as BlocksInput };\n"],"names":["BlocksInput","React","forwardRef","label","name","required","hint","labelAction","editorProps","forwardedRef","id","useId","field","useField","_jsx","Field","Root","error","_jsxs","Flex","direction","alignItems","gap","Label","action","BlocksEditor","ref","value","onChange","ariaLabelId","Hint","Error","MemoizedBlocksInput","memo"],"mappings":";;;;;;AAcA,MAAMA,4BAAcC,KAAMC,CAAAA,UAAU,CAClC,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAEC,QAAW,GAAA,KAAK,EAAEC,IAAI,EAAEC,WAAW,EAAE,GAAGC,aAAa,EAAEC,YAAAA,GAAAA;IACrE,MAAMC,EAAAA,GAAKT,MAAMU,KAAK,EAAA;AACtB,IAAA,MAAMC,QAAQC,QAAST,CAAAA,IAAAA,CAAAA;IAEvB,qBACEU,GAAA,CAACC,MAAMC,IAAI,EAAA;QAACN,EAAIA,EAAAA,EAAAA;QAAIN,IAAMA,EAAAA,IAAAA;QAAME,IAAMA,EAAAA,IAAAA;AAAMW,QAAAA,KAAAA,EAAOL,MAAMK,KAAK;QAAEZ,QAAUA,EAAAA,QAAAA;AACxE,QAAA,QAAA,gBAAAa,IAACC,CAAAA,IAAAA,EAAAA;YAAKC,SAAU,EAAA,QAAA;YAASC,UAAW,EAAA,SAAA;YAAUC,GAAK,EAAA,CAAA;;AACjD,8BAAAR,GAAA,CAACC,MAAMQ,KAAK,EAAA;oBAACC,MAAQjB,EAAAA,WAAAA;AAAcJ,oBAAAA,QAAAA,EAAAA;;8BACnCW,GAACW,CAAAA,YAAAA,EAAAA;oBACCrB,IAAMA,EAAAA,IAAAA;AACNa,oBAAAA,KAAAA,EAAOL,MAAMK,KAAK;oBAClBS,GAAKjB,EAAAA,YAAAA;AACLkB,oBAAAA,KAAAA,EAAOf,MAAMe,KAAK;AAClBC,oBAAAA,QAAAA,EAAUhB,MAAMgB,QAAQ;oBACxBC,WAAanB,EAAAA,EAAAA;AACZ,oBAAA,GAAGF;;AAEN,8BAAAM,GAAA,CAACC,MAAMe,IAAI,EAAA,EAAA,CAAA;AACX,8BAAAhB,GAAA,CAACC,MAAMgB,KAAK,EAAA,EAAA;;;;AAIpB,CAAA,CAAA;AAGIC,MAAAA,mBAAAA,iBAAsB/B,KAAMgC,CAAAA,IAAI,CAACjC,WAAAA;;;;"}

View File

@@ -0,0 +1,635 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var Toolbar = require('@radix-ui/react-toolbar');
require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var slate = require('slate');
var slateReact = require('slate-react');
var styledComponents = require('styled-components');
var EditorToolbarObserver = require('../../EditorToolbarObserver.js');
var BlocksEditor = require('./BlocksEditor.js');
var links = require('./utils/links.js');
var types = require('./utils/types.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
var Toolbar__namespace = /*#__PURE__*/_interopNamespaceDefault(Toolbar);
const ToolbarWrapper = styledComponents.styled(designSystem.Flex)`
&[aria-disabled='true'] {
cursor: not-allowed;
background: ${({ theme })=>theme.colors.neutral150};
}
`;
const ToolbarSeparator = styledComponents.styled(Toolbar__namespace.Separator)`
background: ${({ theme })=>theme.colors.neutral150};
width: 1px;
height: 2.4rem;
`;
const FlexButton = styledComponents.styled(designSystem.Flex)`
// Inherit the not-allowed cursor from ToolbarWrapper when disabled
&[aria-disabled] {
cursor: not-allowed;
}
&[aria-disabled='false'] {
cursor: pointer;
// Only apply hover styles if the button is enabled
&:hover {
background: ${({ theme })=>theme.colors.primary100};
}
}
`;
const SelectWrapper = styledComponents.styled(designSystem.Box)`
// Styling changes to SingleSelect component don't work, so adding wrapper to target SingleSelect
div[role='combobox'] {
border: none;
cursor: pointer;
min-height: unset;
padding-top: 6px;
padding-bottom: 6px;
&[aria-disabled='false']:hover {
cursor: pointer;
background: ${({ theme })=>theme.colors.primary100};
}
&[aria-disabled] {
background: transparent;
cursor: inherit;
// Select text and icons should also have disabled color
span {
color: ${({ theme })=>theme.colors.neutral600};
}
}
}
`;
/**
* Handles the modal component that may be returned by a block when converting it
*/ function useConversionModal() {
const [modalElement, setModalComponent] = React__namespace.useState(null);
const handleConversionResult = (renderModal)=>{
// Not all blocks return a modal
if (renderModal) {
// Use cloneElement to apply a key because to create a new instance of the component
// Without the new key, the state is kept from previous times that option was picked
setModalComponent(/*#__PURE__*/ React__namespace.cloneElement(renderModal(), {
key: Date.now()
}));
}
};
return {
modalElement,
handleConversionResult
};
}
const ToolbarButton = ({ icon: Icon, name, label, isActive, disabled, handleClick })=>{
const { editor } = BlocksEditor.useBlocksEditorContext('ToolbarButton');
const { formatMessage } = reactIntl.useIntl();
const labelMessage = formatMessage(label);
const enabledColor = isActive ? 'primary600' : 'neutral600';
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Tooltip, {
label: labelMessage,
children: /*#__PURE__*/ jsxRuntime.jsx(Toolbar__namespace.ToggleItem, {
value: name,
"data-state": isActive ? 'on' : 'off',
onMouseDown: (e)=>{
e.preventDefault();
handleClick();
slateReact.ReactEditor.focus(editor);
},
"aria-disabled": disabled,
disabled: disabled,
"aria-label": labelMessage,
asChild: true,
children: /*#__PURE__*/ jsxRuntime.jsx(FlexButton, {
tag: "button",
background: isActive ? 'primary100' : '',
alignItems: "center",
justifyContent: "center",
width: 7,
height: 7,
hasRadius: true,
children: /*#__PURE__*/ jsxRuntime.jsx(Icon, {
fill: disabled ? 'neutral300' : enabledColor
})
})
})
});
};
const BlocksDropdown = ()=>{
const { editor, blocks, disabled } = BlocksEditor.useBlocksEditorContext('BlocksDropdown');
const { formatMessage } = reactIntl.useIntl();
const { modalElement, handleConversionResult } = useConversionModal();
const blockKeysToInclude = types.getEntries(blocks).reduce((currentKeys, entry)=>{
const [key, block] = entry;
return block.isInBlocksSelector ? [
...currentKeys,
key
] : currentKeys;
}, []);
const [blockSelected, setBlockSelected] = React__namespace.useState('paragraph');
const handleSelect = (optionKey)=>{
if (!BlocksEditor.isSelectorBlockKey(optionKey)) {
return;
}
const editorIsEmpty = editor.children.length === 1 && slate.Editor.isEmpty(editor, editor.children[0]);
if (!editor.selection && !editorIsEmpty) {
// When there is no selection, create an empty block at the end of the editor
// so that it can be converted to the selected block
slate.Transforms.insertNodes(editor, {
type: 'quote',
children: [
{
type: 'text',
text: ''
}
]
}, {
select: true
});
} else if (!editor.selection && editorIsEmpty) {
// When there is no selection and the editor is empty,
// select the empty paragraph from Slate's initialValue so it gets converted
slate.Transforms.select(editor, slate.Editor.start(editor, [
0,
0
]));
}
// If selection is already a list block, toggle its format
const currentListEntry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list'
});
if (currentListEntry && [
'list-ordered',
'list-unordered'
].includes(optionKey)) {
const [currentList, currentListPath] = currentListEntry;
const format = optionKey === 'list-ordered' ? 'ordered' : 'unordered';
if (!slate.Editor.isEditor(currentList) && isListNode(currentList)) {
// Format is different, toggle list format
if (currentList.format !== format) {
slate.Transforms.setNodes(editor, {
format
}, {
at: currentListPath
});
}
}
return;
}
// Let the block handle the Slate conversion logic
const maybeRenderModal = blocks[optionKey].handleConvert?.(editor);
handleConversionResult(maybeRenderModal);
setBlockSelected(optionKey);
slateReact.ReactEditor.focus(editor);
};
/**
* Prevent the select from focusing itself so ReactEditor.focus(editor) can focus the editor instead.
*
* The editor first loses focus to a blur event when clicking the select button. However,
* refocusing the editor is not enough since the select's default behavior is to refocus itself
* after an option is selected.
*
*/ const preventSelectFocus = (e)=>e.preventDefault();
// Listen to the selection change and update the selected block in the dropdown
React__namespace.useEffect(()=>{
if (editor.selection) {
let selectedNode;
// If selection anchor is a list-item, get its parent
const currentListEntry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list',
at: editor.selection.anchor
});
if (currentListEntry) {
const [currentList] = currentListEntry;
selectedNode = currentList;
} else {
// Get the parent node of the anchor other than list-item
const [anchorNode] = slate.Editor.parent(editor, editor.selection.anchor, {
edge: 'start',
depth: 2
});
// @ts-expect-error slate's delete behaviour creates an exceptional type
if (anchorNode.type === 'list-item') {
// When the last node in the selection is a list item,
// slate's default delete operation leaves an empty list-item instead of converting it into a paragraph.
// Issue: https://github.com/ianstormtaylor/slate/issues/2500
slate.Transforms.setNodes(editor, {
type: 'paragraph'
});
// @ts-expect-error convert explicitly type to paragraph
selectedNode = {
...anchorNode,
type: 'paragraph'
};
} else {
selectedNode = anchorNode;
}
}
// Find the block key that matches the anchor node
const anchorBlockKey = types.getKeys(blocks).find((blockKey)=>!slate.Editor.isEditor(selectedNode) && blocks[blockKey].matchNode(selectedNode));
// Change the value selected in the dropdown if it doesn't match the anchor block key
if (anchorBlockKey && anchorBlockKey !== blockSelected) {
setBlockSelected(anchorBlockKey);
}
}
}, [
editor.selection,
editor,
blocks,
blockSelected
]);
const Icon = blocks[blockSelected].icon;
return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(SelectWrapper, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelect, {
startIcon: /*#__PURE__*/ jsxRuntime.jsx(Icon, {}),
onChange: handleSelect,
placeholder: formatMessage(blocks[blockSelected].label),
value: blockSelected,
onCloseAutoFocus: preventSelectFocus,
"aria-label": formatMessage({
id: 'components.Blocks.blocks.selectBlock',
defaultMessage: 'Select a block'
}),
disabled: disabled,
children: blockKeysToInclude.map((key)=>/*#__PURE__*/ jsxRuntime.jsx(BlockOption, {
value: key,
label: blocks[key].label,
icon: blocks[key].icon,
blockSelected: blockSelected
}, key))
})
}),
modalElement
]
});
};
const BlockOption = ({ value, icon: Icon, label, blockSelected })=>{
const { formatMessage } = reactIntl.useIntl();
const isSelected = value === blockSelected;
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelectOption, {
startIcon: /*#__PURE__*/ jsxRuntime.jsx(Icon, {
fill: isSelected ? 'primary600' : 'neutral600'
}),
value: value,
children: formatMessage(label)
});
};
const isListNode = (node)=>{
return slate.Node.isNode(node) && !slate.Editor.isEditor(node) && node.type === 'list';
};
const ListButton = ({ block, format, location = 'toolbar' })=>{
const { editor, disabled, blocks } = BlocksEditor.useBlocksEditorContext('ListButton');
const { formatMessage } = reactIntl.useIntl();
const isListActive = ()=>{
if (!editor.selection) return false;
// Get the parent list at selection anchor node
const currentListEntry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list',
at: editor.selection.anchor
});
if (currentListEntry) {
const [currentList] = currentListEntry;
if (!slate.Editor.isEditor(currentList) && isListNode(currentList) && currentList.format === format) return true;
}
return false;
};
/**
* @TODO: Currently, applying list while multiple blocks are selected is not supported.
* We should implement this feature in the future.
*/ const isListDisabled = ()=>{
// Always disabled when the whole editor is disabled
if (disabled) {
return true;
}
// Always enabled when there's no selection
if (!editor.selection) {
return false;
}
// Get the block node closest to the anchor and focus
const anchorNodeEntry = slate.Editor.above(editor, {
at: editor.selection.anchor,
match: (node)=>!slate.Editor.isEditor(node) && node.type !== 'text'
});
const focusNodeEntry = slate.Editor.above(editor, {
at: editor.selection.focus,
match: (node)=>!slate.Editor.isEditor(node) && node.type !== 'text'
});
if (!anchorNodeEntry || !focusNodeEntry) {
return false;
}
// Disabled if the anchor and focus are not in the same block
return anchorNodeEntry[0] !== focusNodeEntry[0];
};
const toggleList = (format)=>{
let currentListEntry;
if (editor.selection) {
currentListEntry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list'
});
} else {
// If no selection, toggle last inserted node
const [_, lastNodePath] = slate.Editor.last(editor, []);
currentListEntry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list',
at: lastNodePath
});
}
if (!currentListEntry) {
// If selection is not a list then convert it to list
blocks[`list-${format}`].handleConvert(editor);
return;
}
// If selection is already a list then toggle format
const [currentList, currentListPath] = currentListEntry;
if (!slate.Editor.isEditor(currentList) && isListNode(currentList)) {
if (currentList.format !== format) {
// Format is different, toggle list format
slate.Transforms.setNodes(editor, {
format
}, {
at: currentListPath
});
} else {
// Format is same, convert selected list-item to paragraph
blocks['paragraph'].handleConvert(editor);
}
}
};
if (location === 'menu') {
const Icon = block.icon;
return /*#__PURE__*/ jsxRuntime.jsxs(StyledMenuItem, {
onSelect: ()=>toggleList(format),
isActive: isListActive(),
disabled: isListDisabled(),
children: [
/*#__PURE__*/ jsxRuntime.jsx(Icon, {}),
formatMessage(block.label)
]
});
}
return /*#__PURE__*/ jsxRuntime.jsx(ToolbarButton, {
icon: block.icon,
name: format,
label: block.label,
isActive: isListActive(),
disabled: isListDisabled(),
handleClick: ()=>toggleList(format)
});
};
const LinkButton = ({ disabled, location = 'toolbar' })=>{
const { editor } = BlocksEditor.useBlocksEditorContext('LinkButton');
const { formatMessage } = reactIntl.useIntl();
const isLinkActive = ()=>{
const { selection } = editor;
if (!selection) return false;
const [match] = Array.from(slate.Editor.nodes(editor, {
at: slate.Editor.unhangRange(editor, selection),
match: (node)=>slate.Element.isElement(node) && node.type === 'link'
}));
return Boolean(match);
};
const isLinkDisabled = ()=>{
// Always disabled when the whole editor is disabled
if (disabled) {
return true;
}
// Always enabled when there's no selection
if (!editor.selection) {
return false;
}
// Get the block node closest to the anchor and focus
const anchorNodeEntry = slate.Editor.above(editor, {
at: editor.selection.anchor,
match: (node)=>!slate.Editor.isEditor(node) && node.type !== 'text'
});
const focusNodeEntry = slate.Editor.above(editor, {
at: editor.selection.focus,
match: (node)=>!slate.Editor.isEditor(node) && node.type !== 'text'
});
if (!anchorNodeEntry || !focusNodeEntry) {
return false;
}
// Disabled if the anchor and focus are not in the same block
return anchorNodeEntry[0] !== focusNodeEntry[0];
};
const addLink = ()=>{
editor.shouldSaveLinkPath = true;
// We insert an empty anchor, so we split the DOM to have a element we can use as reference for the popover
links.insertLink(editor, {
url: ''
});
};
const label = {
id: 'components.Blocks.link',
defaultMessage: 'Link'
};
if (location === 'menu') {
return /*#__PURE__*/ jsxRuntime.jsxs(StyledMenuItem, {
onSelect: addLink,
isActive: isLinkActive(),
disabled: isLinkDisabled(),
children: [
/*#__PURE__*/ jsxRuntime.jsx(Icons.Link, {}),
formatMessage(label)
]
});
}
return /*#__PURE__*/ jsxRuntime.jsx(ToolbarButton, {
icon: Icons.Link,
name: "link",
label: label,
isActive: isLinkActive(),
handleClick: addLink,
disabled: isLinkDisabled()
});
};
const StyledMenuItem = styledComponents.styled(designSystem.Menu.Item)`
&:hover {
background-color: ${({ theme })=>theme.colors.primary100};
}
${(props)=>props.isActive && styledComponents.css`
font-weight: bold;
background-color: ${({ theme })=>theme.colors.primary100};
color: ${({ theme })=>theme.colors.primary600};
font-weight: bold;
`}
> span {
display: inline-flex;
gap: ${({ theme })=>theme.spaces[2]};
align-items: center;
}
svg {
fill: ${({ theme, isActive })=>isActive ? theme.colors.primary600 : theme.colors.neutral600};
}
`;
const BlocksToolbar = ()=>{
const { editor, blocks, modifiers, disabled } = BlocksEditor.useBlocksEditorContext('BlocksToolbar');
const { formatMessage } = reactIntl.useIntl();
/**
* The modifier buttons are disabled when an image is selected.
*/ const checkButtonDisabled = ()=>{
// Always disabled when the whole editor is disabled
if (disabled) {
return true;
}
if (!editor.selection) {
return false;
}
const selectedNode = editor.children[editor.selection.anchor.path[0]];
if ([
'image',
'code'
].includes(selectedNode.type)) {
return true;
}
return false;
};
const isButtonDisabled = checkButtonDisabled();
/**
* Observed components are ones that may or may not be visible in the toolbar, depending on the
* available space. They provide two render props:
* - renderInToolbar: for when we try to render the component in the toolbar (may be hidden)
* - renderInMenu: for when the component didn't fit in the toolbar and is relegated
* to the "more" menu
*/ const observedComponents = [
...Object.entries(modifiers).map(([name, modifier])=>{
const Icon = modifier.icon;
const isActive = modifier.checkIsActive(editor);
const handleSelect = ()=>modifier.handleToggle(editor);
return {
toolbar: /*#__PURE__*/ jsxRuntime.jsx(ToolbarButton, {
name: name,
icon: modifier.icon,
label: modifier.label,
isActive: modifier.checkIsActive(editor),
handleClick: handleSelect,
disabled: isButtonDisabled
}, name),
menu: /*#__PURE__*/ jsxRuntime.jsxs(StyledMenuItem, {
onSelect: handleSelect,
isActive: isActive,
children: [
/*#__PURE__*/ jsxRuntime.jsx(Icon, {}),
formatMessage(modifier.label)
]
}),
key: `modifier.${name}`
};
}),
{
toolbar: /*#__PURE__*/ jsxRuntime.jsx(LinkButton, {
disabled: isButtonDisabled,
location: "toolbar"
}),
menu: /*#__PURE__*/ jsxRuntime.jsx(LinkButton, {
disabled: isButtonDisabled,
location: "menu"
}),
key: 'block.link'
},
{
// List buttons can only be rendered together when in the toolbar
toolbar: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "row",
gap: 1,
children: [
/*#__PURE__*/ jsxRuntime.jsx(ToolbarSeparator, {}),
/*#__PURE__*/ jsxRuntime.jsx(Toolbar__namespace.ToggleGroup, {
type: "single",
asChild: true,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 1,
children: [
/*#__PURE__*/ jsxRuntime.jsx(ListButton, {
block: blocks['list-unordered'],
format: "unordered",
location: "toolbar"
}),
/*#__PURE__*/ jsxRuntime.jsx(ListButton, {
block: blocks['list-ordered'],
format: "ordered",
location: "toolbar"
})
]
})
})
]
}),
menu: /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Separator, {}),
/*#__PURE__*/ jsxRuntime.jsx(ListButton, {
block: blocks['list-unordered'],
format: "unordered",
location: "menu"
}),
/*#__PURE__*/ jsxRuntime.jsx(ListButton, {
block: blocks['list-ordered'],
format: "ordered",
location: "menu"
})
]
}),
key: 'block.list'
}
];
return /*#__PURE__*/ jsxRuntime.jsx(Toolbar__namespace.Root, {
"aria-disabled": disabled,
asChild: true,
children: /*#__PURE__*/ jsxRuntime.jsxs(ToolbarWrapper, {
gap: 2,
padding: 2,
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(BlocksDropdown, {}),
/*#__PURE__*/ jsxRuntime.jsx(ToolbarSeparator, {}),
/*#__PURE__*/ jsxRuntime.jsx(Toolbar__namespace.ToggleGroup, {
type: "multiple",
asChild: true,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
direction: "row",
gap: 1,
grow: 1,
overflow: "hidden",
children: /*#__PURE__*/ jsxRuntime.jsx(EditorToolbarObserver.EditorToolbarObserver, {
observedComponents: observedComponents
})
})
})
]
})
});
};
exports.BlocksToolbar = BlocksToolbar;
exports.useConversionModal = useConversionModal;
//# sourceMappingURL=BlocksToolbar.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,612 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import * as Toolbar from '@radix-ui/react-toolbar';
import '@strapi/admin/strapi-admin';
import { Flex, Box, Menu, Tooltip, SingleSelect, SingleSelectOption } from '@strapi/design-system';
import { Link } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { Editor, Transforms, Node, Element } from 'slate';
import { ReactEditor } from 'slate-react';
import { styled, css } from 'styled-components';
import { EditorToolbarObserver } from '../../EditorToolbarObserver.mjs';
import { useBlocksEditorContext, isSelectorBlockKey } from './BlocksEditor.mjs';
import { insertLink } from './utils/links.mjs';
import { getEntries, getKeys } from './utils/types.mjs';
const ToolbarWrapper = styled(Flex)`
&[aria-disabled='true'] {
cursor: not-allowed;
background: ${({ theme })=>theme.colors.neutral150};
}
`;
const ToolbarSeparator = styled(Toolbar.Separator)`
background: ${({ theme })=>theme.colors.neutral150};
width: 1px;
height: 2.4rem;
`;
const FlexButton = styled(Flex)`
// Inherit the not-allowed cursor from ToolbarWrapper when disabled
&[aria-disabled] {
cursor: not-allowed;
}
&[aria-disabled='false'] {
cursor: pointer;
// Only apply hover styles if the button is enabled
&:hover {
background: ${({ theme })=>theme.colors.primary100};
}
}
`;
const SelectWrapper = styled(Box)`
// Styling changes to SingleSelect component don't work, so adding wrapper to target SingleSelect
div[role='combobox'] {
border: none;
cursor: pointer;
min-height: unset;
padding-top: 6px;
padding-bottom: 6px;
&[aria-disabled='false']:hover {
cursor: pointer;
background: ${({ theme })=>theme.colors.primary100};
}
&[aria-disabled] {
background: transparent;
cursor: inherit;
// Select text and icons should also have disabled color
span {
color: ${({ theme })=>theme.colors.neutral600};
}
}
}
`;
/**
* Handles the modal component that may be returned by a block when converting it
*/ function useConversionModal() {
const [modalElement, setModalComponent] = React.useState(null);
const handleConversionResult = (renderModal)=>{
// Not all blocks return a modal
if (renderModal) {
// Use cloneElement to apply a key because to create a new instance of the component
// Without the new key, the state is kept from previous times that option was picked
setModalComponent(/*#__PURE__*/ React.cloneElement(renderModal(), {
key: Date.now()
}));
}
};
return {
modalElement,
handleConversionResult
};
}
const ToolbarButton = ({ icon: Icon, name, label, isActive, disabled, handleClick })=>{
const { editor } = useBlocksEditorContext('ToolbarButton');
const { formatMessage } = useIntl();
const labelMessage = formatMessage(label);
const enabledColor = isActive ? 'primary600' : 'neutral600';
return /*#__PURE__*/ jsx(Tooltip, {
label: labelMessage,
children: /*#__PURE__*/ jsx(Toolbar.ToggleItem, {
value: name,
"data-state": isActive ? 'on' : 'off',
onMouseDown: (e)=>{
e.preventDefault();
handleClick();
ReactEditor.focus(editor);
},
"aria-disabled": disabled,
disabled: disabled,
"aria-label": labelMessage,
asChild: true,
children: /*#__PURE__*/ jsx(FlexButton, {
tag: "button",
background: isActive ? 'primary100' : '',
alignItems: "center",
justifyContent: "center",
width: 7,
height: 7,
hasRadius: true,
children: /*#__PURE__*/ jsx(Icon, {
fill: disabled ? 'neutral300' : enabledColor
})
})
})
});
};
const BlocksDropdown = ()=>{
const { editor, blocks, disabled } = useBlocksEditorContext('BlocksDropdown');
const { formatMessage } = useIntl();
const { modalElement, handleConversionResult } = useConversionModal();
const blockKeysToInclude = getEntries(blocks).reduce((currentKeys, entry)=>{
const [key, block] = entry;
return block.isInBlocksSelector ? [
...currentKeys,
key
] : currentKeys;
}, []);
const [blockSelected, setBlockSelected] = React.useState('paragraph');
const handleSelect = (optionKey)=>{
if (!isSelectorBlockKey(optionKey)) {
return;
}
const editorIsEmpty = editor.children.length === 1 && Editor.isEmpty(editor, editor.children[0]);
if (!editor.selection && !editorIsEmpty) {
// When there is no selection, create an empty block at the end of the editor
// so that it can be converted to the selected block
Transforms.insertNodes(editor, {
type: 'quote',
children: [
{
type: 'text',
text: ''
}
]
}, {
select: true
});
} else if (!editor.selection && editorIsEmpty) {
// When there is no selection and the editor is empty,
// select the empty paragraph from Slate's initialValue so it gets converted
Transforms.select(editor, Editor.start(editor, [
0,
0
]));
}
// If selection is already a list block, toggle its format
const currentListEntry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'list'
});
if (currentListEntry && [
'list-ordered',
'list-unordered'
].includes(optionKey)) {
const [currentList, currentListPath] = currentListEntry;
const format = optionKey === 'list-ordered' ? 'ordered' : 'unordered';
if (!Editor.isEditor(currentList) && isListNode(currentList)) {
// Format is different, toggle list format
if (currentList.format !== format) {
Transforms.setNodes(editor, {
format
}, {
at: currentListPath
});
}
}
return;
}
// Let the block handle the Slate conversion logic
const maybeRenderModal = blocks[optionKey].handleConvert?.(editor);
handleConversionResult(maybeRenderModal);
setBlockSelected(optionKey);
ReactEditor.focus(editor);
};
/**
* Prevent the select from focusing itself so ReactEditor.focus(editor) can focus the editor instead.
*
* The editor first loses focus to a blur event when clicking the select button. However,
* refocusing the editor is not enough since the select's default behavior is to refocus itself
* after an option is selected.
*
*/ const preventSelectFocus = (e)=>e.preventDefault();
// Listen to the selection change and update the selected block in the dropdown
React.useEffect(()=>{
if (editor.selection) {
let selectedNode;
// If selection anchor is a list-item, get its parent
const currentListEntry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'list',
at: editor.selection.anchor
});
if (currentListEntry) {
const [currentList] = currentListEntry;
selectedNode = currentList;
} else {
// Get the parent node of the anchor other than list-item
const [anchorNode] = Editor.parent(editor, editor.selection.anchor, {
edge: 'start',
depth: 2
});
// @ts-expect-error slate's delete behaviour creates an exceptional type
if (anchorNode.type === 'list-item') {
// When the last node in the selection is a list item,
// slate's default delete operation leaves an empty list-item instead of converting it into a paragraph.
// Issue: https://github.com/ianstormtaylor/slate/issues/2500
Transforms.setNodes(editor, {
type: 'paragraph'
});
// @ts-expect-error convert explicitly type to paragraph
selectedNode = {
...anchorNode,
type: 'paragraph'
};
} else {
selectedNode = anchorNode;
}
}
// Find the block key that matches the anchor node
const anchorBlockKey = getKeys(blocks).find((blockKey)=>!Editor.isEditor(selectedNode) && blocks[blockKey].matchNode(selectedNode));
// Change the value selected in the dropdown if it doesn't match the anchor block key
if (anchorBlockKey && anchorBlockKey !== blockSelected) {
setBlockSelected(anchorBlockKey);
}
}
}, [
editor.selection,
editor,
blocks,
blockSelected
]);
const Icon = blocks[blockSelected].icon;
return /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx(SelectWrapper, {
children: /*#__PURE__*/ jsx(SingleSelect, {
startIcon: /*#__PURE__*/ jsx(Icon, {}),
onChange: handleSelect,
placeholder: formatMessage(blocks[blockSelected].label),
value: blockSelected,
onCloseAutoFocus: preventSelectFocus,
"aria-label": formatMessage({
id: 'components.Blocks.blocks.selectBlock',
defaultMessage: 'Select a block'
}),
disabled: disabled,
children: blockKeysToInclude.map((key)=>/*#__PURE__*/ jsx(BlockOption, {
value: key,
label: blocks[key].label,
icon: blocks[key].icon,
blockSelected: blockSelected
}, key))
})
}),
modalElement
]
});
};
const BlockOption = ({ value, icon: Icon, label, blockSelected })=>{
const { formatMessage } = useIntl();
const isSelected = value === blockSelected;
return /*#__PURE__*/ jsx(SingleSelectOption, {
startIcon: /*#__PURE__*/ jsx(Icon, {
fill: isSelected ? 'primary600' : 'neutral600'
}),
value: value,
children: formatMessage(label)
});
};
const isListNode = (node)=>{
return Node.isNode(node) && !Editor.isEditor(node) && node.type === 'list';
};
const ListButton = ({ block, format, location = 'toolbar' })=>{
const { editor, disabled, blocks } = useBlocksEditorContext('ListButton');
const { formatMessage } = useIntl();
const isListActive = ()=>{
if (!editor.selection) return false;
// Get the parent list at selection anchor node
const currentListEntry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'list',
at: editor.selection.anchor
});
if (currentListEntry) {
const [currentList] = currentListEntry;
if (!Editor.isEditor(currentList) && isListNode(currentList) && currentList.format === format) return true;
}
return false;
};
/**
* @TODO: Currently, applying list while multiple blocks are selected is not supported.
* We should implement this feature in the future.
*/ const isListDisabled = ()=>{
// Always disabled when the whole editor is disabled
if (disabled) {
return true;
}
// Always enabled when there's no selection
if (!editor.selection) {
return false;
}
// Get the block node closest to the anchor and focus
const anchorNodeEntry = Editor.above(editor, {
at: editor.selection.anchor,
match: (node)=>!Editor.isEditor(node) && node.type !== 'text'
});
const focusNodeEntry = Editor.above(editor, {
at: editor.selection.focus,
match: (node)=>!Editor.isEditor(node) && node.type !== 'text'
});
if (!anchorNodeEntry || !focusNodeEntry) {
return false;
}
// Disabled if the anchor and focus are not in the same block
return anchorNodeEntry[0] !== focusNodeEntry[0];
};
const toggleList = (format)=>{
let currentListEntry;
if (editor.selection) {
currentListEntry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'list'
});
} else {
// If no selection, toggle last inserted node
const [_, lastNodePath] = Editor.last(editor, []);
currentListEntry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'list',
at: lastNodePath
});
}
if (!currentListEntry) {
// If selection is not a list then convert it to list
blocks[`list-${format}`].handleConvert(editor);
return;
}
// If selection is already a list then toggle format
const [currentList, currentListPath] = currentListEntry;
if (!Editor.isEditor(currentList) && isListNode(currentList)) {
if (currentList.format !== format) {
// Format is different, toggle list format
Transforms.setNodes(editor, {
format
}, {
at: currentListPath
});
} else {
// Format is same, convert selected list-item to paragraph
blocks['paragraph'].handleConvert(editor);
}
}
};
if (location === 'menu') {
const Icon = block.icon;
return /*#__PURE__*/ jsxs(StyledMenuItem, {
onSelect: ()=>toggleList(format),
isActive: isListActive(),
disabled: isListDisabled(),
children: [
/*#__PURE__*/ jsx(Icon, {}),
formatMessage(block.label)
]
});
}
return /*#__PURE__*/ jsx(ToolbarButton, {
icon: block.icon,
name: format,
label: block.label,
isActive: isListActive(),
disabled: isListDisabled(),
handleClick: ()=>toggleList(format)
});
};
const LinkButton = ({ disabled, location = 'toolbar' })=>{
const { editor } = useBlocksEditorContext('LinkButton');
const { formatMessage } = useIntl();
const isLinkActive = ()=>{
const { selection } = editor;
if (!selection) return false;
const [match] = Array.from(Editor.nodes(editor, {
at: Editor.unhangRange(editor, selection),
match: (node)=>Element.isElement(node) && node.type === 'link'
}));
return Boolean(match);
};
const isLinkDisabled = ()=>{
// Always disabled when the whole editor is disabled
if (disabled) {
return true;
}
// Always enabled when there's no selection
if (!editor.selection) {
return false;
}
// Get the block node closest to the anchor and focus
const anchorNodeEntry = Editor.above(editor, {
at: editor.selection.anchor,
match: (node)=>!Editor.isEditor(node) && node.type !== 'text'
});
const focusNodeEntry = Editor.above(editor, {
at: editor.selection.focus,
match: (node)=>!Editor.isEditor(node) && node.type !== 'text'
});
if (!anchorNodeEntry || !focusNodeEntry) {
return false;
}
// Disabled if the anchor and focus are not in the same block
return anchorNodeEntry[0] !== focusNodeEntry[0];
};
const addLink = ()=>{
editor.shouldSaveLinkPath = true;
// We insert an empty anchor, so we split the DOM to have a element we can use as reference for the popover
insertLink(editor, {
url: ''
});
};
const label = {
id: 'components.Blocks.link',
defaultMessage: 'Link'
};
if (location === 'menu') {
return /*#__PURE__*/ jsxs(StyledMenuItem, {
onSelect: addLink,
isActive: isLinkActive(),
disabled: isLinkDisabled(),
children: [
/*#__PURE__*/ jsx(Link, {}),
formatMessage(label)
]
});
}
return /*#__PURE__*/ jsx(ToolbarButton, {
icon: Link,
name: "link",
label: label,
isActive: isLinkActive(),
handleClick: addLink,
disabled: isLinkDisabled()
});
};
const StyledMenuItem = styled(Menu.Item)`
&:hover {
background-color: ${({ theme })=>theme.colors.primary100};
}
${(props)=>props.isActive && css`
font-weight: bold;
background-color: ${({ theme })=>theme.colors.primary100};
color: ${({ theme })=>theme.colors.primary600};
font-weight: bold;
`}
> span {
display: inline-flex;
gap: ${({ theme })=>theme.spaces[2]};
align-items: center;
}
svg {
fill: ${({ theme, isActive })=>isActive ? theme.colors.primary600 : theme.colors.neutral600};
}
`;
const BlocksToolbar = ()=>{
const { editor, blocks, modifiers, disabled } = useBlocksEditorContext('BlocksToolbar');
const { formatMessage } = useIntl();
/**
* The modifier buttons are disabled when an image is selected.
*/ const checkButtonDisabled = ()=>{
// Always disabled when the whole editor is disabled
if (disabled) {
return true;
}
if (!editor.selection) {
return false;
}
const selectedNode = editor.children[editor.selection.anchor.path[0]];
if ([
'image',
'code'
].includes(selectedNode.type)) {
return true;
}
return false;
};
const isButtonDisabled = checkButtonDisabled();
/**
* Observed components are ones that may or may not be visible in the toolbar, depending on the
* available space. They provide two render props:
* - renderInToolbar: for when we try to render the component in the toolbar (may be hidden)
* - renderInMenu: for when the component didn't fit in the toolbar and is relegated
* to the "more" menu
*/ const observedComponents = [
...Object.entries(modifiers).map(([name, modifier])=>{
const Icon = modifier.icon;
const isActive = modifier.checkIsActive(editor);
const handleSelect = ()=>modifier.handleToggle(editor);
return {
toolbar: /*#__PURE__*/ jsx(ToolbarButton, {
name: name,
icon: modifier.icon,
label: modifier.label,
isActive: modifier.checkIsActive(editor),
handleClick: handleSelect,
disabled: isButtonDisabled
}, name),
menu: /*#__PURE__*/ jsxs(StyledMenuItem, {
onSelect: handleSelect,
isActive: isActive,
children: [
/*#__PURE__*/ jsx(Icon, {}),
formatMessage(modifier.label)
]
}),
key: `modifier.${name}`
};
}),
{
toolbar: /*#__PURE__*/ jsx(LinkButton, {
disabled: isButtonDisabled,
location: "toolbar"
}),
menu: /*#__PURE__*/ jsx(LinkButton, {
disabled: isButtonDisabled,
location: "menu"
}),
key: 'block.link'
},
{
// List buttons can only be rendered together when in the toolbar
toolbar: /*#__PURE__*/ jsxs(Flex, {
direction: "row",
gap: 1,
children: [
/*#__PURE__*/ jsx(ToolbarSeparator, {}),
/*#__PURE__*/ jsx(Toolbar.ToggleGroup, {
type: "single",
asChild: true,
children: /*#__PURE__*/ jsxs(Flex, {
gap: 1,
children: [
/*#__PURE__*/ jsx(ListButton, {
block: blocks['list-unordered'],
format: "unordered",
location: "toolbar"
}),
/*#__PURE__*/ jsx(ListButton, {
block: blocks['list-ordered'],
format: "ordered",
location: "toolbar"
})
]
})
})
]
}),
menu: /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx(Menu.Separator, {}),
/*#__PURE__*/ jsx(ListButton, {
block: blocks['list-unordered'],
format: "unordered",
location: "menu"
}),
/*#__PURE__*/ jsx(ListButton, {
block: blocks['list-ordered'],
format: "ordered",
location: "menu"
})
]
}),
key: 'block.list'
}
];
return /*#__PURE__*/ jsx(Toolbar.Root, {
"aria-disabled": disabled,
asChild: true,
children: /*#__PURE__*/ jsxs(ToolbarWrapper, {
gap: 2,
padding: 2,
width: "100%",
children: [
/*#__PURE__*/ jsx(BlocksDropdown, {}),
/*#__PURE__*/ jsx(ToolbarSeparator, {}),
/*#__PURE__*/ jsx(Toolbar.ToggleGroup, {
type: "multiple",
asChild: true,
children: /*#__PURE__*/ jsx(Flex, {
direction: "row",
gap: 1,
grow: 1,
overflow: "hidden",
children: /*#__PURE__*/ jsx(EditorToolbarObserver, {
observedComponents: observedComponents
})
})
})
]
})
});
};
export { BlocksToolbar, useConversionModal };
//# sourceMappingURL=BlocksToolbar.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,89 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var styledComponents = require('styled-components');
var translations = require('../../../../../utils/translations.js');
var BlocksEditor = require('./BlocksEditor.js');
const EditorLayout = ({ children, error, disabled, onToggleExpand, ariaDescriptionId })=>{
const { formatMessage } = reactIntl.useIntl();
const { isExpandedMode } = BlocksEditor.useBlocksEditorContext('editorLayout');
return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
isExpandedMode && /*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Root, {
open: isExpandedMode,
onOpenChange: onToggleExpand,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Content, {
style: {
maxWidth: 'unset',
width: 'unset'
},
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
height: "90vh",
width: "90vw",
alignItems: "flex-start",
direction: "column",
children: [
children,
/*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
position: "absolute",
bottom: "1.2rem",
right: "1.2rem",
shadow: "filterShadow",
label: formatMessage({
id: translations.getTranslation('components.Blocks.collapse'),
defaultMessage: 'Collapse'
}),
onClick: onToggleExpand,
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Collapse, {})
})
]
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(InputWrapper, {
direction: "column",
alignItems: "flex-start",
height: "512px",
$disabled: disabled,
$hasError: Boolean(error),
style: {
overflow: 'hidden'
},
"aria-describedby": ariaDescriptionId,
position: "relative",
children: !isExpandedMode && children
})
]
});
};
const InputWrapper = styledComponents.styled(designSystem.Flex)`
border: 1px solid
${({ theme, $hasError })=>$hasError ? theme.colors.danger600 : theme.colors.neutral200};
border-radius: ${({ theme })=>theme.borderRadius};
background: ${({ theme })=>theme.colors.neutral0};
${({ theme, $hasError = false })=>styledComponents.css`
outline: none;
box-shadow: 0;
transition-property: border-color, box-shadow, fill;
transition-duration: 0.2s;
&:focus-within {
border: 1px solid ${$hasError ? theme.colors.danger600 : theme.colors.primary600};
box-shadow: ${$hasError ? theme.colors.danger600 : theme.colors.primary600} 0px 0px 0px 2px;
}
`}
${({ theme, $disabled })=>$disabled ? styledComponents.css`
color: ${theme.colors.neutral600};
background: ${theme.colors.neutral150};
` : undefined}
`;
exports.EditorLayout = EditorLayout;
//# sourceMappingURL=EditorLayout.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,87 @@
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
import 'react';
import { Flex, Modal, IconButton } from '@strapi/design-system';
import { Collapse } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { styled, css } from 'styled-components';
import { getTranslation } from '../../../../../utils/translations.mjs';
import { useBlocksEditorContext } from './BlocksEditor.mjs';
const EditorLayout = ({ children, error, disabled, onToggleExpand, ariaDescriptionId })=>{
const { formatMessage } = useIntl();
const { isExpandedMode } = useBlocksEditorContext('editorLayout');
return /*#__PURE__*/ jsxs(Fragment, {
children: [
isExpandedMode && /*#__PURE__*/ jsx(Modal.Root, {
open: isExpandedMode,
onOpenChange: onToggleExpand,
children: /*#__PURE__*/ jsx(Modal.Content, {
style: {
maxWidth: 'unset',
width: 'unset'
},
children: /*#__PURE__*/ jsxs(Flex, {
height: "90vh",
width: "90vw",
alignItems: "flex-start",
direction: "column",
children: [
children,
/*#__PURE__*/ jsx(IconButton, {
position: "absolute",
bottom: "1.2rem",
right: "1.2rem",
shadow: "filterShadow",
label: formatMessage({
id: getTranslation('components.Blocks.collapse'),
defaultMessage: 'Collapse'
}),
onClick: onToggleExpand,
children: /*#__PURE__*/ jsx(Collapse, {})
})
]
})
})
}),
/*#__PURE__*/ jsx(InputWrapper, {
direction: "column",
alignItems: "flex-start",
height: "512px",
$disabled: disabled,
$hasError: Boolean(error),
style: {
overflow: 'hidden'
},
"aria-describedby": ariaDescriptionId,
position: "relative",
children: !isExpandedMode && children
})
]
});
};
const InputWrapper = styled(Flex)`
border: 1px solid
${({ theme, $hasError })=>$hasError ? theme.colors.danger600 : theme.colors.neutral200};
border-radius: ${({ theme })=>theme.borderRadius};
background: ${({ theme })=>theme.colors.neutral0};
${({ theme, $hasError = false })=>css`
outline: none;
box-shadow: 0;
transition-property: border-color, box-shadow, fill;
transition-duration: 0.2s;
&:focus-within {
border: 1px solid ${$hasError ? theme.colors.danger600 : theme.colors.primary600};
box-shadow: ${$hasError ? theme.colors.danger600 : theme.colors.primary600} 0px 0px 0px 2px;
}
`}
${({ theme, $disabled })=>$disabled ? css`
color: ${theme.colors.neutral600};
background: ${theme.colors.neutral150};
` : undefined}
`;
export { EditorLayout };
//# sourceMappingURL=EditorLayout.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,134 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var slate = require('slate');
var styledComponents = require('styled-components');
const stylesToInherit = styledComponents.css`
font-size: inherit;
color: inherit;
line-height: inherit;
`;
const BoldText = styledComponents.styled(designSystem.Typography).attrs({
fontWeight: 'bold'
})`
${stylesToInherit}
`;
const ItalicText = styledComponents.styled(designSystem.Typography)`
font-style: italic;
${stylesToInherit}
`;
const UnderlineText = styledComponents.styled(designSystem.Typography).attrs({
textDecoration: 'underline'
})`
${stylesToInherit}
`;
const StrikeThroughText = styledComponents.styled(designSystem.Typography).attrs({
textDecoration: 'line-through'
})`
${stylesToInherit}
`;
const InlineCode = styledComponents.styled.code`
background-color: ${({ theme })=>theme.colors.neutral150};
border-radius: ${({ theme })=>theme.borderRadius};
padding: ${({ theme })=>`0 ${theme.spaces[2]}`};
font-family: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas,
monospace;
color: inherit;
`;
/**
* The default handler for checking if a modifier is active
*/ const baseCheckIsActive = (editor, name)=>{
const marks = slate.Editor.marks(editor);
if (!marks) return false;
return Boolean(marks[name]);
};
/**
* The default handler for toggling a modifier
*/ const baseHandleToggle = (editor, name)=>{
const marks = slate.Editor.marks(editor);
// If there is no selection, set selection to the end of line
if (!editor.selection) {
const endOfEditor = slate.Editor.end(editor, []);
slate.Transforms.select(editor, endOfEditor);
}
// Toggle the modifier
if (marks?.[name]) {
slate.Editor.removeMark(editor, name);
} else {
slate.Editor.addMark(editor, name, true);
}
};
const modifiers = {
bold: {
icon: Icons.Bold,
isValidEventKey: (event)=>event.key === 'b',
label: {
id: 'components.Blocks.modifiers.bold',
defaultMessage: 'Bold'
},
checkIsActive: (editor)=>baseCheckIsActive(editor, 'bold'),
handleToggle: (editor)=>baseHandleToggle(editor, 'bold'),
renderLeaf: (children)=>/*#__PURE__*/ jsxRuntime.jsx(BoldText, {
children: children
})
},
italic: {
icon: Icons.Italic,
isValidEventKey: (event)=>event.key === 'i',
label: {
id: 'components.Blocks.modifiers.italic',
defaultMessage: 'Italic'
},
checkIsActive: (editor)=>baseCheckIsActive(editor, 'italic'),
handleToggle: (editor)=>baseHandleToggle(editor, 'italic'),
renderLeaf: (children)=>/*#__PURE__*/ jsxRuntime.jsx(ItalicText, {
children: children
})
},
underline: {
icon: Icons.Underline,
isValidEventKey: (event)=>event.key === 'u',
label: {
id: 'components.Blocks.modifiers.underline',
defaultMessage: 'Underline'
},
checkIsActive: (editor)=>baseCheckIsActive(editor, 'underline'),
handleToggle: (editor)=>baseHandleToggle(editor, 'underline'),
renderLeaf: (children)=>/*#__PURE__*/ jsxRuntime.jsx(UnderlineText, {
children: children
})
},
strikethrough: {
icon: Icons.StrikeThrough,
isValidEventKey: (event)=>event.key === 'S' && event.shiftKey,
label: {
id: 'components.Blocks.modifiers.strikethrough',
defaultMessage: 'Strikethrough'
},
checkIsActive: (editor)=>baseCheckIsActive(editor, 'strikethrough'),
handleToggle: (editor)=>baseHandleToggle(editor, 'strikethrough'),
renderLeaf: (children)=>/*#__PURE__*/ jsxRuntime.jsx(StrikeThroughText, {
children: children
})
},
code: {
icon: Icons.Code,
isValidEventKey: (event)=>event.key === 'e',
label: {
id: 'components.Blocks.modifiers.code',
defaultMessage: 'Inline code'
},
checkIsActive: (editor)=>baseCheckIsActive(editor, 'code'),
handleToggle: (editor)=>baseHandleToggle(editor, 'code'),
renderLeaf: (children)=>/*#__PURE__*/ jsxRuntime.jsx(InlineCode, {
children: children
})
}
};
exports.modifiers = modifiers;
//# sourceMappingURL=Modifiers.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,132 @@
import { jsx } from 'react/jsx-runtime';
import 'react';
import { Typography } from '@strapi/design-system';
import { Bold, Italic, Underline, StrikeThrough, Code } from '@strapi/icons';
import { Editor, Transforms } from 'slate';
import { css, styled } from 'styled-components';
const stylesToInherit = css`
font-size: inherit;
color: inherit;
line-height: inherit;
`;
const BoldText = styled(Typography).attrs({
fontWeight: 'bold'
})`
${stylesToInherit}
`;
const ItalicText = styled(Typography)`
font-style: italic;
${stylesToInherit}
`;
const UnderlineText = styled(Typography).attrs({
textDecoration: 'underline'
})`
${stylesToInherit}
`;
const StrikeThroughText = styled(Typography).attrs({
textDecoration: 'line-through'
})`
${stylesToInherit}
`;
const InlineCode = styled.code`
background-color: ${({ theme })=>theme.colors.neutral150};
border-radius: ${({ theme })=>theme.borderRadius};
padding: ${({ theme })=>`0 ${theme.spaces[2]}`};
font-family: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas,
monospace;
color: inherit;
`;
/**
* The default handler for checking if a modifier is active
*/ const baseCheckIsActive = (editor, name)=>{
const marks = Editor.marks(editor);
if (!marks) return false;
return Boolean(marks[name]);
};
/**
* The default handler for toggling a modifier
*/ const baseHandleToggle = (editor, name)=>{
const marks = Editor.marks(editor);
// If there is no selection, set selection to the end of line
if (!editor.selection) {
const endOfEditor = Editor.end(editor, []);
Transforms.select(editor, endOfEditor);
}
// Toggle the modifier
if (marks?.[name]) {
Editor.removeMark(editor, name);
} else {
Editor.addMark(editor, name, true);
}
};
const modifiers = {
bold: {
icon: Bold,
isValidEventKey: (event)=>event.key === 'b',
label: {
id: 'components.Blocks.modifiers.bold',
defaultMessage: 'Bold'
},
checkIsActive: (editor)=>baseCheckIsActive(editor, 'bold'),
handleToggle: (editor)=>baseHandleToggle(editor, 'bold'),
renderLeaf: (children)=>/*#__PURE__*/ jsx(BoldText, {
children: children
})
},
italic: {
icon: Italic,
isValidEventKey: (event)=>event.key === 'i',
label: {
id: 'components.Blocks.modifiers.italic',
defaultMessage: 'Italic'
},
checkIsActive: (editor)=>baseCheckIsActive(editor, 'italic'),
handleToggle: (editor)=>baseHandleToggle(editor, 'italic'),
renderLeaf: (children)=>/*#__PURE__*/ jsx(ItalicText, {
children: children
})
},
underline: {
icon: Underline,
isValidEventKey: (event)=>event.key === 'u',
label: {
id: 'components.Blocks.modifiers.underline',
defaultMessage: 'Underline'
},
checkIsActive: (editor)=>baseCheckIsActive(editor, 'underline'),
handleToggle: (editor)=>baseHandleToggle(editor, 'underline'),
renderLeaf: (children)=>/*#__PURE__*/ jsx(UnderlineText, {
children: children
})
},
strikethrough: {
icon: StrikeThrough,
isValidEventKey: (event)=>event.key === 'S' && event.shiftKey,
label: {
id: 'components.Blocks.modifiers.strikethrough',
defaultMessage: 'Strikethrough'
},
checkIsActive: (editor)=>baseCheckIsActive(editor, 'strikethrough'),
handleToggle: (editor)=>baseHandleToggle(editor, 'strikethrough'),
renderLeaf: (children)=>/*#__PURE__*/ jsx(StrikeThroughText, {
children: children
})
},
code: {
icon: Code,
isValidEventKey: (event)=>event.key === 'e',
label: {
id: 'components.Blocks.modifiers.code',
defaultMessage: 'Inline code'
},
checkIsActive: (editor)=>baseCheckIsActive(editor, 'code'),
handleToggle: (editor)=>baseHandleToggle(editor, 'code'),
renderLeaf: (children)=>/*#__PURE__*/ jsx(InlineCode, {
children: children
})
}
};
export { modifiers };
//# sourceMappingURL=Modifiers.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,17 @@
'use strict';
/**
* Images are void elements. They handle the rendering of their children instead of Slate.
* See the Slate documentation for more information:
* - https://docs.slatejs.org/api/nodes/element#void-vs-not-void
* - https://docs.slatejs.org/api/nodes/element#rendering-void-elements
*/ const withImages = (editor)=>{
const { isVoid } = editor;
editor.isVoid = (element)=>{
return element.type === 'image' ? true : isVoid(element);
};
return editor;
};
exports.withImages = withImages;
//# sourceMappingURL=withImages.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"withImages.js","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/plugins/withImages.ts"],"sourcesContent":["import { type Editor } from 'slate';\n\n/**\n * Images are void elements. They handle the rendering of their children instead of Slate.\n * See the Slate documentation for more information:\n * - https://docs.slatejs.org/api/nodes/element#void-vs-not-void\n * - https://docs.slatejs.org/api/nodes/element#rendering-void-elements\n */\nconst withImages = (editor: Editor) => {\n const { isVoid } = editor;\n\n editor.isVoid = (element) => {\n return element.type === 'image' ? true : isVoid(element);\n };\n\n return editor;\n};\n\nexport { withImages };\n"],"names":["withImages","editor","isVoid","element","type"],"mappings":";;AAEA;;;;;IAMA,MAAMA,aAAa,CAACC,MAAAA,GAAAA;IAClB,MAAM,EAAEC,MAAM,EAAE,GAAGD,MAAAA;IAEnBA,MAAOC,CAAAA,MAAM,GAAG,CAACC,OAAAA,GAAAA;AACf,QAAA,OAAOA,OAAQC,CAAAA,IAAI,KAAK,OAAA,GAAU,OAAOF,MAAOC,CAAAA,OAAAA,CAAAA;AAClD,KAAA;IAEA,OAAOF,MAAAA;AACT;;;;"}

View File

@@ -0,0 +1,15 @@
/**
* Images are void elements. They handle the rendering of their children instead of Slate.
* See the Slate documentation for more information:
* - https://docs.slatejs.org/api/nodes/element#void-vs-not-void
* - https://docs.slatejs.org/api/nodes/element#rendering-void-elements
*/ const withImages = (editor)=>{
const { isVoid } = editor;
editor.isVoid = (element)=>{
return element.type === 'image' ? true : isVoid(element);
};
return editor;
};
export { withImages };
//# sourceMappingURL=withImages.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"withImages.mjs","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/plugins/withImages.ts"],"sourcesContent":["import { type Editor } from 'slate';\n\n/**\n * Images are void elements. They handle the rendering of their children instead of Slate.\n * See the Slate documentation for more information:\n * - https://docs.slatejs.org/api/nodes/element#void-vs-not-void\n * - https://docs.slatejs.org/api/nodes/element#rendering-void-elements\n */\nconst withImages = (editor: Editor) => {\n const { isVoid } = editor;\n\n editor.isVoid = (element) => {\n return element.type === 'image' ? true : isVoid(element);\n };\n\n return editor;\n};\n\nexport { withImages };\n"],"names":["withImages","editor","isVoid","element","type"],"mappings":"AAEA;;;;;IAMA,MAAMA,aAAa,CAACC,MAAAA,GAAAA;IAClB,MAAM,EAAEC,MAAM,EAAE,GAAGD,MAAAA;IAEnBA,MAAOC,CAAAA,MAAM,GAAG,CAACC,OAAAA,GAAAA;AACf,QAAA,OAAOA,OAAQC,CAAAA,IAAI,KAAK,OAAA,GAAU,OAAOF,MAAOC,CAAAA,OAAAA,CAAAA;AAClD,KAAA;IAEA,OAAOF,MAAAA;AACT;;;;"}

View File

@@ -0,0 +1,75 @@
'use strict';
var slate = require('slate');
var links = require('../utils/links.js');
const withLinks = (editor)=>{
const { isInline, apply, insertText, insertData } = editor;
// Links are inline elements, so we need to override the isInline method for slate
editor.isInline = (element)=>{
return element.type === 'link' ? true : isInline(element);
};
// We keep a track of the last inserted link path
// So we can show the popover on the link component if that link is the last one inserted
editor.lastInsertedLinkPath = null;
// We intercept the apply method, so everytime we insert a new link, we save its path
editor.apply = (operation)=>{
if (operation.type === 'insert_node') {
if (!slate.Editor.isEditor(operation.node) && operation.node.type === 'link' && editor.shouldSaveLinkPath) {
editor.lastInsertedLinkPath = operation.path;
}
} else if (operation.type === 'move_node') {
// We need to update the last inserted link path when link is moved
// If link is the first word in the paragraph we dont need to update the path
if (slate.Path.hasPrevious(operation.path) && editor.lastInsertedLinkPath && editor.shouldSaveLinkPath) {
editor.lastInsertedLinkPath = slate.Path.transform(editor.lastInsertedLinkPath, operation);
}
}
apply(operation);
};
editor.insertText = (text)=>{
// When selection is at the end of a link and user types a space, we want to break the link
if (editor.selection && slate.Range.isCollapsed(editor.selection) && text === ' ') {
const linksInSelection = Array.from(slate.Editor.nodes(editor, {
at: editor.selection,
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'link'
}));
const selectionIsInLink = editor.selection && linksInSelection.length > 0;
const selectionIsAtEndOfLink = selectionIsInLink && slate.Point.equals(editor.selection.anchor, slate.Editor.end(editor, linksInSelection[0][1]));
if (selectionIsAtEndOfLink) {
slate.Transforms.insertNodes(editor, {
text: ' ',
type: 'text'
}, {
at: slate.Path.next(linksInSelection[0][1]),
select: true
});
return;
}
}
insertText(text);
};
// Add data as a clickable link if its a valid URL
editor.insertData = (data)=>{
const pastedText = data.getData('text/plain');
if (pastedText) {
try {
// eslint-disable-next-line no-new
new URL(pastedText);
// Do not show link popup on copy-paste a link, so do not save its path
editor.shouldSaveLinkPath = false;
links.insertLink(editor, {
url: pastedText
});
return;
} catch (error) {
// continue normal data insertion
}
}
insertData(data);
};
return editor;
};
exports.withLinks = withLinks;
//# sourceMappingURL=withLinks.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,73 @@
import { Editor, Path, Range, Point, Transforms } from 'slate';
import { insertLink } from '../utils/links.mjs';
const withLinks = (editor)=>{
const { isInline, apply, insertText, insertData } = editor;
// Links are inline elements, so we need to override the isInline method for slate
editor.isInline = (element)=>{
return element.type === 'link' ? true : isInline(element);
};
// We keep a track of the last inserted link path
// So we can show the popover on the link component if that link is the last one inserted
editor.lastInsertedLinkPath = null;
// We intercept the apply method, so everytime we insert a new link, we save its path
editor.apply = (operation)=>{
if (operation.type === 'insert_node') {
if (!Editor.isEditor(operation.node) && operation.node.type === 'link' && editor.shouldSaveLinkPath) {
editor.lastInsertedLinkPath = operation.path;
}
} else if (operation.type === 'move_node') {
// We need to update the last inserted link path when link is moved
// If link is the first word in the paragraph we dont need to update the path
if (Path.hasPrevious(operation.path) && editor.lastInsertedLinkPath && editor.shouldSaveLinkPath) {
editor.lastInsertedLinkPath = Path.transform(editor.lastInsertedLinkPath, operation);
}
}
apply(operation);
};
editor.insertText = (text)=>{
// When selection is at the end of a link and user types a space, we want to break the link
if (editor.selection && Range.isCollapsed(editor.selection) && text === ' ') {
const linksInSelection = Array.from(Editor.nodes(editor, {
at: editor.selection,
match: (node)=>!Editor.isEditor(node) && node.type === 'link'
}));
const selectionIsInLink = editor.selection && linksInSelection.length > 0;
const selectionIsAtEndOfLink = selectionIsInLink && Point.equals(editor.selection.anchor, Editor.end(editor, linksInSelection[0][1]));
if (selectionIsAtEndOfLink) {
Transforms.insertNodes(editor, {
text: ' ',
type: 'text'
}, {
at: Path.next(linksInSelection[0][1]),
select: true
});
return;
}
}
insertText(text);
};
// Add data as a clickable link if its a valid URL
editor.insertData = (data)=>{
const pastedText = data.getData('text/plain');
if (pastedText) {
try {
// eslint-disable-next-line no-new
new URL(pastedText);
// Do not show link popup on copy-paste a link, so do not save its path
editor.shouldSaveLinkPath = false;
insertLink(editor, {
url: pastedText
});
return;
} catch (error) {
// continue normal data insertion
}
}
insertData(data);
};
return editor;
};
export { withLinks };
//# sourceMappingURL=withLinks.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,32 @@
'use strict';
var slate = require('slate');
const isText = (node)=>{
return slate.Node.isNode(node) && !slate.Editor.isEditor(node) && node.type === 'text';
};
/**
* This plugin is used to normalize the Slate document to match the Strapi schema.
*/ const withStrapiSchema = (editor)=>{
const { normalizeNode } = editor;
/**
* On the strapi schema, we want text nodes to have type: text
* By default, Slate add text nodes without type: text
* So we add this normalization for the cases when Slate add text nodes automatically
*/ editor.normalizeNode = (entry)=>{
const [node, path] = entry;
if (!slate.Element.isElement(node) && !isText(node)) {
slate.Transforms.setNodes(editor, {
type: 'text'
}, {
at: path
});
return;
}
normalizeNode(entry);
};
return editor;
};
exports.withStrapiSchema = withStrapiSchema;
//# sourceMappingURL=withStrapiSchema.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"withStrapiSchema.js","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/plugins/withStrapiSchema.ts"],"sourcesContent":["import { type Text, Node, Editor, Element, Transforms } from 'slate';\n\nconst isText = (node: unknown): node is Text => {\n return Node.isNode(node) && !Editor.isEditor(node) && node.type === 'text';\n};\n\n/**\n * This plugin is used to normalize the Slate document to match the Strapi schema.\n */\nconst withStrapiSchema = (editor: Editor) => {\n const { normalizeNode } = editor;\n\n /**\n * On the strapi schema, we want text nodes to have type: text\n * By default, Slate add text nodes without type: text\n * So we add this normalization for the cases when Slate add text nodes automatically\n */\n editor.normalizeNode = (entry) => {\n const [node, path] = entry;\n\n if (!Element.isElement(node) && !isText(node)) {\n Transforms.setNodes(editor, { type: 'text' }, { at: path });\n\n return;\n }\n\n normalizeNode(entry);\n };\n\n return editor;\n};\n\nexport { withStrapiSchema };\n"],"names":["isText","node","Node","isNode","Editor","isEditor","type","withStrapiSchema","editor","normalizeNode","entry","path","Element","isElement","Transforms","setNodes","at"],"mappings":";;;;AAEA,MAAMA,SAAS,CAACC,IAAAA,GAAAA;IACd,OAAOC,UAAAA,CAAKC,MAAM,CAACF,IAAS,CAAA,IAAA,CAACG,YAAOC,CAAAA,QAAQ,CAACJ,IAAAA,CAAAA,IAASA,IAAKK,CAAAA,IAAI,KAAK,MAAA;AACtE,CAAA;AAEA;;IAGA,MAAMC,mBAAmB,CAACC,MAAAA,GAAAA;IACxB,MAAM,EAAEC,aAAa,EAAE,GAAGD,MAAAA;AAE1B;;;;MAKAA,MAAAA,CAAOC,aAAa,GAAG,CAACC,KAAAA,GAAAA;QACtB,MAAM,CAACT,IAAMU,EAAAA,IAAAA,CAAK,GAAGD,KAAAA;AAErB,QAAA,IAAI,CAACE,aAAQC,CAAAA,SAAS,CAACZ,IAAS,CAAA,IAAA,CAACD,OAAOC,IAAO,CAAA,EAAA;YAC7Ca,gBAAWC,CAAAA,QAAQ,CAACP,MAAQ,EAAA;gBAAEF,IAAM,EAAA;aAAU,EAAA;gBAAEU,EAAIL,EAAAA;AAAK,aAAA,CAAA;AAEzD,YAAA;AACF;QAEAF,aAAcC,CAAAA,KAAAA,CAAAA;AAChB,KAAA;IAEA,OAAOF,MAAAA;AACT;;;;"}

View File

@@ -0,0 +1,30 @@
import { Element, Transforms, Node, Editor } from 'slate';
const isText = (node)=>{
return Node.isNode(node) && !Editor.isEditor(node) && node.type === 'text';
};
/**
* This plugin is used to normalize the Slate document to match the Strapi schema.
*/ const withStrapiSchema = (editor)=>{
const { normalizeNode } = editor;
/**
* On the strapi schema, we want text nodes to have type: text
* By default, Slate add text nodes without type: text
* So we add this normalization for the cases when Slate add text nodes automatically
*/ editor.normalizeNode = (entry)=>{
const [node, path] = entry;
if (!Element.isElement(node) && !isText(node)) {
Transforms.setNodes(editor, {
type: 'text'
}, {
at: path
});
return;
}
normalizeNode(entry);
};
return editor;
};
export { withStrapiSchema };
//# sourceMappingURL=withStrapiSchema.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"withStrapiSchema.mjs","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/plugins/withStrapiSchema.ts"],"sourcesContent":["import { type Text, Node, Editor, Element, Transforms } from 'slate';\n\nconst isText = (node: unknown): node is Text => {\n return Node.isNode(node) && !Editor.isEditor(node) && node.type === 'text';\n};\n\n/**\n * This plugin is used to normalize the Slate document to match the Strapi schema.\n */\nconst withStrapiSchema = (editor: Editor) => {\n const { normalizeNode } = editor;\n\n /**\n * On the strapi schema, we want text nodes to have type: text\n * By default, Slate add text nodes without type: text\n * So we add this normalization for the cases when Slate add text nodes automatically\n */\n editor.normalizeNode = (entry) => {\n const [node, path] = entry;\n\n if (!Element.isElement(node) && !isText(node)) {\n Transforms.setNodes(editor, { type: 'text' }, { at: path });\n\n return;\n }\n\n normalizeNode(entry);\n };\n\n return editor;\n};\n\nexport { withStrapiSchema };\n"],"names":["isText","node","Node","isNode","Editor","isEditor","type","withStrapiSchema","editor","normalizeNode","entry","path","Element","isElement","Transforms","setNodes","at"],"mappings":";;AAEA,MAAMA,SAAS,CAACC,IAAAA,GAAAA;IACd,OAAOC,IAAAA,CAAKC,MAAM,CAACF,IAAS,CAAA,IAAA,CAACG,MAAOC,CAAAA,QAAQ,CAACJ,IAAAA,CAAAA,IAASA,IAAKK,CAAAA,IAAI,KAAK,MAAA;AACtE,CAAA;AAEA;;IAGA,MAAMC,mBAAmB,CAACC,MAAAA,GAAAA;IACxB,MAAM,EAAEC,aAAa,EAAE,GAAGD,MAAAA;AAE1B;;;;MAKAA,MAAAA,CAAOC,aAAa,GAAG,CAACC,KAAAA,GAAAA;QACtB,MAAM,CAACT,IAAMU,EAAAA,IAAAA,CAAK,GAAGD,KAAAA;AAErB,QAAA,IAAI,CAACE,OAAQC,CAAAA,SAAS,CAACZ,IAAS,CAAA,IAAA,CAACD,OAAOC,IAAO,CAAA,EAAA;YAC7Ca,UAAWC,CAAAA,QAAQ,CAACP,MAAQ,EAAA;gBAAEF,IAAM,EAAA;aAAU,EAAA;gBAAEU,EAAIL,EAAAA;AAAK,aAAA,CAAA;AAEzD,YAAA;AACF;QAEAF,aAAcC,CAAAA,KAAAA,CAAAA;AAChB,KAAA;IAEA,OAAOF,MAAAA;AACT;;;;"}

View File

@@ -0,0 +1,223 @@
'use strict';
const codeLanguages = [
{
value: 'asm',
label: 'Assembly',
decorate: 'asmatmel'
},
{
value: 'bash',
label: 'Bash'
},
{
value: 'c',
label: 'C'
},
{
value: 'clojure',
label: 'Clojure'
},
{
value: 'cobol',
label: 'COBOL'
},
{
value: 'cpp',
label: 'C++'
},
{
value: 'csharp',
label: 'C#'
},
{
value: 'css',
label: 'CSS'
},
{
value: 'dart',
label: 'Dart'
},
{
value: 'dockerfile',
label: 'Dockerfile',
decorate: 'docker'
},
{
value: 'elixir',
label: 'Elixir'
},
{
value: 'erlang',
label: 'Erlang'
},
{
value: 'fortran',
label: 'Fortran'
},
{
value: 'fsharp',
label: 'F#'
},
{
value: 'go',
label: 'Go'
},
{
value: 'graphql',
label: 'GraphQL'
},
{
value: 'groovy',
label: 'Groovy'
},
{
value: 'haskell',
label: 'Haskell'
},
{
value: 'haxe',
label: 'Haxe'
},
{
value: 'html',
label: 'HTML'
},
{
value: 'ini',
label: 'INI'
},
{
value: 'java',
label: 'Java'
},
{
value: 'javascript',
label: 'JavaScript'
},
{
value: 'jsx',
label: 'JavaScript (React)'
},
{
value: 'json',
label: 'JSON'
},
{
value: 'julia',
label: 'Julia'
},
{
value: 'kotlin',
label: 'Kotlin'
},
{
value: 'latex',
label: 'LaTeX'
},
{
value: 'lua',
label: 'Lua'
},
{
value: 'markdown',
label: 'Markdown'
},
{
value: 'matlab',
label: 'MATLAB'
},
{
value: 'makefile',
label: 'Makefile'
},
{
value: 'objectivec',
label: 'Objective-C'
},
{
value: 'perl',
label: 'Perl'
},
{
value: 'php',
label: 'PHP'
},
{
value: 'plaintext',
label: 'Plain text'
},
{
value: 'powershell',
label: 'PowerShell'
},
{
value: 'python',
label: 'Python'
},
{
value: 'r',
label: 'R'
},
{
value: 'ruby',
label: 'Ruby'
},
{
value: 'rust',
label: 'Rust'
},
{
value: 'sas',
label: 'SAS'
},
{
value: 'scala',
label: 'Scala'
},
{
value: 'scheme',
label: 'Scheme'
},
{
value: 'shell',
label: 'Shell'
},
{
value: 'sql',
label: 'SQL'
},
{
value: 'stata',
label: 'Stata'
},
{
value: 'swift',
label: 'Swift'
},
{
value: 'typescript',
label: 'TypeScript',
decorate: 'ts'
},
{
value: 'tsx',
label: 'TypeScript (React)'
},
{
value: 'vbnet',
label: 'VB.NET'
},
{
value: 'xml',
label: 'XML'
},
{
value: 'yaml',
label: 'YAML',
decorate: 'yml'
}
];
exports.codeLanguages = codeLanguages;
//# sourceMappingURL=constants.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,221 @@
const codeLanguages = [
{
value: 'asm',
label: 'Assembly',
decorate: 'asmatmel'
},
{
value: 'bash',
label: 'Bash'
},
{
value: 'c',
label: 'C'
},
{
value: 'clojure',
label: 'Clojure'
},
{
value: 'cobol',
label: 'COBOL'
},
{
value: 'cpp',
label: 'C++'
},
{
value: 'csharp',
label: 'C#'
},
{
value: 'css',
label: 'CSS'
},
{
value: 'dart',
label: 'Dart'
},
{
value: 'dockerfile',
label: 'Dockerfile',
decorate: 'docker'
},
{
value: 'elixir',
label: 'Elixir'
},
{
value: 'erlang',
label: 'Erlang'
},
{
value: 'fortran',
label: 'Fortran'
},
{
value: 'fsharp',
label: 'F#'
},
{
value: 'go',
label: 'Go'
},
{
value: 'graphql',
label: 'GraphQL'
},
{
value: 'groovy',
label: 'Groovy'
},
{
value: 'haskell',
label: 'Haskell'
},
{
value: 'haxe',
label: 'Haxe'
},
{
value: 'html',
label: 'HTML'
},
{
value: 'ini',
label: 'INI'
},
{
value: 'java',
label: 'Java'
},
{
value: 'javascript',
label: 'JavaScript'
},
{
value: 'jsx',
label: 'JavaScript (React)'
},
{
value: 'json',
label: 'JSON'
},
{
value: 'julia',
label: 'Julia'
},
{
value: 'kotlin',
label: 'Kotlin'
},
{
value: 'latex',
label: 'LaTeX'
},
{
value: 'lua',
label: 'Lua'
},
{
value: 'markdown',
label: 'Markdown'
},
{
value: 'matlab',
label: 'MATLAB'
},
{
value: 'makefile',
label: 'Makefile'
},
{
value: 'objectivec',
label: 'Objective-C'
},
{
value: 'perl',
label: 'Perl'
},
{
value: 'php',
label: 'PHP'
},
{
value: 'plaintext',
label: 'Plain text'
},
{
value: 'powershell',
label: 'PowerShell'
},
{
value: 'python',
label: 'Python'
},
{
value: 'r',
label: 'R'
},
{
value: 'ruby',
label: 'Ruby'
},
{
value: 'rust',
label: 'Rust'
},
{
value: 'sas',
label: 'SAS'
},
{
value: 'scala',
label: 'Scala'
},
{
value: 'scheme',
label: 'Scheme'
},
{
value: 'shell',
label: 'Shell'
},
{
value: 'sql',
label: 'SQL'
},
{
value: 'stata',
label: 'Stata'
},
{
value: 'swift',
label: 'Swift'
},
{
value: 'typescript',
label: 'TypeScript',
decorate: 'ts'
},
{
value: 'tsx',
label: 'TypeScript (React)'
},
{
value: 'vbnet',
label: 'VB.NET'
},
{
value: 'xml',
label: 'XML'
},
{
value: 'yaml',
label: 'YAML',
decorate: 'yml'
}
];
export { codeLanguages };
//# sourceMappingURL=constants.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,48 @@
'use strict';
var slate = require('slate');
/**
* Extracts some logic that is common to most blocks' handleConvert functions.
* @returns The path of the converted block
*/ const baseHandleConvert = (editor, attributesToSet)=>{
// If there is no selection, convert last inserted node
const [_, lastNodePath] = slate.Editor.last(editor, []);
// If the selection is inside a list, split the list so that the modified block is outside of it
slate.Transforms.unwrapNodes(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'list',
split: true,
at: editor.selection ?? lastNodePath
});
// Make sure we get a block node, not an inline node
const [, updatedLastNodePath] = slate.Editor.last(editor, []);
const entry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type !== 'text' && node.type !== 'link',
at: editor.selection ?? updatedLastNodePath
});
if (!entry || slate.Editor.isEditor(entry[0])) {
return;
}
const [element, elementPath] = entry;
slate.Transforms.setNodes(editor, {
...getAttributesToClear(element),
...attributesToSet
}, {
at: elementPath
});
return elementPath;
};
/**
* Set all attributes except type and children to null so that Slate deletes them
*/ const getAttributesToClear = (element)=>{
const { children: _children, type: _type, ...extra } = element;
const attributesToClear = Object.keys(extra).reduce((currentAttributes, key)=>({
...currentAttributes,
[key]: null
}), {});
return attributesToClear;
};
exports.baseHandleConvert = baseHandleConvert;
exports.getAttributesToClear = getAttributesToClear;
//# sourceMappingURL=conversions.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"conversions.js","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/conversions.ts"],"sourcesContent":["import { type Element, type Path, Editor, Transforms } from 'slate';\n\n/**\n * Extracts some logic that is common to most blocks' handleConvert functions.\n * @returns The path of the converted block\n */\nconst baseHandleConvert = <T extends Element>(\n editor: Editor,\n attributesToSet: Partial<T> & { type: T['type'] }\n): void | Path => {\n // If there is no selection, convert last inserted node\n const [_, lastNodePath] = Editor.last(editor, []);\n\n // If the selection is inside a list, split the list so that the modified block is outside of it\n Transforms.unwrapNodes(editor, {\n match: (node) => !Editor.isEditor(node) && node.type === 'list',\n split: true,\n at: editor.selection ?? lastNodePath,\n });\n\n // Make sure we get a block node, not an inline node\n const [, updatedLastNodePath] = Editor.last(editor, []);\n const entry = Editor.above(editor, {\n match: (node) => !Editor.isEditor(node) && node.type !== 'text' && node.type !== 'link',\n at: editor.selection ?? updatedLastNodePath,\n });\n\n if (!entry || Editor.isEditor(entry[0])) {\n return;\n }\n\n const [element, elementPath] = entry;\n\n Transforms.setNodes(\n editor,\n {\n ...getAttributesToClear(element),\n ...attributesToSet,\n } as Partial<Element>,\n { at: elementPath }\n );\n\n return elementPath;\n};\n\n/**\n * Set all attributes except type and children to null so that Slate deletes them\n */\nconst getAttributesToClear = (element: Element) => {\n const { children: _children, type: _type, ...extra } = element;\n\n const attributesToClear = Object.keys(extra).reduce(\n (currentAttributes, key) => ({ ...currentAttributes, [key]: null }),\n {}\n );\n\n return attributesToClear as Record<string, null>;\n};\n\nexport { baseHandleConvert, getAttributesToClear };\n"],"names":["baseHandleConvert","editor","attributesToSet","_","lastNodePath","Editor","last","Transforms","unwrapNodes","match","node","isEditor","type","split","at","selection","updatedLastNodePath","entry","above","element","elementPath","setNodes","getAttributesToClear","children","_children","_type","extra","attributesToClear","Object","keys","reduce","currentAttributes","key"],"mappings":";;;;AAEA;;;IAIA,MAAMA,iBAAoB,GAAA,CACxBC,MACAC,EAAAA,eAAAA,GAAAA;;IAGA,MAAM,CAACC,GAAGC,YAAa,CAAA,GAAGC,aAAOC,IAAI,CAACL,QAAQ,EAAE,CAAA;;IAGhDM,gBAAWC,CAAAA,WAAW,CAACP,MAAQ,EAAA;QAC7BQ,KAAO,EAAA,CAACC,OAAS,CAACL,YAAAA,CAAOM,QAAQ,CAACD,IAAAA,CAAAA,IAASA,IAAKE,CAAAA,IAAI,KAAK,MAAA;QACzDC,KAAO,EAAA,IAAA;QACPC,EAAIb,EAAAA,MAAAA,CAAOc,SAAS,IAAIX;AAC1B,KAAA,CAAA;;AAGA,IAAA,MAAM,GAAGY,mBAAoB,CAAA,GAAGX,aAAOC,IAAI,CAACL,QAAQ,EAAE,CAAA;AACtD,IAAA,MAAMgB,KAAQZ,GAAAA,YAAAA,CAAOa,KAAK,CAACjB,MAAQ,EAAA;AACjCQ,QAAAA,KAAAA,EAAO,CAACC,IAAAA,GAAS,CAACL,YAAAA,CAAOM,QAAQ,CAACD,IAASA,CAAAA,IAAAA,IAAAA,CAAKE,IAAI,KAAK,MAAUF,IAAAA,IAAAA,CAAKE,IAAI,KAAK,MAAA;QACjFE,EAAIb,EAAAA,MAAAA,CAAOc,SAAS,IAAIC;AAC1B,KAAA,CAAA;IAEA,IAAI,CAACC,SAASZ,YAAOM,CAAAA,QAAQ,CAACM,KAAK,CAAC,EAAE,CAAG,EAAA;AACvC,QAAA;AACF;IAEA,MAAM,CAACE,OAASC,EAAAA,WAAAA,CAAY,GAAGH,KAAAA;IAE/BV,gBAAWc,CAAAA,QAAQ,CACjBpB,MACA,EAAA;AACE,QAAA,GAAGqB,qBAAqBH,OAAQ,CAAA;AAChC,QAAA,GAAGjB;KAEL,EAAA;QAAEY,EAAIM,EAAAA;AAAY,KAAA,CAAA;IAGpB,OAAOA,WAAAA;AACT;AAEA;;IAGA,MAAME,uBAAuB,CAACH,OAAAA,GAAAA;IAC5B,MAAM,EAAEI,UAAUC,SAAS,EAAEZ,MAAMa,KAAK,EAAE,GAAGC,KAAAA,EAAO,GAAGP,OAAAA;IAEvD,MAAMQ,iBAAAA,GAAoBC,MAAOC,CAAAA,IAAI,CAACH,KAAAA,CAAAA,CAAOI,MAAM,CACjD,CAACC,iBAAmBC,EAAAA,GAAAA,IAAS;AAAE,YAAA,GAAGD,iBAAiB;AAAE,YAAA,CAACC,MAAM;AAAK,SAAA,GACjE,EAAC,CAAA;IAGH,OAAOL,iBAAAA;AACT;;;;;"}

View File

@@ -0,0 +1,45 @@
import { Editor, Transforms } from 'slate';
/**
* Extracts some logic that is common to most blocks' handleConvert functions.
* @returns The path of the converted block
*/ const baseHandleConvert = (editor, attributesToSet)=>{
// If there is no selection, convert last inserted node
const [_, lastNodePath] = Editor.last(editor, []);
// If the selection is inside a list, split the list so that the modified block is outside of it
Transforms.unwrapNodes(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'list',
split: true,
at: editor.selection ?? lastNodePath
});
// Make sure we get a block node, not an inline node
const [, updatedLastNodePath] = Editor.last(editor, []);
const entry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && node.type !== 'text' && node.type !== 'link',
at: editor.selection ?? updatedLastNodePath
});
if (!entry || Editor.isEditor(entry[0])) {
return;
}
const [element, elementPath] = entry;
Transforms.setNodes(editor, {
...getAttributesToClear(element),
...attributesToSet
}, {
at: elementPath
});
return elementPath;
};
/**
* Set all attributes except type and children to null so that Slate deletes them
*/ const getAttributesToClear = (element)=>{
const { children: _children, type: _type, ...extra } = element;
const attributesToClear = Object.keys(extra).reduce((currentAttributes, key)=>({
...currentAttributes,
[key]: null
}), {});
return attributesToClear;
};
export { baseHandleConvert, getAttributesToClear };
//# sourceMappingURL=conversions.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"conversions.mjs","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/conversions.ts"],"sourcesContent":["import { type Element, type Path, Editor, Transforms } from 'slate';\n\n/**\n * Extracts some logic that is common to most blocks' handleConvert functions.\n * @returns The path of the converted block\n */\nconst baseHandleConvert = <T extends Element>(\n editor: Editor,\n attributesToSet: Partial<T> & { type: T['type'] }\n): void | Path => {\n // If there is no selection, convert last inserted node\n const [_, lastNodePath] = Editor.last(editor, []);\n\n // If the selection is inside a list, split the list so that the modified block is outside of it\n Transforms.unwrapNodes(editor, {\n match: (node) => !Editor.isEditor(node) && node.type === 'list',\n split: true,\n at: editor.selection ?? lastNodePath,\n });\n\n // Make sure we get a block node, not an inline node\n const [, updatedLastNodePath] = Editor.last(editor, []);\n const entry = Editor.above(editor, {\n match: (node) => !Editor.isEditor(node) && node.type !== 'text' && node.type !== 'link',\n at: editor.selection ?? updatedLastNodePath,\n });\n\n if (!entry || Editor.isEditor(entry[0])) {\n return;\n }\n\n const [element, elementPath] = entry;\n\n Transforms.setNodes(\n editor,\n {\n ...getAttributesToClear(element),\n ...attributesToSet,\n } as Partial<Element>,\n { at: elementPath }\n );\n\n return elementPath;\n};\n\n/**\n * Set all attributes except type and children to null so that Slate deletes them\n */\nconst getAttributesToClear = (element: Element) => {\n const { children: _children, type: _type, ...extra } = element;\n\n const attributesToClear = Object.keys(extra).reduce(\n (currentAttributes, key) => ({ ...currentAttributes, [key]: null }),\n {}\n );\n\n return attributesToClear as Record<string, null>;\n};\n\nexport { baseHandleConvert, getAttributesToClear };\n"],"names":["baseHandleConvert","editor","attributesToSet","_","lastNodePath","Editor","last","Transforms","unwrapNodes","match","node","isEditor","type","split","at","selection","updatedLastNodePath","entry","above","element","elementPath","setNodes","getAttributesToClear","children","_children","_type","extra","attributesToClear","Object","keys","reduce","currentAttributes","key"],"mappings":";;AAEA;;;IAIA,MAAMA,iBAAoB,GAAA,CACxBC,MACAC,EAAAA,eAAAA,GAAAA;;IAGA,MAAM,CAACC,GAAGC,YAAa,CAAA,GAAGC,OAAOC,IAAI,CAACL,QAAQ,EAAE,CAAA;;IAGhDM,UAAWC,CAAAA,WAAW,CAACP,MAAQ,EAAA;QAC7BQ,KAAO,EAAA,CAACC,OAAS,CAACL,MAAAA,CAAOM,QAAQ,CAACD,IAAAA,CAAAA,IAASA,IAAKE,CAAAA,IAAI,KAAK,MAAA;QACzDC,KAAO,EAAA,IAAA;QACPC,EAAIb,EAAAA,MAAAA,CAAOc,SAAS,IAAIX;AAC1B,KAAA,CAAA;;AAGA,IAAA,MAAM,GAAGY,mBAAoB,CAAA,GAAGX,OAAOC,IAAI,CAACL,QAAQ,EAAE,CAAA;AACtD,IAAA,MAAMgB,KAAQZ,GAAAA,MAAAA,CAAOa,KAAK,CAACjB,MAAQ,EAAA;AACjCQ,QAAAA,KAAAA,EAAO,CAACC,IAAAA,GAAS,CAACL,MAAAA,CAAOM,QAAQ,CAACD,IAASA,CAAAA,IAAAA,IAAAA,CAAKE,IAAI,KAAK,MAAUF,IAAAA,IAAAA,CAAKE,IAAI,KAAK,MAAA;QACjFE,EAAIb,EAAAA,MAAAA,CAAOc,SAAS,IAAIC;AAC1B,KAAA,CAAA;IAEA,IAAI,CAACC,SAASZ,MAAOM,CAAAA,QAAQ,CAACM,KAAK,CAAC,EAAE,CAAG,EAAA;AACvC,QAAA;AACF;IAEA,MAAM,CAACE,OAASC,EAAAA,WAAAA,CAAY,GAAGH,KAAAA;IAE/BV,UAAWc,CAAAA,QAAQ,CACjBpB,MACA,EAAA;AACE,QAAA,GAAGqB,qBAAqBH,OAAQ,CAAA;AAChC,QAAA,GAAGjB;KAEL,EAAA;QAAEY,EAAIM,EAAAA;AAAY,KAAA,CAAA;IAGpB,OAAOA,WAAAA;AACT;AAEA;;IAGA,MAAME,uBAAuB,CAACH,OAAAA,GAAAA;IAC5B,MAAM,EAAEI,UAAUC,SAAS,EAAEZ,MAAMa,KAAK,EAAE,GAAGC,KAAAA,EAAO,GAAGP,OAAAA;IAEvD,MAAMQ,iBAAAA,GAAoBC,MAAOC,CAAAA,IAAI,CAACH,KAAAA,CAAAA,CAAOI,MAAM,CACjD,CAACC,iBAAmBC,EAAAA,GAAAA,IAAS;AAAE,YAAA,GAAGD,iBAAiB;AAAE,YAAA,CAACC,MAAM;AAAK,SAAA,GACjE,EAAC,CAAA;IAGH,OAAOL,iBAAAA;AACT;;;;"}

View File

@@ -0,0 +1,64 @@
'use strict';
var slate = require('slate');
const isText = (node)=>{
return slate.Node.isNode(node) && !slate.Editor.isEditor(node) && node.type === 'text';
};
/**
* Inserts a line break the first time the user presses enter, and exits the node the second time.
*/ const pressEnterTwiceToExit = (editor)=>{
/**
* To determine if we should break out of the node, check 2 things:
* 1. If the cursor is at the end of the node
* 2. If the last line of the node is empty
*/ const nodeEntry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && ![
'link',
'text'
].includes(node.type)
});
if (!nodeEntry || !editor.selection) {
return;
}
const [node, nodePath] = nodeEntry;
const isNodeEnd = slate.Editor.isEnd(editor, editor.selection.anchor, nodePath);
const lastTextNode = node.children.at(-1);
const isEmptyLine = isText(lastTextNode) && lastTextNode.text.endsWith('\n');
if (isNodeEnd && isEmptyLine) {
// Remove the last line break
slate.Transforms.delete(editor, {
distance: 1,
unit: 'character',
reverse: true
});
// Break out of the node by creating a new paragraph
slate.Transforms.insertNodes(editor, {
type: 'paragraph',
children: [
{
type: 'text',
text: ''
}
]
});
return;
}
// Otherwise insert a new line within the node
slate.Transforms.insertText(editor, '\n');
// If there's nothing after the cursor, disable modifiers
if (isNodeEnd) {
[
'bold',
'italic',
'underline',
'strikethrough',
'code'
].forEach((modifier)=>{
slate.Editor.removeMark(editor, modifier);
});
}
};
exports.pressEnterTwiceToExit = pressEnterTwiceToExit;
//# sourceMappingURL=enterKey.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"enterKey.js","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/enterKey.ts"],"sourcesContent":["import { type Text, Editor, Node, Transforms } from 'slate';\n\nconst isText = (node: unknown): node is Text => {\n return Node.isNode(node) && !Editor.isEditor(node) && node.type === 'text';\n};\n\n/**\n * Inserts a line break the first time the user presses enter, and exits the node the second time.\n */\nconst pressEnterTwiceToExit = (editor: Editor) => {\n /**\n * To determine if we should break out of the node, check 2 things:\n * 1. If the cursor is at the end of the node\n * 2. If the last line of the node is empty\n */\n const nodeEntry = Editor.above(editor, {\n match: (node) => !Editor.isEditor(node) && !['link', 'text'].includes(node.type),\n });\n if (!nodeEntry || !editor.selection) {\n return;\n }\n const [node, nodePath] = nodeEntry;\n const isNodeEnd = Editor.isEnd(editor, editor.selection.anchor, nodePath);\n const lastTextNode = node.children.at(-1);\n const isEmptyLine = isText(lastTextNode) && lastTextNode.text.endsWith('\\n');\n\n if (isNodeEnd && isEmptyLine) {\n // Remove the last line break\n Transforms.delete(editor, { distance: 1, unit: 'character', reverse: true });\n // Break out of the node by creating a new paragraph\n Transforms.insertNodes(editor, {\n type: 'paragraph',\n children: [{ type: 'text', text: '' }],\n });\n return;\n }\n\n // Otherwise insert a new line within the node\n Transforms.insertText(editor, '\\n');\n\n // If there's nothing after the cursor, disable modifiers\n if (isNodeEnd) {\n ['bold', 'italic', 'underline', 'strikethrough', 'code'].forEach((modifier) => {\n Editor.removeMark(editor, modifier);\n });\n }\n};\n\nexport { pressEnterTwiceToExit };\n"],"names":["isText","node","Node","isNode","Editor","isEditor","type","pressEnterTwiceToExit","editor","nodeEntry","above","match","includes","selection","nodePath","isNodeEnd","isEnd","anchor","lastTextNode","children","at","isEmptyLine","text","endsWith","Transforms","delete","distance","unit","reverse","insertNodes","insertText","forEach","modifier","removeMark"],"mappings":";;;;AAEA,MAAMA,SAAS,CAACC,IAAAA,GAAAA;IACd,OAAOC,UAAAA,CAAKC,MAAM,CAACF,IAAS,CAAA,IAAA,CAACG,YAAOC,CAAAA,QAAQ,CAACJ,IAAAA,CAAAA,IAASA,IAAKK,CAAAA,IAAI,KAAK,MAAA;AACtE,CAAA;AAEA;;IAGA,MAAMC,wBAAwB,CAACC,MAAAA,GAAAA;AAC7B;;;;AAIC,MACD,MAAMC,SAAAA,GAAYL,YAAOM,CAAAA,KAAK,CAACF,MAAQ,EAAA;AACrCG,QAAAA,KAAAA,EAAO,CAACV,IAAS,GAAA,CAACG,aAAOC,QAAQ,CAACJ,SAAS,CAAC;AAAC,gBAAA,MAAA;AAAQ,gBAAA;aAAO,CAACW,QAAQ,CAACX,IAAAA,CAAKK,IAAI;AACjF,KAAA,CAAA;AACA,IAAA,IAAI,CAACG,SAAAA,IAAa,CAACD,MAAAA,CAAOK,SAAS,EAAE;AACnC,QAAA;AACF;IACA,MAAM,CAACZ,IAAMa,EAAAA,QAAAA,CAAS,GAAGL,SAAAA;IACzB,MAAMM,SAAAA,GAAYX,aAAOY,KAAK,CAACR,QAAQA,MAAOK,CAAAA,SAAS,CAACI,MAAM,EAAEH,QAAAA,CAAAA;AAChE,IAAA,MAAMI,eAAejB,IAAKkB,CAAAA,QAAQ,CAACC,EAAE,CAAC,CAAC,CAAA,CAAA;AACvC,IAAA,MAAMC,cAAcrB,MAAOkB,CAAAA,YAAAA,CAAAA,IAAiBA,aAAaI,IAAI,CAACC,QAAQ,CAAC,IAAA,CAAA;AAEvE,IAAA,IAAIR,aAAaM,WAAa,EAAA;;QAE5BG,gBAAWC,CAAAA,MAAM,CAACjB,MAAQ,EAAA;YAAEkB,QAAU,EAAA,CAAA;YAAGC,IAAM,EAAA,WAAA;YAAaC,OAAS,EAAA;AAAK,SAAA,CAAA;;QAE1EJ,gBAAWK,CAAAA,WAAW,CAACrB,MAAQ,EAAA;YAC7BF,IAAM,EAAA,WAAA;YACNa,QAAU,EAAA;AAAC,gBAAA;oBAAEb,IAAM,EAAA,MAAA;oBAAQgB,IAAM,EAAA;AAAG;AAAE;AACxC,SAAA,CAAA;AACA,QAAA;AACF;;IAGAE,gBAAWM,CAAAA,UAAU,CAACtB,MAAQ,EAAA,IAAA,CAAA;;AAG9B,IAAA,IAAIO,SAAW,EAAA;AACb,QAAA;AAAC,YAAA,MAAA;AAAQ,YAAA,QAAA;AAAU,YAAA,WAAA;AAAa,YAAA,eAAA;AAAiB,YAAA;SAAO,CAACgB,OAAO,CAAC,CAACC,QAAAA,GAAAA;YAChE5B,YAAO6B,CAAAA,UAAU,CAACzB,MAAQwB,EAAAA,QAAAA,CAAAA;AAC5B,SAAA,CAAA;AACF;AACF;;;;"}

View File

@@ -0,0 +1,62 @@
import { Editor, Transforms, Node } from 'slate';
const isText = (node)=>{
return Node.isNode(node) && !Editor.isEditor(node) && node.type === 'text';
};
/**
* Inserts a line break the first time the user presses enter, and exits the node the second time.
*/ const pressEnterTwiceToExit = (editor)=>{
/**
* To determine if we should break out of the node, check 2 things:
* 1. If the cursor is at the end of the node
* 2. If the last line of the node is empty
*/ const nodeEntry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && ![
'link',
'text'
].includes(node.type)
});
if (!nodeEntry || !editor.selection) {
return;
}
const [node, nodePath] = nodeEntry;
const isNodeEnd = Editor.isEnd(editor, editor.selection.anchor, nodePath);
const lastTextNode = node.children.at(-1);
const isEmptyLine = isText(lastTextNode) && lastTextNode.text.endsWith('\n');
if (isNodeEnd && isEmptyLine) {
// Remove the last line break
Transforms.delete(editor, {
distance: 1,
unit: 'character',
reverse: true
});
// Break out of the node by creating a new paragraph
Transforms.insertNodes(editor, {
type: 'paragraph',
children: [
{
type: 'text',
text: ''
}
]
});
return;
}
// Otherwise insert a new line within the node
Transforms.insertText(editor, '\n');
// If there's nothing after the cursor, disable modifiers
if (isNodeEnd) {
[
'bold',
'italic',
'underline',
'strikethrough',
'code'
].forEach((modifier)=>{
Editor.removeMark(editor, modifier);
});
}
};
export { pressEnterTwiceToExit };
//# sourceMappingURL=enterKey.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"enterKey.mjs","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/enterKey.ts"],"sourcesContent":["import { type Text, Editor, Node, Transforms } from 'slate';\n\nconst isText = (node: unknown): node is Text => {\n return Node.isNode(node) && !Editor.isEditor(node) && node.type === 'text';\n};\n\n/**\n * Inserts a line break the first time the user presses enter, and exits the node the second time.\n */\nconst pressEnterTwiceToExit = (editor: Editor) => {\n /**\n * To determine if we should break out of the node, check 2 things:\n * 1. If the cursor is at the end of the node\n * 2. If the last line of the node is empty\n */\n const nodeEntry = Editor.above(editor, {\n match: (node) => !Editor.isEditor(node) && !['link', 'text'].includes(node.type),\n });\n if (!nodeEntry || !editor.selection) {\n return;\n }\n const [node, nodePath] = nodeEntry;\n const isNodeEnd = Editor.isEnd(editor, editor.selection.anchor, nodePath);\n const lastTextNode = node.children.at(-1);\n const isEmptyLine = isText(lastTextNode) && lastTextNode.text.endsWith('\\n');\n\n if (isNodeEnd && isEmptyLine) {\n // Remove the last line break\n Transforms.delete(editor, { distance: 1, unit: 'character', reverse: true });\n // Break out of the node by creating a new paragraph\n Transforms.insertNodes(editor, {\n type: 'paragraph',\n children: [{ type: 'text', text: '' }],\n });\n return;\n }\n\n // Otherwise insert a new line within the node\n Transforms.insertText(editor, '\\n');\n\n // If there's nothing after the cursor, disable modifiers\n if (isNodeEnd) {\n ['bold', 'italic', 'underline', 'strikethrough', 'code'].forEach((modifier) => {\n Editor.removeMark(editor, modifier);\n });\n }\n};\n\nexport { pressEnterTwiceToExit };\n"],"names":["isText","node","Node","isNode","Editor","isEditor","type","pressEnterTwiceToExit","editor","nodeEntry","above","match","includes","selection","nodePath","isNodeEnd","isEnd","anchor","lastTextNode","children","at","isEmptyLine","text","endsWith","Transforms","delete","distance","unit","reverse","insertNodes","insertText","forEach","modifier","removeMark"],"mappings":";;AAEA,MAAMA,SAAS,CAACC,IAAAA,GAAAA;IACd,OAAOC,IAAAA,CAAKC,MAAM,CAACF,IAAS,CAAA,IAAA,CAACG,MAAOC,CAAAA,QAAQ,CAACJ,IAAAA,CAAAA,IAASA,IAAKK,CAAAA,IAAI,KAAK,MAAA;AACtE,CAAA;AAEA;;IAGA,MAAMC,wBAAwB,CAACC,MAAAA,GAAAA;AAC7B;;;;AAIC,MACD,MAAMC,SAAAA,GAAYL,MAAOM,CAAAA,KAAK,CAACF,MAAQ,EAAA;AACrCG,QAAAA,KAAAA,EAAO,CAACV,IAAS,GAAA,CAACG,OAAOC,QAAQ,CAACJ,SAAS,CAAC;AAAC,gBAAA,MAAA;AAAQ,gBAAA;aAAO,CAACW,QAAQ,CAACX,IAAAA,CAAKK,IAAI;AACjF,KAAA,CAAA;AACA,IAAA,IAAI,CAACG,SAAAA,IAAa,CAACD,MAAAA,CAAOK,SAAS,EAAE;AACnC,QAAA;AACF;IACA,MAAM,CAACZ,IAAMa,EAAAA,QAAAA,CAAS,GAAGL,SAAAA;IACzB,MAAMM,SAAAA,GAAYX,OAAOY,KAAK,CAACR,QAAQA,MAAOK,CAAAA,SAAS,CAACI,MAAM,EAAEH,QAAAA,CAAAA;AAChE,IAAA,MAAMI,eAAejB,IAAKkB,CAAAA,QAAQ,CAACC,EAAE,CAAC,CAAC,CAAA,CAAA;AACvC,IAAA,MAAMC,cAAcrB,MAAOkB,CAAAA,YAAAA,CAAAA,IAAiBA,aAAaI,IAAI,CAACC,QAAQ,CAAC,IAAA,CAAA;AAEvE,IAAA,IAAIR,aAAaM,WAAa,EAAA;;QAE5BG,UAAWC,CAAAA,MAAM,CAACjB,MAAQ,EAAA;YAAEkB,QAAU,EAAA,CAAA;YAAGC,IAAM,EAAA,WAAA;YAAaC,OAAS,EAAA;AAAK,SAAA,CAAA;;QAE1EJ,UAAWK,CAAAA,WAAW,CAACrB,MAAQ,EAAA;YAC7BF,IAAM,EAAA,WAAA;YACNa,QAAU,EAAA;AAAC,gBAAA;oBAAEb,IAAM,EAAA,MAAA;oBAAQgB,IAAM,EAAA;AAAG;AAAE;AACxC,SAAA,CAAA;AACA,QAAA;AACF;;IAGAE,UAAWM,CAAAA,UAAU,CAACtB,MAAQ,EAAA,IAAA,CAAA;;AAG9B,IAAA,IAAIO,SAAW,EAAA;AACb,QAAA;AAAC,YAAA,MAAA;AAAQ,YAAA,QAAA;AAAU,YAAA,WAAA;AAAa,YAAA,eAAA;AAAiB,YAAA;SAAO,CAACgB,OAAO,CAAC,CAACC,QAAAA,GAAAA;YAChE5B,MAAO6B,CAAAA,UAAU,CAACzB,MAAQwB,EAAAA,QAAAA,CAAAA;AAC5B,SAAA,CAAA;AACF;AACF;;;;"}

View File

@@ -0,0 +1,84 @@
'use strict';
var slate = require('slate');
const removeLink = (editor)=>{
slate.Transforms.unwrapNodes(editor, {
match: (node)=>!slate.Editor.isEditor(node) && slate.Element.isElement(node) && node.type === 'link'
});
};
const insertLink = (editor, { url })=>{
if (editor.selection) {
// We want to remove all link on the selection
const linkNodes = Array.from(slate.Editor.nodes(editor, {
at: editor.selection,
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'link'
}));
linkNodes.forEach(([, path])=>{
slate.Transforms.unwrapNodes(editor, {
at: path
});
});
if (slate.Range.isCollapsed(editor.selection)) {
const link = {
type: 'link',
url: url ?? '',
children: [
{
type: 'text',
text: url
}
]
};
slate.Transforms.insertNodes(editor, link);
} else {
slate.Transforms.wrapNodes(editor, {
type: 'link',
url: url ?? ''
}, {
split: true
});
}
}
};
const editLink = (editor, link)=>{
const { url, text } = link;
if (!editor.selection) {
return;
}
const linkEntry = slate.Editor.above(editor, {
match: (node)=>!slate.Editor.isEditor(node) && node.type === 'link'
});
if (linkEntry) {
const [, linkPath] = linkEntry;
slate.Transforms.setNodes(editor, {
url
}, {
at: linkPath
});
// If link text is different, we remove the old text and insert the new one
if (text !== '' && text !== slate.Editor.string(editor, linkPath)) {
const linkNodeChildrens = Array.from(slate.Node.children(editor, linkPath, {
reverse: true
}));
linkNodeChildrens.forEach(([, childPath])=>{
slate.Transforms.removeNodes(editor, {
at: childPath
});
});
slate.Transforms.insertNodes(editor, [
{
type: 'text',
text
}
], {
at: linkPath.concat(0)
});
}
}
};
exports.editLink = editLink;
exports.insertLink = insertLink;
exports.removeLink = removeLink;
//# sourceMappingURL=links.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"links.js","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/links.ts"],"sourcesContent":["import { Transforms, Editor, Element as SlateElement, Node, Range } from 'slate';\n\nimport { type Block } from './types';\n\nconst removeLink = (editor: Editor) => {\n Transforms.unwrapNodes(editor, {\n match: (node) => !Editor.isEditor(node) && SlateElement.isElement(node) && node.type === 'link',\n });\n};\n\nconst insertLink = (editor: Editor, { url }: { url: string }) => {\n if (editor.selection) {\n // We want to remove all link on the selection\n const linkNodes = Array.from(\n Editor.nodes(editor, {\n at: editor.selection,\n match: (node) => !Editor.isEditor(node) && node.type === 'link',\n })\n );\n\n linkNodes.forEach(([, path]) => {\n Transforms.unwrapNodes(editor, { at: path });\n });\n\n if (Range.isCollapsed(editor.selection)) {\n const link: Block<'link'> = {\n type: 'link',\n url: url ?? '',\n children: [{ type: 'text', text: url }],\n };\n\n Transforms.insertNodes(editor, link);\n } else {\n Transforms.wrapNodes(editor, { type: 'link', url: url ?? '' } as Block<'link'>, {\n split: true,\n });\n }\n }\n};\n\nconst editLink = (editor: Editor, link: { url: string; text: string }) => {\n const { url, text } = link;\n\n if (!editor.selection) {\n return;\n }\n\n const linkEntry = Editor.above(editor, {\n match: (node) => !Editor.isEditor(node) && node.type === 'link',\n });\n\n if (linkEntry) {\n const [, linkPath] = linkEntry;\n Transforms.setNodes(editor, { url }, { at: linkPath });\n\n // If link text is different, we remove the old text and insert the new one\n if (text !== '' && text !== Editor.string(editor, linkPath)) {\n const linkNodeChildrens = Array.from(Node.children(editor, linkPath, { reverse: true }));\n\n linkNodeChildrens.forEach(([, childPath]) => {\n Transforms.removeNodes(editor, { at: childPath });\n });\n\n Transforms.insertNodes(editor, [{ type: 'text', text }], { at: linkPath.concat(0) });\n }\n }\n};\n\nexport { insertLink, editLink, removeLink };\n"],"names":["removeLink","editor","Transforms","unwrapNodes","match","node","Editor","isEditor","SlateElement","isElement","type","insertLink","url","selection","linkNodes","Array","from","nodes","at","forEach","path","Range","isCollapsed","link","children","text","insertNodes","wrapNodes","split","editLink","linkEntry","above","linkPath","setNodes","string","linkNodeChildrens","Node","reverse","childPath","removeNodes","concat"],"mappings":";;;;AAIA,MAAMA,aAAa,CAACC,MAAAA,GAAAA;IAClBC,gBAAWC,CAAAA,WAAW,CAACF,MAAQ,EAAA;AAC7BG,QAAAA,KAAAA,EAAO,CAACC,IAAAA,GAAS,CAACC,YAAAA,CAAOC,QAAQ,CAACF,IAASG,CAAAA,IAAAA,aAAAA,CAAaC,SAAS,CAACJ,IAASA,CAAAA,IAAAA,IAAAA,CAAKK,IAAI,KAAK;AAC3F,KAAA,CAAA;AACF;AAEA,MAAMC,UAAa,GAAA,CAACV,MAAgB,EAAA,EAAEW,GAAG,EAAmB,GAAA;IAC1D,IAAIX,MAAAA,CAAOY,SAAS,EAAE;;AAEpB,QAAA,MAAMC,YAAYC,KAAMC,CAAAA,IAAI,CAC1BV,YAAOW,CAAAA,KAAK,CAAChB,MAAQ,EAAA;AACnBiB,YAAAA,EAAAA,EAAIjB,OAAOY,SAAS;YACpBT,KAAO,EAAA,CAACC,OAAS,CAACC,YAAAA,CAAOC,QAAQ,CAACF,IAAAA,CAAAA,IAASA,IAAKK,CAAAA,IAAI,KAAK;AAC3D,SAAA,CAAA,CAAA;AAGFI,QAAAA,SAAAA,CAAUK,OAAO,CAAC,CAAC,GAAGC,IAAK,CAAA,GAAA;YACzBlB,gBAAWC,CAAAA,WAAW,CAACF,MAAQ,EAAA;gBAAEiB,EAAIE,EAAAA;AAAK,aAAA,CAAA;AAC5C,SAAA,CAAA;AAEA,QAAA,IAAIC,WAAMC,CAAAA,WAAW,CAACrB,MAAAA,CAAOY,SAAS,CAAG,EAAA;AACvC,YAAA,MAAMU,IAAsB,GAAA;gBAC1Bb,IAAM,EAAA,MAAA;AACNE,gBAAAA,GAAAA,EAAKA,GAAO,IAAA,EAAA;gBACZY,QAAU,EAAA;AAAC,oBAAA;wBAAEd,IAAM,EAAA,MAAA;wBAAQe,IAAMb,EAAAA;AAAI;AAAE;AACzC,aAAA;YAEAV,gBAAWwB,CAAAA,WAAW,CAACzB,MAAQsB,EAAAA,IAAAA,CAAAA;SAC1B,MAAA;YACLrB,gBAAWyB,CAAAA,SAAS,CAAC1B,MAAQ,EAAA;gBAAES,IAAM,EAAA,MAAA;AAAQE,gBAAAA,GAAAA,EAAKA,GAAO,IAAA;aAAuB,EAAA;gBAC9EgB,KAAO,EAAA;AACT,aAAA,CAAA;AACF;AACF;AACF;AAEMC,MAAAA,QAAAA,GAAW,CAAC5B,MAAgBsB,EAAAA,IAAAA,GAAAA;AAChC,IAAA,MAAM,EAAEX,GAAG,EAAEa,IAAI,EAAE,GAAGF,IAAAA;IAEtB,IAAI,CAACtB,MAAOY,CAAAA,SAAS,EAAE;AACrB,QAAA;AACF;AAEA,IAAA,MAAMiB,SAAYxB,GAAAA,YAAAA,CAAOyB,KAAK,CAAC9B,MAAQ,EAAA;QACrCG,KAAO,EAAA,CAACC,OAAS,CAACC,YAAAA,CAAOC,QAAQ,CAACF,IAAAA,CAAAA,IAASA,IAAKK,CAAAA,IAAI,KAAK;AAC3D,KAAA,CAAA;AAEA,IAAA,IAAIoB,SAAW,EAAA;QACb,MAAM,GAAGE,SAAS,GAAGF,SAAAA;QACrB5B,gBAAW+B,CAAAA,QAAQ,CAAChC,MAAQ,EAAA;AAAEW,YAAAA;SAAO,EAAA;YAAEM,EAAIc,EAAAA;AAAS,SAAA,CAAA;;AAGpD,QAAA,IAAIP,SAAS,EAAMA,IAAAA,IAAAA,KAASnB,aAAO4B,MAAM,CAACjC,QAAQ+B,QAAW,CAAA,EAAA;YAC3D,MAAMG,iBAAAA,GAAoBpB,MAAMC,IAAI,CAACoB,WAAKZ,QAAQ,CAACvB,QAAQ+B,QAAU,EAAA;gBAAEK,OAAS,EAAA;AAAK,aAAA,CAAA,CAAA;AAErFF,YAAAA,iBAAAA,CAAkBhB,OAAO,CAAC,CAAC,GAAGmB,SAAU,CAAA,GAAA;gBACtCpC,gBAAWqC,CAAAA,WAAW,CAACtC,MAAQ,EAAA;oBAAEiB,EAAIoB,EAAAA;AAAU,iBAAA,CAAA;AACjD,aAAA,CAAA;YAEApC,gBAAWwB,CAAAA,WAAW,CAACzB,MAAQ,EAAA;AAAC,gBAAA;oBAAES,IAAM,EAAA,MAAA;AAAQe,oBAAAA;AAAK;aAAE,EAAE;gBAAEP,EAAIc,EAAAA,QAAAA,CAASQ,MAAM,CAAC,CAAA;AAAG,aAAA,CAAA;AACpF;AACF;AACF;;;;;;"}

View File

@@ -0,0 +1,80 @@
import { Editor, Transforms, Range, Element, Node } from 'slate';
const removeLink = (editor)=>{
Transforms.unwrapNodes(editor, {
match: (node)=>!Editor.isEditor(node) && Element.isElement(node) && node.type === 'link'
});
};
const insertLink = (editor, { url })=>{
if (editor.selection) {
// We want to remove all link on the selection
const linkNodes = Array.from(Editor.nodes(editor, {
at: editor.selection,
match: (node)=>!Editor.isEditor(node) && node.type === 'link'
}));
linkNodes.forEach(([, path])=>{
Transforms.unwrapNodes(editor, {
at: path
});
});
if (Range.isCollapsed(editor.selection)) {
const link = {
type: 'link',
url: url ?? '',
children: [
{
type: 'text',
text: url
}
]
};
Transforms.insertNodes(editor, link);
} else {
Transforms.wrapNodes(editor, {
type: 'link',
url: url ?? ''
}, {
split: true
});
}
}
};
const editLink = (editor, link)=>{
const { url, text } = link;
if (!editor.selection) {
return;
}
const linkEntry = Editor.above(editor, {
match: (node)=>!Editor.isEditor(node) && node.type === 'link'
});
if (linkEntry) {
const [, linkPath] = linkEntry;
Transforms.setNodes(editor, {
url
}, {
at: linkPath
});
// If link text is different, we remove the old text and insert the new one
if (text !== '' && text !== Editor.string(editor, linkPath)) {
const linkNodeChildrens = Array.from(Node.children(editor, linkPath, {
reverse: true
}));
linkNodeChildrens.forEach(([, childPath])=>{
Transforms.removeNodes(editor, {
at: childPath
});
});
Transforms.insertNodes(editor, [
{
type: 'text',
text
}
], {
at: linkPath.concat(0)
});
}
}
};
export { editLink, insertLink, removeLink };
//# sourceMappingURL=links.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"links.mjs","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/links.ts"],"sourcesContent":["import { Transforms, Editor, Element as SlateElement, Node, Range } from 'slate';\n\nimport { type Block } from './types';\n\nconst removeLink = (editor: Editor) => {\n Transforms.unwrapNodes(editor, {\n match: (node) => !Editor.isEditor(node) && SlateElement.isElement(node) && node.type === 'link',\n });\n};\n\nconst insertLink = (editor: Editor, { url }: { url: string }) => {\n if (editor.selection) {\n // We want to remove all link on the selection\n const linkNodes = Array.from(\n Editor.nodes(editor, {\n at: editor.selection,\n match: (node) => !Editor.isEditor(node) && node.type === 'link',\n })\n );\n\n linkNodes.forEach(([, path]) => {\n Transforms.unwrapNodes(editor, { at: path });\n });\n\n if (Range.isCollapsed(editor.selection)) {\n const link: Block<'link'> = {\n type: 'link',\n url: url ?? '',\n children: [{ type: 'text', text: url }],\n };\n\n Transforms.insertNodes(editor, link);\n } else {\n Transforms.wrapNodes(editor, { type: 'link', url: url ?? '' } as Block<'link'>, {\n split: true,\n });\n }\n }\n};\n\nconst editLink = (editor: Editor, link: { url: string; text: string }) => {\n const { url, text } = link;\n\n if (!editor.selection) {\n return;\n }\n\n const linkEntry = Editor.above(editor, {\n match: (node) => !Editor.isEditor(node) && node.type === 'link',\n });\n\n if (linkEntry) {\n const [, linkPath] = linkEntry;\n Transforms.setNodes(editor, { url }, { at: linkPath });\n\n // If link text is different, we remove the old text and insert the new one\n if (text !== '' && text !== Editor.string(editor, linkPath)) {\n const linkNodeChildrens = Array.from(Node.children(editor, linkPath, { reverse: true }));\n\n linkNodeChildrens.forEach(([, childPath]) => {\n Transforms.removeNodes(editor, { at: childPath });\n });\n\n Transforms.insertNodes(editor, [{ type: 'text', text }], { at: linkPath.concat(0) });\n }\n }\n};\n\nexport { insertLink, editLink, removeLink };\n"],"names":["removeLink","editor","Transforms","unwrapNodes","match","node","Editor","isEditor","SlateElement","isElement","type","insertLink","url","selection","linkNodes","Array","from","nodes","at","forEach","path","Range","isCollapsed","link","children","text","insertNodes","wrapNodes","split","editLink","linkEntry","above","linkPath","setNodes","string","linkNodeChildrens","Node","reverse","childPath","removeNodes","concat"],"mappings":";;AAIA,MAAMA,aAAa,CAACC,MAAAA,GAAAA;IAClBC,UAAWC,CAAAA,WAAW,CAACF,MAAQ,EAAA;AAC7BG,QAAAA,KAAAA,EAAO,CAACC,IAAAA,GAAS,CAACC,MAAAA,CAAOC,QAAQ,CAACF,IAASG,CAAAA,IAAAA,OAAAA,CAAaC,SAAS,CAACJ,IAASA,CAAAA,IAAAA,IAAAA,CAAKK,IAAI,KAAK;AAC3F,KAAA,CAAA;AACF;AAEA,MAAMC,UAAa,GAAA,CAACV,MAAgB,EAAA,EAAEW,GAAG,EAAmB,GAAA;IAC1D,IAAIX,MAAAA,CAAOY,SAAS,EAAE;;AAEpB,QAAA,MAAMC,YAAYC,KAAMC,CAAAA,IAAI,CAC1BV,MAAOW,CAAAA,KAAK,CAAChB,MAAQ,EAAA;AACnBiB,YAAAA,EAAAA,EAAIjB,OAAOY,SAAS;YACpBT,KAAO,EAAA,CAACC,OAAS,CAACC,MAAAA,CAAOC,QAAQ,CAACF,IAAAA,CAAAA,IAASA,IAAKK,CAAAA,IAAI,KAAK;AAC3D,SAAA,CAAA,CAAA;AAGFI,QAAAA,SAAAA,CAAUK,OAAO,CAAC,CAAC,GAAGC,IAAK,CAAA,GAAA;YACzBlB,UAAWC,CAAAA,WAAW,CAACF,MAAQ,EAAA;gBAAEiB,EAAIE,EAAAA;AAAK,aAAA,CAAA;AAC5C,SAAA,CAAA;AAEA,QAAA,IAAIC,KAAMC,CAAAA,WAAW,CAACrB,MAAAA,CAAOY,SAAS,CAAG,EAAA;AACvC,YAAA,MAAMU,IAAsB,GAAA;gBAC1Bb,IAAM,EAAA,MAAA;AACNE,gBAAAA,GAAAA,EAAKA,GAAO,IAAA,EAAA;gBACZY,QAAU,EAAA;AAAC,oBAAA;wBAAEd,IAAM,EAAA,MAAA;wBAAQe,IAAMb,EAAAA;AAAI;AAAE;AACzC,aAAA;YAEAV,UAAWwB,CAAAA,WAAW,CAACzB,MAAQsB,EAAAA,IAAAA,CAAAA;SAC1B,MAAA;YACLrB,UAAWyB,CAAAA,SAAS,CAAC1B,MAAQ,EAAA;gBAAES,IAAM,EAAA,MAAA;AAAQE,gBAAAA,GAAAA,EAAKA,GAAO,IAAA;aAAuB,EAAA;gBAC9EgB,KAAO,EAAA;AACT,aAAA,CAAA;AACF;AACF;AACF;AAEMC,MAAAA,QAAAA,GAAW,CAAC5B,MAAgBsB,EAAAA,IAAAA,GAAAA;AAChC,IAAA,MAAM,EAAEX,GAAG,EAAEa,IAAI,EAAE,GAAGF,IAAAA;IAEtB,IAAI,CAACtB,MAAOY,CAAAA,SAAS,EAAE;AACrB,QAAA;AACF;AAEA,IAAA,MAAMiB,SAAYxB,GAAAA,MAAAA,CAAOyB,KAAK,CAAC9B,MAAQ,EAAA;QACrCG,KAAO,EAAA,CAACC,OAAS,CAACC,MAAAA,CAAOC,QAAQ,CAACF,IAAAA,CAAAA,IAASA,IAAKK,CAAAA,IAAI,KAAK;AAC3D,KAAA,CAAA;AAEA,IAAA,IAAIoB,SAAW,EAAA;QACb,MAAM,GAAGE,SAAS,GAAGF,SAAAA;QACrB5B,UAAW+B,CAAAA,QAAQ,CAAChC,MAAQ,EAAA;AAAEW,YAAAA;SAAO,EAAA;YAAEM,EAAIc,EAAAA;AAAS,SAAA,CAAA;;AAGpD,QAAA,IAAIP,SAAS,EAAMA,IAAAA,IAAAA,KAASnB,OAAO4B,MAAM,CAACjC,QAAQ+B,QAAW,CAAA,EAAA;YAC3D,MAAMG,iBAAAA,GAAoBpB,MAAMC,IAAI,CAACoB,KAAKZ,QAAQ,CAACvB,QAAQ+B,QAAU,EAAA;gBAAEK,OAAS,EAAA;AAAK,aAAA,CAAA,CAAA;AAErFF,YAAAA,iBAAAA,CAAkBhB,OAAO,CAAC,CAAC,GAAGmB,SAAU,CAAA,GAAA;gBACtCpC,UAAWqC,CAAAA,WAAW,CAACtC,MAAQ,EAAA;oBAAEiB,EAAIoB,EAAAA;AAAU,iBAAA,CAAA;AACjD,aAAA,CAAA;YAEApC,UAAWwB,CAAAA,WAAW,CAACzB,MAAQ,EAAA;AAAC,gBAAA;oBAAES,IAAM,EAAA,MAAA;AAAQe,oBAAAA;AAAK;aAAE,EAAE;gBAAEP,EAAIc,EAAAA,QAAAA,CAASQ,MAAM,CAAC,CAAA;AAAG,aAAA,CAAA;AACpF;AACF;AACF;;;;"}

View File

@@ -0,0 +1,18 @@
'use strict';
// Wrap Object.entries to get the correct types
const getEntries = (object)=>Object.entries(object);
// Wrap Object.keys to get the correct types
const getKeys = (object)=>Object.keys(object);
const isLinkNode = (element)=>{
return element.type === 'link';
};
const isListNode = (element)=>{
return element.type === 'list';
};
exports.getEntries = getEntries;
exports.getKeys = getKeys;
exports.isLinkNode = isLinkNode;
exports.isListNode = isListNode;
//# sourceMappingURL=types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.js","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/types.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport type { Element, Node } from 'slate';\n\ntype Block<T extends Element['type']> = Extract<Node, { type: T }>;\n\n// Wrap Object.entries to get the correct types\nconst getEntries = <T extends object>(object: T) =>\n Object.entries(object) as [keyof T, T[keyof T]][];\n\n// Wrap Object.keys to get the correct types\nconst getKeys = <T extends object>(object: T) => Object.keys(object) as (keyof T)[];\n\nconst isLinkNode = (element: Element): element is Schema.Attribute.LinkInlineNode => {\n return element.type === 'link';\n};\n\nconst isListNode = (element: Element): element is Schema.Attribute.ListBlockNode => {\n return element.type === 'list';\n};\n\nexport { type Block, getEntries, getKeys, isLinkNode, isListNode };\n"],"names":["getEntries","object","Object","entries","getKeys","keys","isLinkNode","element","type","isListNode"],"mappings":";;AAKA;AACA,MAAMA,UAAa,GAAA,CAAmBC,MACpCC,GAAAA,MAAAA,CAAOC,OAAO,CAACF,MAAAA;AAEjB;AACA,MAAMG,OAAU,GAAA,CAAmBH,MAAcC,GAAAA,MAAAA,CAAOG,IAAI,CAACJ,MAAAA;AAE7D,MAAMK,aAAa,CAACC,OAAAA,GAAAA;IAClB,OAAOA,OAAAA,CAAQC,IAAI,KAAK,MAAA;AAC1B;AAEA,MAAMC,aAAa,CAACF,OAAAA,GAAAA;IAClB,OAAOA,OAAAA,CAAQC,IAAI,KAAK,MAAA;AAC1B;;;;;;;"}

View File

@@ -0,0 +1,13 @@
// Wrap Object.entries to get the correct types
const getEntries = (object)=>Object.entries(object);
// Wrap Object.keys to get the correct types
const getKeys = (object)=>Object.keys(object);
const isLinkNode = (element)=>{
return element.type === 'link';
};
const isListNode = (element)=>{
return element.type === 'list';
};
export { getEntries, getKeys, isLinkNode, isListNode };
//# sourceMappingURL=types.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.mjs","sources":["../../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/types.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport type { Element, Node } from 'slate';\n\ntype Block<T extends Element['type']> = Extract<Node, { type: T }>;\n\n// Wrap Object.entries to get the correct types\nconst getEntries = <T extends object>(object: T) =>\n Object.entries(object) as [keyof T, T[keyof T]][];\n\n// Wrap Object.keys to get the correct types\nconst getKeys = <T extends object>(object: T) => Object.keys(object) as (keyof T)[];\n\nconst isLinkNode = (element: Element): element is Schema.Attribute.LinkInlineNode => {\n return element.type === 'link';\n};\n\nconst isListNode = (element: Element): element is Schema.Attribute.ListBlockNode => {\n return element.type === 'list';\n};\n\nexport { type Block, getEntries, getKeys, isLinkNode, isListNode };\n"],"names":["getEntries","object","Object","entries","getKeys","keys","isLinkNode","element","type","isListNode"],"mappings":"AAKA;AACA,MAAMA,UAAa,GAAA,CAAmBC,MACpCC,GAAAA,MAAAA,CAAOC,OAAO,CAACF,MAAAA;AAEjB;AACA,MAAMG,OAAU,GAAA,CAAmBH,MAAcC,GAAAA,MAAAA,CAAOG,IAAI,CAACJ,MAAAA;AAE7D,MAAMK,aAAa,CAACC,OAAAA,GAAAA;IAClB,OAAOA,OAAAA,CAAQC,IAAI,KAAK,MAAA;AAC1B;AAEA,MAAMC,aAAa,CAACF,OAAAA,GAAAA;IAClB,OAAOA,OAAAA,CAAQC,IAAI,KAAK,MAAA;AAC1B;;;;"}

Some files were not shown because too many files have changed in this diff Show More