@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
317 lines (277 loc) • 11.2 kB
JavaScript
/* eslint-disable @atlaskit/ui-styling-standard/use-compiled -- Pre-existing lint debt surfaced by this mechanical type-import-only PR. */
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
import { css } from '@emotion/react';
import { participantColors } from '@atlaskit/editor-shared-styles';
import { getGlobalTheme } from '@atlaskit/tokens';
// Format of the payload returned by the callback function passed to the collab provider
// that gets called when syncing with the back-end service fails.
// Format of the document and its metadata returned from the collab provider
// after editing and for draft sync
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// Provider Errors
// Emitted errors
export let PROVIDER_ERROR_CODE = /*#__PURE__*/function (PROVIDER_ERROR_CODE) {
PROVIDER_ERROR_CODE["NO_PERMISSION_ERROR"] = "NO_PERMISSION_ERROR";
PROVIDER_ERROR_CODE["INVALID_USER_TOKEN"] = "INVALID_USER_TOKEN";
PROVIDER_ERROR_CODE["DOCUMENT_NOT_FOUND"] = "DOCUMENT_NOT_FOUND";
PROVIDER_ERROR_CODE["LOCKED"] = "LOCKED";
PROVIDER_ERROR_CODE["FAIL_TO_SAVE"] = "FAIL_TO_SAVE";
PROVIDER_ERROR_CODE["DOCUMENT_RESTORE_ERROR"] = "DOCUMENT_RESTORE_ERROR";
PROVIDER_ERROR_CODE["INITIALISATION_ERROR"] = "INITIALISATION_ERROR";
PROVIDER_ERROR_CODE["NETWORK_ISSUE"] = "NETWORK_ISSUE";
PROVIDER_ERROR_CODE["INVALID_PROVIDER_CONFIGURATION"] = "INVALID_PROVIDER_CONFIGURATION";
PROVIDER_ERROR_CODE["INTERNAL_SERVICE_ERROR"] = "INTERNAL_SERVICE_ERROR";
PROVIDER_ERROR_CODE["DOCUMENT_UPDATE_ERROR"] = "DOCUMENT_UPDATE_ERROR";
return PROVIDER_ERROR_CODE;
}({});
/**
* This occurs when the provided user token is considered invalid for the given document ARI.
* It happens during initialisation of the provider.
* It could mean the document has been deleted (hence not found).
* @message Message returned to editor, i.e User does not have permissions to access this document or document is not found
* @recoverable It is recoverable, as we will try to refresh the token.
*/
/**
* Similar to InsufficientEditingPermission, but the user token is invalid because it has expired or been revoked.
* It may also be an invalid token format.
* This error is given to the provider by NCS.
* @message Message returned to editor, i.e. The user token was invalid
* @recoverable It is recoverable, as we will try to refresh the token.
*/
/**
* Document not found error, thrown when the provider is unable to find a document with the given ARI and user token.
* It occurs during fetchCatchup, a function that fetches the latest document state during catchup.
* We need to recieve a 404 from the document service to throw this error.
* @message Message returned to editor, i.e. The requested document is not found
* @recoverable It is recoverable, as the provider can try again later.
*/
/**
* This error is thrown when the document is locked by another user.
* The error is passed to us by NCS.
* @message Message returned to editor, i.e. The document is currently not available, please try again later
* @recoverable It is recoverable, as the provider can try again later.
*/
/**
* This error is thrown when the provider is unable to save the document.
* This can happen when the connection to dynamoDB is lost, or when we do not have sufficient permissions (DYNAMO ERROR).
* This error is given to us by NCS.
* @message Message returned to editor, i.e. Collab service is not able to save changes
* @recoverable It is not recoverable, as we don't want the user to continue editing a document that is not being saved.
*/
/**
* This error is thrown when the provider is unable to restore the document.
* It occurs during onRestore, a function that restores the document to a previous version and reapplies unconfirmed steps.
* onRestore is called when page recovery has emitted an 'init' event on a page client is currently connected to.
* It could mean we failed to update the page metadata, or we failed to reapply unconfirmed steps.
* @message Message returned to editor, i.e. Collab service unable to restore document
* @recoverable It is not recoverable, as the provider has no further options after this.
* The user will need to refresh the page to try again.
*/
/**
* The initial document couldn't be loaded from the collab service.
* This error is given to us by NCS.
* It could indicate either a network issue, or an internal service error in NCS.
* @message Message returned to editor, i.e. The initial document couldn't be loaded from the collab service
* @recoverable It is not recoverable, as the provider cannot do anything to fix it.
* The user will need to refresh the page to try again.
*/
/**
* Couldn't reconnect to the collab service (NCS) due to network issues.
* NCS could be down, or the user could be offline. It's also possible the url is incorrect, or the user is behind a proxy blocking the connection.
* Fired upon a reconnection attempt error (from Socket.IO Manager)
* @message Message returned to editor, i.e. Couldn't reconnect to the collab service due to network issues
* @recoverable It is recoverable, as the provider will try to reconnect.
*/
/**
* This error is thrown when the provider has an invalid configuration.
* It could happen due to these errors from NCS:
* NAMESPACE_INVALID
INVALID_ACTIVATION_ID
INVALID_DOCUMENT_ARI
INVALID_CLOUD_ID
* @message Message returned to editor, i.e. Invalid provider configuration
* @recoverable It is not recoverable, as the provider cannot do anything to fix it.
* The service using the provider will need to fix the configuration.
*/
/**
* This error is thrown when the provider encounters an internal service error, not otherwise accounted for.
* @message Message returned to editor, i.e. Collab Provider experienced an unrecoverable error
* @recoverable It is not recoverable, as the provider cannot do anything to fix it.
*/
/**
* A union of all possible provider errors that can be emitted back to the editor.
*/
// Collab Provider interface
// Ignored via go/ees007
// eslint-disable-next-line @atlaskit/editor/enforce-todo-comment-format
// TODO: Deprecate redundant payload types
export let DisconnectReason = /*#__PURE__*/function (DisconnectReason) {
DisconnectReason["CLIENT_DISCONNECT"] = "CLIENT_DISCONNECT";
DisconnectReason["SERVER_DISCONNECT"] = "SERVER_DISCONNECT";
DisconnectReason["SOCKET_CLOSED"] = "SOCKET_CLOSED";
DisconnectReason["SOCKET_ERROR"] = "SOCKET_ERROR";
DisconnectReason["SOCKET_TIMEOUT"] = "SOCKET_TIMEOUT";
DisconnectReason["UNKNOWN_DISCONNECT"] = "UNKNOWN_DISCONNECT";
return DisconnectReason;
}({});
// Match with ProseMirror: https://prosemirror.net/docs/ref/#transform.ReplaceStep
// Match with ProseMirror: https://prosemirror.net/docs/ref/#transform.ReplaceAroundStep
// Match with ProseMirror: https://prosemirror.net/docs/ref/#transform.AttrStep
//Intersection: NCS custom step type config in adf-schema
//Unions
const telepointerColorStyle = (backgroundColor, textColor, index) => {
const {
colorMode
} = getGlobalTheme();
const backgroundStyle = colorMode === 'dark' ? `linear-gradient(to bottom, ${backgroundColor} -800000%, transparent 200000%)` : `linear-gradient(to bottom, ${backgroundColor} -850000%, transparent 150000%)`;
return `
&.color-${index} {
background: ${backgroundStyle};
&::after {
background-color: ${backgroundColor};
color: ${textColor};
border-color: ${backgroundColor};
}
}
`;
};
export const TELEPOINTER_DIM_CLASS = 'telepointer-dim';
export const TELEPOINTER_PULSE_CLASS = 'telepointer-pulse-animate';
export const TELEPOINTER_PULSE_DURING_TR_CLASS = 'telepointer-pulse-during-tr';
export const TELEPOINTER_PULSE_DURING_TR_DURATION_MS = 7500; // Keeping it longer so it'll be easier to spot during transactions
export const TELEPOINTER_DATA_SESSION_ID_ATTR = 'data-telepointer-sessionid';
// ED-22557: Safely convert to object styling
// Disable top: -14px since it is necessary to align to cursor
// eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage/preview, @atlaskit/design-system/no-css-tagged-template-expression, @atlaskit/ui-styling-standard/no-exported-styles -- Ignored via go/DSP-18766
export const telepointerStyle = css`
@keyframes pulseIn {
0%,
100% {
transform: scaleX(0);
opacity: 0;
}
10% {
transform: scaleX(1.4);
opacity: 1;
}
15%,
85% {
transform: scaleX(1);
opacity: 1;
}
}
@keyframes pulseOut {
0%,
90%,
100% {
transform: scaleX(1);
opacity: 1;
}
10%,
80% {
transform: scaleX(0);
opacity: 0;
}
}
@keyframes pulseInDuringTr {
0%,
95% {
transform: scaleX(1);
opacity: 1;
}
100% {
transform: scaleX(0);
opacity: 0;
}
}
@keyframes pulseOutDuringTr {
100% {
transform: scaleX(1);
opacity: 1;
}
0%,
90% {
transform: scaleX(0);
opacity: 0;
}
}
.ProseMirror .telepointer {
position: relative;
transition: opacity 200ms;
&.telepointer-selection:not(.inlineNodeView) {
line-height: 1.2;
pointer-events: none;
user-select: none;
}
&.telepointer-selection-badge {
.telepointer-initial,
.telepointer-fullname {
position: absolute;
display: block;
user-select: none;
white-space: pre;
top: -14px;
left: 0px;
font: ${"var(--ds-font-body-small, normal 400 12px/16px \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)"};
padding-left: ${"var(--ds-space-050, 4px)"};
padding-right: ${"var(--ds-space-050, 4px)"};
border-radius: 0 2px 2px 0;
}
.telepointer-initial {
opacity: 1;
transition: opacity 0.15s ease-out;
}
.telepointer-fullname {
opacity: 0;
transform: scaleX(0);
transform-origin: top left;
transition:
transform 0.15s ease-out,
opacity 0.15s ease-out;
}
}
&.${TELEPOINTER_PULSE_CLASS} {
.telepointer-initial {
animation: pulseOut 2.5s ease-in-out;
}
.telepointer-fullname {
animation: pulseIn 2.5s ease-in-out;
}
}
&.${TELEPOINTER_PULSE_DURING_TR_CLASS} {
.telepointer-initial {
animation: pulseOutDuringTr ${TELEPOINTER_PULSE_DURING_TR_DURATION_MS}ms ease-in-out;
}
.telepointer-fullname {
animation: pulseInDuringTr ${TELEPOINTER_PULSE_DURING_TR_DURATION_MS}ms ease-in-out;
}
}
&:hover {
.telepointer-initial {
opacity: 0;
transition-delay: 150ms;
}
.telepointer-fullname {
transform: scaleX(1);
opacity: 1;
z-index: 1;
}
}
&.${TELEPOINTER_DIM_CLASS} {
opacity: 0.2;
}
${participantColors.map((participantColor, index) => telepointerColorStyle(participantColor.backgroundColor, participantColor.textColor, index))};
}
`;
const tintKey = 'collab:isDirtyTransaction';
export const isDirtyTransaction = tr => {
return Boolean(tr.getMeta(tintKey));
};
/*
* This function is used to mark which commands that are dispatching
* unnecessary changes on Editor.
*/
export const tintDirtyTransaction = tr => {
tr.setMeta(tintKey, true);
};