[WEB-986] fix: editor slash command positioning (#4186)

* fix: table selected cell border

* chore: add syncing message when revalidating page data

* fix: slash command positioning

* fix: mentions list dropdown positioning
This commit is contained in:
Aaryan Khandelwal 2024-04-15 19:45:03 +05:30 committed by GitHub
parent 7a21855ab6
commit 8454e4f1e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 36 additions and 35 deletions

View File

@ -49,15 +49,12 @@
} }
} }
/* Custom list item styles */
/* Custom gap cursor styles */ /* Custom gap cursor styles */
.ProseMirror-gapcursor::after { .ProseMirror-gapcursor::after {
border-top: 1px solid rgb(var(--color-text-100)) !important; border-top: 1px solid rgb(var(--color-text-100)) !important;
} }
/* Custom TODO list checkboxes shoutout to this awesome tutorial: https://moderncss.dev/pure-css-custom-checkbox-style/ */ /* to-do list */
ul[data-type="taskList"] li { ul[data-type="taskList"] li {
font-size: 1rem; font-size: 1rem;
line-height: 1.5; line-height: 1.5;
@ -143,6 +140,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
text-decoration: line-through; text-decoration: line-through;
text-decoration-thickness: 2px; text-decoration-thickness: 2px;
} }
/* end to-do list */
/* Overwrite tippy-box original max-width */ /* Overwrite tippy-box original max-width */
@ -238,7 +236,7 @@ div[data-type="horizontalRule"] {
margin-bottom: 0; margin-bottom: 0;
& > div { & > div {
border-bottom: 1px solid rgb(var(--color-text-100)); border-bottom: 1px solid rgb(var(--color-border-200));
} }
} }
@ -277,7 +275,7 @@ span:focus .fake-cursor {
z-index: 1; z-index: 1;
} }
/* number, bulleted and to-do lists */ /* numbered, bulleted and to-do lists spacing */
.prose ol:where(.prose > :first-child):not(:where([class~="not-prose"], [class~="not-prose"] *)), .prose ol:where(.prose > :first-child):not(:where([class~="not-prose"], [class~="not-prose"] *)),
.prose .prose
ul:not([data-type="taskList"]):where(.prose > :first-child):not(:where([class~="not-prose"], [class~="not-prose"] *)), ul:not([data-type="taskList"]):where(.prose > :first-child):not(:where([class~="not-prose"], [class~="not-prose"] *)),
@ -306,7 +304,7 @@ ul:not([data-type="taskList"]) ol {
ul[data-type="taskList"] ul[data-type="taskList"] { ul[data-type="taskList"] ul[data-type="taskList"] {
margin-top: 0.6rem; margin-top: 0.6rem;
} }
/* end number, bulleted and to-do lists */ /* end numbered, bulleted and to-do lists spacing */
/* tailwind typography */ /* tailwind typography */
.prose :where(h1):not(:where([class~="not-prose"], [class~="not-prose"] *)) { .prose :where(h1):not(:where([class~="not-prose"], [class~="not-prose"] *)) {

View File

@ -31,17 +31,6 @@
} }
} }
.table-wrapper table td > *,
.table-wrapper table th > * {
margin: 0 !important;
padding: 0.25rem 0 !important;
}
.table-wrapper table td.has-focus,
.table-wrapper table th.has-focus {
box-shadow: rgba(var(--color-primary-300), 0.1) 0px 0px 0px 2px inset !important;
}
.table-wrapper table th { .table-wrapper table th {
font-weight: 500; font-weight: 500;
text-align: left; text-align: left;
@ -49,7 +38,7 @@
} }
.table-wrapper table .selectedCell { .table-wrapper table .selectedCell {
border-color: rgba(var(--color-primary-100)); outline: 0.5px solid rgba(var(--color-primary-100));
} }
/* table dropdown */ /* table dropdown */

View File

@ -69,7 +69,7 @@ export const CoreEditorExtensions = (
CustomQuoteExtension, CustomQuoteExtension,
CustomHorizontalRule.configure({ CustomHorizontalRule.configure({
HTMLAttributes: { HTMLAttributes: {
class: "my-4", class: "my-4 border-custom-border-400",
}, },
}), }),
CustomKeymap, CustomKeymap,

View File

@ -135,7 +135,7 @@ export const MentionList = forwardRef((props: MentionListProps, ref) => {
return ( return (
<div <div
ref={commandListContainer} ref={commandListContainer}
className="mentions absolute max-h-48 min-w-[12rem] rounded-md bg-custom-background-100 border-[0.5px] border-custom-border-300 px-2 py-2.5 text-xs shadow-custom-shadow-rg overflow-y-scroll" className="mentions max-h-48 min-w-[12rem] rounded-md bg-custom-background-100 border-[0.5px] border-custom-border-300 px-2 py-2.5 text-xs shadow-custom-shadow-rg overflow-y-scroll"
> >
{isLoading ? ( {isLoading ? (
<div className="text-center text-custom-text-400">Loading...</div> <div className="text-center text-custom-text-400">Loading...</div>

View File

@ -247,14 +247,15 @@ export const updateScrollView = (container: HTMLElement, item: HTMLElement) => {
}; };
const CommandList = ({ items, command }: { items: CommandItemProps[]; command: any; editor: any; range: any }) => { const CommandList = ({ items, command }: { items: CommandItemProps[]; command: any; editor: any; range: any }) => {
// states
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
// refs
const commandListContainer = useRef<HTMLDivElement>(null);
const selectItem = useCallback( const selectItem = useCallback(
(index: number) => { (index: number) => {
const item = items[index]; const item = items[index];
if (item) { if (item) command(item);
command(item);
}
}, },
[command, items] [command, items]
); );
@ -289,8 +290,6 @@ const CommandList = ({ items, command }: { items: CommandItemProps[]; command: a
setSelectedIndex(0); setSelectedIndex(0);
}, [items]); }, [items]);
const commandListContainer = useRef<HTMLDivElement>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
const container = commandListContainer?.current; const container = commandListContainer?.current;
@ -299,29 +298,31 @@ const CommandList = ({ items, command }: { items: CommandItemProps[]; command: a
if (item && container) updateScrollView(container, item); if (item && container) updateScrollView(container, item);
}, [selectedIndex]); }, [selectedIndex]);
return items.length > 0 ? ( if (items.length <= 0) return null;
return (
<div <div
id="slash-command" id="slash-command"
ref={commandListContainer} ref={commandListContainer}
className="fixed z-50 h-auto max-h-[330px] w-52 overflow-y-auto rounded-md border border-custom-border-300 bg-custom-background-100 px-1 py-2 shadow-md transition-all" className="z-10 max-h-80 min-w-[12rem] overflow-y-auto rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 shadow-custom-shadow-rg"
> >
{items.map((item, index) => ( {items.map((item, index) => (
<button <button
key={item.key} key={item.key}
className={cn( className={cn(
`flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-sm text-custom-text-100 hover:bg-custom-background-80`, "flex items-center gap-2 w-full rounded px-1 py-1.5 text-sm text-left truncate text-custom-text-200 hover:bg-custom-background-80",
{ {
"bg-custom-background-80": index === selectedIndex, "bg-custom-background-80": index === selectedIndex,
} }
)} )}
onClick={() => selectItem(index)} onClick={() => selectItem(index)}
> >
<div className="grid flex-shrink-0 place-items-center">{item.icon}</div> <span className="grid place-items-center flex-shrink-0">{item.icon}</span>
<p>{item.title}</p> <p className="flex-grow truncate">{item.title}</p>
</button> </button>
))} ))}
</div> </div>
) : null; );
}; };
interface CommandListInstance { interface CommandListInstance {
@ -338,10 +339,12 @@ const renderItems = () => {
editor: props.editor, editor: props.editor,
}); });
const tippyContainer = document.querySelector(".active-editor") ?? document.querySelector("#editor-container");
// @ts-expect-error Tippy overloads are messed up // @ts-expect-error Tippy overloads are messed up
popup = tippy("body", { popup = tippy("body", {
getReferenceClientRect: props.clientRect, getReferenceClientRect: props.clientRect,
appendTo: () => document.querySelector(".active-editor") ?? document.querySelector("#editor-container"), appendTo: tippyContainer,
content: component.element, content: component.element,
showOnCreate: true, showOnCreate: true,
interactive: true, interactive: true,

View File

@ -19,13 +19,14 @@ import { IPageStore } from "@/store/pages/page.store";
type Props = { type Props = {
editorRef: React.RefObject<EditorRefApi>; editorRef: React.RefObject<EditorRefApi>;
handleDuplicatePage: () => void; handleDuplicatePage: () => void;
isSyncing: boolean;
pageStore: IPageStore; pageStore: IPageStore;
projectId: string; projectId: string;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>; readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
}; };
export const PageExtraOptions: React.FC<Props> = observer((props) => { export const PageExtraOptions: React.FC<Props> = observer((props) => {
const { editorRef, handleDuplicatePage, pageStore, projectId, readOnlyEditorRef } = props; const { editorRef, handleDuplicatePage, isSyncing, pageStore, projectId, readOnlyEditorRef } = props;
// states // states
const [gptModalOpen, setGptModal] = useState(false); const [gptModalOpen, setGptModal] = useState(false);
// store hooks // store hooks
@ -52,6 +53,12 @@ export const PageExtraOptions: React.FC<Props> = observer((props) => {
<span className="text-sm text-custom-text-300">{isSubmitting === "submitting" ? "Saving..." : "Saved"}</span> <span className="text-sm text-custom-text-300">{isSubmitting === "submitting" ? "Saving..." : "Saved"}</span>
</div> </div>
)} )}
{isSyncing && (
<div className="flex items-center gap-x-2">
<RefreshCw className="h-4 w-4 stroke-custom-text-300" />
<span className="text-sm text-custom-text-300">Syncing...</span>
</div>
)}
{is_locked && ( {is_locked && (
<div className="flex h-7 items-center gap-2 rounded-full bg-custom-background-80 px-3 py-0.5 text-xs font-medium text-custom-text-300"> <div className="flex h-7 items-center gap-2 rounded-full bg-custom-background-80 px-3 py-0.5 text-xs font-medium text-custom-text-300">
<Lock className="h-3 w-3" /> <Lock className="h-3 w-3" />

View File

@ -11,6 +11,7 @@ type Props = {
editorRef: React.RefObject<EditorRefApi>; editorRef: React.RefObject<EditorRefApi>;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>; readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
handleDuplicatePage: () => void; handleDuplicatePage: () => void;
isSyncing: boolean;
markings: IMarking[]; markings: IMarking[];
pageStore: IPageStore; pageStore: IPageStore;
projectId: string; projectId: string;
@ -28,6 +29,7 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
markings, markings,
readOnlyEditorReady, readOnlyEditorReady,
handleDuplicatePage, handleDuplicatePage,
isSyncing,
pageStore, pageStore,
projectId, projectId,
sidePeekVisible, sidePeekVisible,
@ -61,6 +63,7 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
<PageExtraOptions <PageExtraOptions
editorRef={editorRef} editorRef={editorRef}
handleDuplicatePage={handleDuplicatePage} handleDuplicatePage={handleDuplicatePage}
isSyncing={isSyncing}
pageStore={pageStore} pageStore={pageStore}
projectId={projectId} projectId={projectId}
readOnlyEditorRef={readOnlyEditorRef} readOnlyEditorRef={readOnlyEditorRef}

View File

@ -46,7 +46,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
}); });
// fetching page details // fetching page details
const { data: swrPageDetails } = useSWR( const { data: swrPageDetails, isValidating } = useSWR(
pageId ? `PAGE_DETAILS_${pageId}` : null, pageId ? `PAGE_DETAILS_${pageId}` : null,
pageId ? () => getPageById(pageId.toString()) : null, pageId ? () => getPageById(pageId.toString()) : null,
{ {
@ -120,6 +120,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
editorReady={editorReady} editorReady={editorReady}
readOnlyEditorReady={readOnlyEditorReady} readOnlyEditorReady={readOnlyEditorReady}
handleDuplicatePage={handleDuplicatePage} handleDuplicatePage={handleDuplicatePage}
isSyncing={isValidating}
markings={markings} markings={markings}
pageStore={pageStore} pageStore={pageStore}
projectId={projectId.toString()} projectId={projectId.toString()}