Skip to content

Commit 5aa17e0

Browse files
authored
Merge pull request #46 from kwinyyyc/fix/5.8.0
Fix/5.8.0
2 parents aa66e0b + 4c6bd4f commit 5aa17e0

File tree

13 files changed

+342
-275
lines changed

13 files changed

+342
-275
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { FC as FunctionComponent, useState, useEffect, useMemo } from "react";
2+
import { Flex, Field } from "@strapi/design-system";
3+
import type { Schema } from "@strapi/types";
4+
import { commands, ICommand } from "@uiw/react-md-editor";
5+
import { useIntl } from "react-intl";
6+
import { styled } from "styled-components";
7+
import "@uiw/react-markdown-preview/markdown.css";
8+
import { PLUGIN_ID } from '../utils/pluginId';
9+
import MediaLib from "./MediaLib";
10+
import { useField } from "@strapi/strapi/admin";
11+
import assetsToMarkdown from "../utils/assetsToMarkdown";
12+
import CustomMDEditor from "./CustomMDEditor";
13+
14+
const Wrapper = styled.div`
15+
flex-basis: 100%;
16+
> div:nth-child(2) {
17+
display: none;
18+
}
19+
.w-md-editor-bar {
20+
display: none;
21+
}
22+
.w-md-editor {
23+
border: 1px solid #dcdce4;
24+
border-radius: 4px;
25+
box-shadow: none;
26+
&:focus-within {
27+
border: 1px solid #4945ff;
28+
box-shadow: #4945ff 0px 0px 0px 2px;
29+
}
30+
min-height: 400px;
31+
display: flex;
32+
flex-direction: column;
33+
img {
34+
max-width: 100%;
35+
}
36+
ul,ol{
37+
list-style:inherit;
38+
}
39+
.w-md-editor-preview {
40+
display: block;
41+
strong {
42+
font-weight: bold;
43+
}
44+
em {
45+
font-style: italic;
46+
}
47+
}
48+
}
49+
.w-md-editor-content {
50+
flex: 1 1 auto;
51+
}
52+
.w-md-editor-fullscreen {
53+
z-index: 11;
54+
}
55+
.w-md-editor-text {
56+
margin: 0;
57+
}
58+
.w-md-editor-preview ol {
59+
list-style: auto;
60+
}
61+
`;
62+
63+
interface FieldProps {
64+
name: string;
65+
onChange: (e: { target: { name: string; value: string } }) => void;
66+
value: string;
67+
intlLabel: {
68+
id: string;
69+
defaultMessage: string;
70+
};
71+
disabled?: boolean;
72+
error?: string;
73+
description?: {
74+
id: string;
75+
defaultMessage: string;
76+
};
77+
required?: boolean;
78+
attribute?: any; // TO FIX
79+
labelAction?: React.ReactNode; //TO FIX TO CHECK
80+
}
81+
82+
interface CursorPosition {
83+
start: number;
84+
end: number;
85+
}
86+
87+
const CustomField: FunctionComponent<FieldProps> = ({
88+
attribute,
89+
name,
90+
disabled,
91+
labelAction,
92+
required,
93+
description,
94+
error,
95+
intlLabel,
96+
}) => {
97+
// const { formatMessage } = useIntl();
98+
const field: any = useField(name);
99+
100+
const formatMessage = (message: { id: string; defaultMessage: string }) =>
101+
message?.defaultMessage ?? "";
102+
const [mediaLibVisible, setMediaLibVisible] = useState(false);
103+
const [cursorPosition, setCursorPosition] = useState<CursorPosition | null>(null);
104+
105+
const handleToggleMediaLib = () => setMediaLibVisible((prev) => !prev);
106+
107+
const updateFieldValue = (value:any) => {
108+
field.onChange({ target: { name, value: value } });
109+
}
110+
111+
const handleChangeAssets = (assets: Schema.Attribute.MediaValue<true>) => {
112+
113+
let output;
114+
const assetsString = assetsToMarkdown(assets);
115+
116+
if (cursorPosition) {
117+
output = field.value.slice(0, cursorPosition.start) + assetsString + field.value.slice(cursorPosition.end);
118+
}else{
119+
output = field.value + assetsString;
120+
}
121+
122+
updateFieldValue(output);
123+
handleToggleMediaLib();
124+
};
125+
126+
const [config, setConfig] = useState<{ toolbarCommands?: string[] }>({});
127+
128+
const toolbarCommands = useMemo(() => {
129+
const mediaLibraryButton: ICommand = {
130+
name: "media",
131+
keyCommand: "media",
132+
buttonProps: { "aria-label": "Insert media" },
133+
icon: (
134+
<svg width="12" height="12" viewBox="0 0 20 20">
135+
<path
136+
fill="currentColor"
137+
d="M15 9c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm4-7H1c-.55 0-1 .45-1 1v14c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 13l-6-5-2 2-4-5-4 8V4h16v11z"
138+
></path>
139+
</svg>
140+
),
141+
execute: (state, _api) => {
142+
setCursorPosition(state.selection);
143+
handleToggleMediaLib();
144+
},
145+
};
146+
if (!config?.toolbarCommands) {
147+
return [...commands.getCommands(),
148+
commands.divider,
149+
mediaLibraryButton
150+
] as ICommand[];
151+
}
152+
const customCommands = config?.toolbarCommands
153+
?.map((config) => {
154+
if (config === "mediaLibraryButton") return mediaLibraryButton;
155+
if (
156+
config in commands &&
157+
commands[config as unknown as keyof typeof commands]
158+
) {
159+
return commands[
160+
config as unknown as keyof typeof commands
161+
] as ICommand;
162+
}
163+
})
164+
.filter((command): command is ICommand => command !== undefined);
165+
166+
return customCommands;
167+
}, [JSON.stringify(config)]);
168+
169+
useEffect(() => {
170+
fetch(`/${PLUGIN_ID}`)
171+
.then((response) => response.json())
172+
.then((data) => {
173+
setConfig(data);
174+
});
175+
}, []);
176+
177+
return (
178+
<Field.Root
179+
name={name}
180+
id={name}
181+
error={error}
182+
hint={description && formatMessage(description)}
183+
>
184+
<Flex spacing={1} alignItems="normal" style={{ flexDirection: "column" }}>
185+
<Field.Label action={labelAction} required={required}>
186+
{intlLabel ? formatMessage(intlLabel) : name}
187+
</Field.Label>
188+
<Wrapper>
189+
<CustomMDEditor
190+
hidden={disabled}
191+
value={field.value}
192+
onChange={updateFieldValue}
193+
commands={toolbarCommands}
194+
/>
195+
</Wrapper>
196+
<Field.Hint />
197+
<Field.Error />
198+
</Flex>
199+
<MediaLib
200+
/*allowedTypes={['images']}*/
201+
isOpen={mediaLibVisible}
202+
onChange={handleChangeAssets}
203+
onToggle={handleToggleMediaLib}
204+
/>
205+
</Field.Root>
206+
);
207+
};
208+
209+
export { CustomField };
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { useState,useEffect } from "react";
2+
import MDEditor from "@uiw/react-md-editor";
3+
4+
const CustomMDEditor = (props:any) => {
5+
6+
const [value, setValue] = useState<string | undefined>(props.value);
7+
8+
useEffect(() => {
9+
setValue(props.value)
10+
}, [props.value]);
11+
12+
useEffect(() => {
13+
props.onChange(value);
14+
}, [value]);
15+
16+
return (
17+
<MDEditor
18+
value={value}
19+
onChange={setValue}
20+
hidden={props.hidden}
21+
commands={props.commands}
22+
/>
23+
);
24+
};
25+
26+
export default CustomMDEditor;

admin/src/components/Initializer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import { useEffect, useRef } from 'react';
8-
import pluginId from '../pluginId';
8+
import {PLUGIN_ID} from '../utils/pluginId';
99

1010
type InitializerProps = {
1111
setPlugin: (id: string) => void;
@@ -15,7 +15,7 @@ const Initializer = ({ setPlugin }: InitializerProps) => {
1515
const ref = useRef(setPlugin);
1616

1717
useEffect(() => {
18-
ref.current(pluginId);
18+
ref.current(PLUGIN_ID);
1919
}, []);
2020

2121
return null;

admin/src/components/MediaLib.tsx

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,49 @@
1-
import { FC as FunctionComponent } from "react";
2-
3-
import { useStrapiApp } from "@strapi/admin/strapi-admin";
1+
import React from "react";
2+
import prefixFileUrlWithBackendUrl from "../utils/prefixFileUrlWithBackendUrl";
3+
import { useStrapiApp } from "@strapi/strapi/admin";
44
import type { Schema } from "@strapi/types";
55

6-
const prefixFileUrlWithBackendUrl = (fileURL: string) => {
7-
return !!fileURL &&
8-
fileURL.startsWith("/") &&
9-
"strapi" in window &&
10-
window.strapi instanceof Object &&
11-
"backendURL" in window.strapi &&
12-
window.strapi.backendURL
13-
? `${window.strapi.backendURL}${fileURL}`
14-
: fileURL;
15-
};
16-
17-
interface MediaLibComponentProps {
18-
isOpen: boolean;
19-
onChange: (files: Schema.Attribute.MediaValue<true>) => void;
20-
onToggle: () => void;
21-
}
22-
23-
const MediaLib: FunctionComponent<MediaLibComponentProps> = ({
24-
isOpen,
6+
const MediaLibComponent: React.FC<any> = ({
7+
isOpen = false,
258
onChange,
269
onToggle,
10+
allowedTypes,
2711
}) => {
2812
const components = useStrapiApp("ImageDialog", (state) => state.components);
2913
if (!components || !isOpen) return null;
3014

31-
const MediaLibraryDialog = components["media-library"] as FunctionComponent<{
15+
const MediaLibraryDialog: any = components[
16+
"media-library"
17+
] as React.ComponentType<{
18+
allowedTypes?: Schema.Attribute.MediaKind[]; // 'images' | 'videos' | 'files' | 'audios'
3219
onClose: () => void;
3320
onSelectAssets: (_images: Schema.Attribute.MediaValue<true>) => void;
3421
}>;
3522

36-
const handleSelectAssets = (files: Schema.Attribute.MediaValue<true>) => {
37-
const formattedFiles = files.map((f) => ({
23+
const handleSelectAssets = (assets: Schema.Attribute.MediaValue<true>) => {
24+
const formattedFiles = assets.map((f) => ({
3825
alt: f.alternativeText || f.name,
3926
url: prefixFileUrlWithBackendUrl(f.url),
4027
mime: f.mime,
28+
//width: f.width,
29+
//height: f.height,
30+
//size: f.size,
31+
//formats:f.formats,
4132
}));
42-
4333
onChange(formattedFiles);
4434
};
4535

36+
if (!isOpen) {
37+
return null;
38+
}
39+
4640
return (
4741
<MediaLibraryDialog
42+
allowedTypes={allowedTypes}
4843
onClose={onToggle}
4944
onSelectAssets={handleSelectAssets}
5045
/>
5146
);
5247
};
5348

54-
MediaLib.defaultProps = {
55-
isOpen: false,
56-
onChange: (_files: Schema.Attribute.MediaValue<true>) => {},
57-
onToggle: () => {},
58-
};
59-
60-
export { MediaLib };
49+
export default MediaLibComponent;

0 commit comments

Comments
 (0)