Skip to content

Commit

Permalink
Improved link editor UX (#4026)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChronicLynx committed Mar 6, 2023
1 parent ea82cb5 commit f161f67
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 27 deletions.
2 changes: 2 additions & 0 deletions packages/lexical-playground/src/appSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type SettingName =
| 'showTreeView'
| 'showNestedEditorTreeView'
| 'emptyEditor'
| 'enableLinkPreviews'
| 'showTableOfContents';

export type Settings = Record<SettingName, boolean>;
Expand All @@ -30,6 +31,7 @@ export const isDevPlayground: boolean =
export const DEFAULT_SETTINGS: Settings = {
disableBeforeInput: false,
emptyEditor: isDevPlayground,
enableLinkPreviews: false,
isAutocomplete: false,
isCharLimit: false,
isCharLimitUtf8: false,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 51 additions & 4 deletions packages/lexical-playground/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -559,9 +559,9 @@ i.prettier-error {

.link-editor .link-input {
display: block;
width: calc(100% - 24px);
width: calc(100% - 75px);
box-sizing: border-box;
margin: 8px 12px;
margin: 12px 12px;
padding: 8px 12px;
border-radius: 15px;
background-color: #eee;
Expand All @@ -573,6 +573,20 @@ i.prettier-error {
font-family: inherit;
}

.link-editor .link-view {
display: block;
width: calc(100% - 24px);
margin: 8px 12px;
padding: 8px 12px;
border-radius: 15px;
font-size: 15px;
color: rgb(5, 5, 5);
border: 0;
outline: 0;
position: relative;
font-family: inherit;
}

.link-editor div.link-edit {
background-image: url(images/icons/pencil-fill.svg);
background-size: 16px;
Expand All @@ -587,10 +601,39 @@ i.prettier-error {
cursor: pointer;
}

.link-editor div.link-cancel {
background-image: url(images/icons/close.svg);
background-size: 16px;
background-position: center;
background-repeat: no-repeat;
width: 35px;
vertical-align: -0.25em;
margin-right: 28px;
position: absolute;
right: 0;
top: 0;
bottom: 0;
cursor: pointer;
}

.link-editor div.link-confirm {
background-image: url(images/icons/success-alt.svg);
background-size: 16px;
background-position: center;
background-repeat: no-repeat;
width: 35px;
vertical-align: -0.25em;
margin-right: 2px;
position: absolute;
right: 0;
top: 0;
bottom: 0;
cursor: pointer;
}

.link-editor .link-input a {
color: rgb(33, 111, 219);
text-decoration: none;
display: block;
text-decoration: underline;
white-space: nowrap;
overflow: hidden;
margin-right: 30px;
Expand All @@ -614,6 +657,10 @@ i.prettier-error {
border-radius: 4px;
}

.ContentEditable__root .PlaygroundEditorTheme__link {
pointer-events: none;
}

.mention:focus {
box-shadow: rgb(180 213 255) 0px 0px 0px 2px;
outline: none;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.link-editor {
display: flex;
position: absolute;
top: 0;
left: 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {Dispatch, useCallback, useEffect, useRef, useState} from 'react';
import * as React from 'react';
import {createPortal} from 'react-dom';

import {useSettings} from '../../context/SettingsContext';
import LinkPreview from '../../ui/LinkPreview';
import {getSelectedNode} from '../../utils/getSelectedNode';
import {setFloatingElemPosition} from '../../utils/setFloatingElemPosition';
Expand All @@ -43,9 +44,13 @@ function FloatingLinkEditor({
setIsLink: Dispatch<boolean>;
anchorElem: HTMLElement;
}): JSX.Element {
const {
settings: {enableLinkPreviews},
} = useSettings();
const editorRef = useRef<HTMLDivElement | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
const [linkUrl, setLinkUrl] = useState('');
const [editedLinkUrl, setEditedLinkUrl] = useState('');
const [isEditMode, setEditMode] = useState(false);
const [lastSelection, setLastSelection] = useState<
RangeSelection | GridSelection | NodeSelection | null
Expand Down Expand Up @@ -196,34 +201,65 @@ function FloatingLinkEditor({
}
}, [isEditMode]);

const monitorInputInteraction = (
event: React.KeyboardEvent<HTMLInputElement>,
) => {
if (event.key === 'Enter') {
event.preventDefault();
handleLinkSubmission();
} else if (event.key === 'Escape') {
event.preventDefault();
setEditMode(false);
}
};

const handleLinkSubmission = () => {
if (lastSelection !== null) {
if (linkUrl !== '') {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(editedLinkUrl));
}
setEditMode(false);
}
};

return (
<div ref={editorRef} className="link-editor">
{!isLink ? null : isEditMode ? (
<input
ref={inputRef}
className="link-input"
value={linkUrl}
onChange={(event) => {
setLinkUrl(event.target.value);
}}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === 'Escape') {
event.preventDefault();
if (lastSelection !== null) {
if (linkUrl !== '') {
editor.dispatchCommand(
TOGGLE_LINK_COMMAND,
sanitizeUrl(linkUrl),
);
}
<>
<input
ref={inputRef}
className="link-input"
value={editedLinkUrl}
onChange={(event) => {
setEditedLinkUrl(event.target.value);
}}
onKeyDown={(event) => {
monitorInputInteraction(event);
}}
/>
<div>
<div
className="link-cancel"
role="button"
tabIndex={0}
onMouseDown={(event) => event.preventDefault()}
onClick={() => {
setEditMode(false);
}
}
}}
/>
}}
/>

<div
className="link-confirm"
role="button"
tabIndex={0}
onMouseDown={(event) => event.preventDefault()}
onClick={handleLinkSubmission}
/>
</div>
</>
) : (
<>
<div className="link-input">
<div className="link-view">
<a href={linkUrl} target="_blank" rel="noopener noreferrer">
{linkUrl}
</a>
Expand All @@ -233,11 +269,12 @@ function FloatingLinkEditor({
tabIndex={0}
onMouseDown={(event) => event.preventDefault()}
onClick={() => {
setEditedLinkUrl(linkUrl);
setEditMode(true);
}}
/>
</div>
<LinkPreview url={linkUrl} />
{enableLinkPreviews ? <LinkPreview url={linkUrl} /> : ''}
</>
)}
</div>
Expand Down

2 comments on commit f161f67

@vercel
Copy link

@vercel vercel bot commented on f161f67 Mar 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

lexical – ./packages/lexical-website

lexical-git-main-fbopensource.vercel.app
lexical-fbopensource.vercel.app
lexicaljs.com
lexicaljs.org
www.lexical.dev
lexical.dev

@vercel
Copy link

@vercel vercel bot commented on f161f67 Mar 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

lexical-playground – ./packages/lexical-playground

lexical-playground-git-main-fbopensource.vercel.app
lexical-playground.vercel.app
lexical-playground-fbopensource.vercel.app
playground.lexical.dev

Please sign in to comment.