Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feature/9433-commit-m…
Browse files Browse the repository at this point in the history
…odal
  • Loading branch information
twschiller committed Nov 14, 2024
2 parents 58d5155 + 4af5865 commit f3cfe59
Show file tree
Hide file tree
Showing 26 changed files with 688 additions and 80 deletions.
1 change: 1 addition & 0 deletions applications/browser-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@rjsf/core": "^5.22.3",
"@rjsf/utils": "^5.22.3",
"@szhsin/react-menu": "^4.2.2",
"@tiptap/extension-image": "^2.9.1",
"@tiptap/extension-link": "^2.9.1",
"@tiptap/extension-underline": "^2.9.1",
"@tiptap/pm": "^2.9.1",
Expand Down
2 changes: 1 addition & 1 deletion applications/browser-extension/public/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* tslint:disable */

/**
* Mock Service Worker (1.3.4).
* Mock Service Worker (1.3.5).
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import React, { Suspense, useMemo } from "react";
import { Stylesheets } from "@/components/Stylesheets";
import EmotionShadowRoot from "@/components/EmotionShadowRoot";
import isolatedComponentList from "./isolatedComponentList";
import { Provider } from "react-redux";
import { configureStore } from "@reduxjs/toolkit";
import { appApi } from "@/data/service/api";

const MODE = process.env.SHADOW_DOM as "open" | "closed";

Expand Down Expand Up @@ -110,6 +113,17 @@ type Props<T> = React.DetailedHTMLProps<
noStyle?: boolean;
};

const store = configureStore({
reducer: {
[appApi.reducerPath]: appApi.reducer,
},
middleware(getDefaultMiddleware) {
/* eslint-disable unicorn/prefer-spread -- It's not Array#concat, can't use spread */
return getDefaultMiddleware().concat(appApi.middleware);
/* eslint-enable unicorn/prefer-spread */
},
});

/**
* Isolate component loaded via React.lazy() in a shadow DOM, including its styles.
*
Expand Down Expand Up @@ -151,8 +165,10 @@ export default function IsolatedComponent<T>({
<EmotionShadowRoot mode={MODE} pb-name={name} {...props}>
<style>{cssText}</style>
<Stylesheets href={stylesheetUrl ?? []}>
{/* Must call the factory on each render to pick up changes to the component props */}
<Suspense fallback={null}>{factory(LazyComponent)}</Suspense>
<Provider store={store}>
{/* Must call the factory on each render to pick up changes to the component props */}
<Suspense fallback={null}>{factory(LazyComponent)}</Suspense>
</Provider>
</Stylesheets>
</EmotionShadowRoot>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,11 @@ const RichTextFields: React.FunctionComponent<{ uiOptionsPath: string }> = ({
return (
<SchemaField
name={configName("database")}
isRequired
schema={{
$ref: "https://app.pixiebrix.com/schemas/database#",
title: "Asset Database",
description:
"Asset database to use for image upload. Asset databases are a specific kind of database that" +
" can be created in the Admin Console.",
"Asset database to use for image upload. If not specified, image support will be disabled.",
}}
uiSchema={{
"ui:widget": "database",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ const FormPreview: React.FC<FormPreviewProps> = ({
propertySchema.oneOf = [{ const: "" }];
}
}

if (value[UI_WIDGET] === "richText") {
propertySchema.readOnly = true;
}
}
}),
[rjsfSchema, activeField],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,24 @@ import CustomFormComponent, {
type CustomFormComponentProps,
} from "@/bricks/renderers/CustomFormComponent";
import { type Schema } from "@/types/schemaTypes";
import { Provider } from "react-redux";
import { configureStore } from "@reduxjs/toolkit";
import { appApi } from "@/data/service/api";

describe("RichTextWidget", () => {
const user = userEvent.setup({
// 20ms delay between key presses to allow the editor state to update
// before the next key press
delay: 20,
});

const createTestStore = () =>
configureStore({
reducer: {
appApi: appApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(appApi.middleware),
});

const createSchema = (properties: Record<string, any>): Schema => ({
type: "object",
properties,
Expand All @@ -48,15 +58,19 @@ describe("RichTextWidget", () => {
}: Pick<CustomFormComponentProps, "schema" | "uiSchema" | "formData"> & {
onSubmit?: jest.Mock;
}) => {
const store = createTestStore();

render(
<CustomFormComponent
schema={schema}
formData={formData}
uiSchema={uiSchema}
submitCaption="Submit"
autoSave={false}
onSubmit={onSubmit}
/>,
<Provider store={store}>
<CustomFormComponent
schema={schema}
formData={formData}
uiSchema={uiSchema}
submitCaption="Submit"
autoSave={false}
onSubmit={onSubmit}
/>
</Provider>,
);
return { onSubmit };
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
*/

import React from "react";
import { type ErrorSchema, type WidgetProps } from "@rjsf/utils";
import { type WidgetProps } from "@rjsf/utils";
import RichTextEditor from "@/components/richTextEditor/RichTextEditor";
import { validateUUID } from "@/types/helpers";

const RichTextWidget: React.FunctionComponent<WidgetProps> = ({
id,
Expand All @@ -27,19 +28,9 @@ const RichTextWidget: React.FunctionComponent<WidgetProps> = ({
disabled,
readonly,
options,
value,
}) => {
const { database } = options;

if (!database) {
// TODO: Can't figure out how to satisfy this type without casting, but this is how it's done in the docs
// https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/custom-widgets-fields/#raising-errors-from-within-a-custom-widget-or-field
const databaseConfigurationError = {
__errors: ["Rich text field asset database is required"],
} as ErrorSchema;
onChange(value, databaseConfigurationError);
}

return (
<RichTextEditor
onUpdate={({ editor }) => {
Expand All @@ -53,6 +44,7 @@ const RichTextWidget: React.FunctionComponent<WidgetProps> = ({
onBlur(id, editor.getHTML());
}}
editable={!(disabled || readonly)}
assetDatabaseId={validateUUID(database)}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { createContext, useContext } from "react";

type ErrorContextType = {
error: string | null;
setError: (error: string | null) => void;
};

const ErrorContext = createContext<ErrorContextType | null>(null);

export function useShowError() {
const context = useContext(ErrorContext);
if (!context) {
throw new Error("useRichTextError must be used within a RichTextEditor");
}

return context;
}

export default ErrorContext;
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React from "react";
import { Toast, Button } from "react-bootstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExclamationCircle, faTimes } from "@fortawesome/free-solid-svg-icons";
import styles from "./RichTextEditor.module.scss";

interface ErrorToastProps {
error: string | null;
onClose: () => void;
}

const ErrorToast: React.FC<ErrorToastProps> = ({ error, onClose }) => (
<Toast
show={Boolean(error)}
onClose={onClose}
className={styles.error}
autohide
animation={false}
delay={5000}
>
<FontAwesomeIcon className="mr-2" icon={faExclamationCircle} /> {error}
<Button variant="outline-danger" onClick={onClose}>
<FontAwesomeIcon icon={faTimes} />
</Button>
</Toast>
);

export default ErrorToast;
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
transition:
border-color 0.15s ease-in-out,
box-shadow 0.15s ease-in-out;
position: relative;

&:focus-within {
border-color: #80bdff;
Expand All @@ -41,3 +42,31 @@
outline: 0;
}
}

.error {
display: flex !important;
align-items: center;
color: #dc3545;
padding: 4px 8px;
font-size: 0.875rem;
position: absolute;
bottom: 4px;
left: 4px;

:global(.btn) {
margin-left: 8px;
color: #dc3545;
padding: 0;
border: none;
background-color: transparent;

&:focus,
&:active,
&:hover,
&:active:focus {
color: #dc3545;
background-color: transparent;
box-shadow: none !important;
}
}
}
Loading

0 comments on commit f3cfe59

Please sign in to comment.