forked from github/plane
[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:
parent
7a21855ab6
commit
8454e4f1e0
@ -49,15 +49,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom list item styles */
|
||||
|
||||
/* Custom gap cursor styles */
|
||||
.ProseMirror-gapcursor::after {
|
||||
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 {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
@ -143,6 +140,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
||||
text-decoration: line-through;
|
||||
text-decoration-thickness: 2px;
|
||||
}
|
||||
/* end to-do list */
|
||||
|
||||
/* Overwrite tippy-box original max-width */
|
||||
|
||||
@ -238,7 +236,7 @@ div[data-type="horizontalRule"] {
|
||||
margin-bottom: 0;
|
||||
|
||||
& > 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;
|
||||
}
|
||||
|
||||
/* 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
|
||||
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"] {
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
/* end number, bulleted and to-do lists */
|
||||
/* end numbered, bulleted and to-do lists spacing */
|
||||
|
||||
/* tailwind typography */
|
||||
.prose :where(h1):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
|
@ -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 {
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
@ -49,7 +38,7 @@
|
||||
}
|
||||
|
||||
.table-wrapper table .selectedCell {
|
||||
border-color: rgba(var(--color-primary-100));
|
||||
outline: 0.5px solid rgba(var(--color-primary-100));
|
||||
}
|
||||
|
||||
/* table dropdown */
|
||||
|
@ -69,7 +69,7 @@ export const CoreEditorExtensions = (
|
||||
CustomQuoteExtension,
|
||||
CustomHorizontalRule.configure({
|
||||
HTMLAttributes: {
|
||||
class: "my-4",
|
||||
class: "my-4 border-custom-border-400",
|
||||
},
|
||||
}),
|
||||
CustomKeymap,
|
||||
|
@ -135,7 +135,7 @@ export const MentionList = forwardRef((props: MentionListProps, ref) => {
|
||||
return (
|
||||
<div
|
||||
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 ? (
|
||||
<div className="text-center text-custom-text-400">Loading...</div>
|
||||
|
@ -247,14 +247,15 @@ export const updateScrollView = (container: HTMLElement, item: HTMLElement) => {
|
||||
};
|
||||
|
||||
const CommandList = ({ items, command }: { items: CommandItemProps[]; command: any; editor: any; range: any }) => {
|
||||
// states
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
// refs
|
||||
const commandListContainer = useRef<HTMLDivElement>(null);
|
||||
|
||||
const selectItem = useCallback(
|
||||
(index: number) => {
|
||||
const item = items[index];
|
||||
if (item) {
|
||||
command(item);
|
||||
}
|
||||
if (item) command(item);
|
||||
},
|
||||
[command, items]
|
||||
);
|
||||
@ -289,8 +290,6 @@ const CommandList = ({ items, command }: { items: CommandItemProps[]; command: a
|
||||
setSelectedIndex(0);
|
||||
}, [items]);
|
||||
|
||||
const commandListContainer = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const container = commandListContainer?.current;
|
||||
|
||||
@ -299,29 +298,31 @@ const CommandList = ({ items, command }: { items: CommandItemProps[]; command: a
|
||||
if (item && container) updateScrollView(container, item);
|
||||
}, [selectedIndex]);
|
||||
|
||||
return items.length > 0 ? (
|
||||
if (items.length <= 0) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
id="slash-command"
|
||||
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) => (
|
||||
<button
|
||||
key={item.key}
|
||||
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,
|
||||
}
|
||||
)}
|
||||
onClick={() => selectItem(index)}
|
||||
>
|
||||
<div className="grid flex-shrink-0 place-items-center">{item.icon}</div>
|
||||
<p>{item.title}</p>
|
||||
<span className="grid place-items-center flex-shrink-0">{item.icon}</span>
|
||||
<p className="flex-grow truncate">{item.title}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null;
|
||||
);
|
||||
};
|
||||
|
||||
interface CommandListInstance {
|
||||
@ -338,10 +339,12 @@ const renderItems = () => {
|
||||
editor: props.editor,
|
||||
});
|
||||
|
||||
const tippyContainer = document.querySelector(".active-editor") ?? document.querySelector("#editor-container");
|
||||
|
||||
// @ts-expect-error Tippy overloads are messed up
|
||||
popup = tippy("body", {
|
||||
getReferenceClientRect: props.clientRect,
|
||||
appendTo: () => document.querySelector(".active-editor") ?? document.querySelector("#editor-container"),
|
||||
appendTo: tippyContainer,
|
||||
content: component.element,
|
||||
showOnCreate: true,
|
||||
interactive: true,
|
||||
|
@ -19,13 +19,14 @@ import { IPageStore } from "@/store/pages/page.store";
|
||||
type Props = {
|
||||
editorRef: React.RefObject<EditorRefApi>;
|
||||
handleDuplicatePage: () => void;
|
||||
isSyncing: boolean;
|
||||
pageStore: IPageStore;
|
||||
projectId: string;
|
||||
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
|
||||
};
|
||||
|
||||
export const PageExtraOptions: React.FC<Props> = observer((props) => {
|
||||
const { editorRef, handleDuplicatePage, pageStore, projectId, readOnlyEditorRef } = props;
|
||||
const { editorRef, handleDuplicatePage, isSyncing, pageStore, projectId, readOnlyEditorRef } = props;
|
||||
// states
|
||||
const [gptModalOpen, setGptModal] = useState(false);
|
||||
// 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>
|
||||
</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 && (
|
||||
<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" />
|
||||
|
@ -11,6 +11,7 @@ type Props = {
|
||||
editorRef: React.RefObject<EditorRefApi>;
|
||||
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
|
||||
handleDuplicatePage: () => void;
|
||||
isSyncing: boolean;
|
||||
markings: IMarking[];
|
||||
pageStore: IPageStore;
|
||||
projectId: string;
|
||||
@ -28,6 +29,7 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
|
||||
markings,
|
||||
readOnlyEditorReady,
|
||||
handleDuplicatePage,
|
||||
isSyncing,
|
||||
pageStore,
|
||||
projectId,
|
||||
sidePeekVisible,
|
||||
@ -61,6 +63,7 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
|
||||
<PageExtraOptions
|
||||
editorRef={editorRef}
|
||||
handleDuplicatePage={handleDuplicatePage}
|
||||
isSyncing={isSyncing}
|
||||
pageStore={pageStore}
|
||||
projectId={projectId}
|
||||
readOnlyEditorRef={readOnlyEditorRef}
|
||||
|
@ -46,7 +46,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
||||
});
|
||||
|
||||
// fetching page details
|
||||
const { data: swrPageDetails } = useSWR(
|
||||
const { data: swrPageDetails, isValidating } = useSWR(
|
||||
pageId ? `PAGE_DETAILS_${pageId}` : null,
|
||||
pageId ? () => getPageById(pageId.toString()) : null,
|
||||
{
|
||||
@ -120,6 +120,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
||||
editorReady={editorReady}
|
||||
readOnlyEditorReady={readOnlyEditorReady}
|
||||
handleDuplicatePage={handleDuplicatePage}
|
||||
isSyncing={isValidating}
|
||||
markings={markings}
|
||||
pageStore={pageStore}
|
||||
projectId={projectId.toString()}
|
||||
|
Loading…
Reference in New Issue
Block a user