@atlaskit/editor-plugin-hyperlink
Version:
Hyperlink plugin for @atlaskit/editor-core
316 lines (312 loc) • 19.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.HyperlinkAddToolbarWithState = HyperlinkAddToolbarWithState;
exports.getToolbarConfig = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _react = _interopRequireWildcard(require("react"));
var _adfSchema = require("@atlaskit/adf-schema");
var _analytics = require("@atlaskit/editor-common/analytics");
var _card = require("@atlaskit/editor-common/card");
var _hooks = require("@atlaskit/editor-common/hooks");
var _link2 = require("@atlaskit/editor-common/link");
var _messages = require("@atlaskit/editor-common/messages");
var _toolbarFlagCheck = require("@atlaskit/editor-common/toolbar-flag-check");
var _ui = require("@atlaskit/editor-common/ui");
var _userIntent = require("@atlaskit/editor-common/user-intent");
var _utils = require("@atlaskit/editor-common/utils");
var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity");
var _state = require("@atlaskit/editor-prosemirror/state");
var _utils2 = require("@atlaskit/editor-prosemirror/utils");
var _edit = _interopRequireDefault(require("@atlaskit/icon/core/edit"));
var _linkBroken = _interopRequireDefault(require("@atlaskit/icon/core/link-broken"));
var _linkExternal = _interopRequireDefault(require("@atlaskit/icon/core/link-external"));
var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
var _commands = require("../../editor-commands/commands");
var _main = require("../../pm-plugins/main");
var _toolbarButtons = require("../../pm-plugins/toolbar-buttons");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
/* type guard for edit links */
function isEditLink(linkMark) {
return linkMark.pos !== undefined;
}
var dispatchAnalytics = function dispatchAnalytics(dispatch, state, analyticsBuilder, editorAnalyticsApi) {
if (dispatch) {
var tr = state.tr;
editorAnalyticsApi === null || editorAnalyticsApi === void 0 || editorAnalyticsApi.attachAnalyticsEvent(analyticsBuilder(_analytics.ACTION_SUBJECT_ID.HYPERLINK))(tr);
dispatch(tr);
}
};
var visitHyperlink = function visitHyperlink(editorAnalyticsApi) {
return function (state, dispatch) {
dispatchAnalytics(dispatch, state, _analytics.buildVisitedLinkPayload, editorAnalyticsApi);
return true;
};
};
function getLinkText(activeLinkMark, state) {
if (!activeLinkMark.node) {
return undefined;
}
var textToUrl = (0, _utils.normalizeUrl)(activeLinkMark.node.text);
var linkMark = activeLinkMark.node.marks.find(function (mark) {
return mark.type === state.schema.marks.link;
});
var linkHref = linkMark && linkMark.attrs.href;
if (textToUrl === linkHref) {
return undefined;
}
return activeLinkMark.node.text;
}
var selector = function selector(states) {
var _states$hyperlinkStat, _states$hyperlinkStat2, _states$hyperlinkStat3;
return {
timesViewed: (_states$hyperlinkStat = states.hyperlinkState) === null || _states$hyperlinkStat === void 0 ? void 0 : _states$hyperlinkStat.timesViewed,
inputMethod: (_states$hyperlinkStat2 = states.hyperlinkState) === null || _states$hyperlinkStat2 === void 0 ? void 0 : _states$hyperlinkStat2.inputMethod,
searchSessionId: (_states$hyperlinkStat3 = states.hyperlinkState) === null || _states$hyperlinkStat3 === void 0 ? void 0 : _states$hyperlinkStat3.searchSessionId
};
};
function HyperlinkAddToolbarWithState(_ref) {
var _pluginInjectionApi$c;
var _ref$linkPickerOption = _ref.linkPickerOptions,
linkPickerOptions = _ref$linkPickerOption === void 0 ? {} : _ref$linkPickerOption,
onSubmit = _ref.onSubmit,
displayText = _ref.displayText,
displayUrl = _ref.displayUrl,
providerFactory = _ref.providerFactory,
view = _ref.view,
onCancel = _ref.onCancel,
invokeMethod = _ref.invokeMethod,
lpLinkPicker = _ref.lpLinkPicker,
onClose = _ref.onClose,
onEscapeCallback = _ref.onEscapeCallback,
onClickAwayCallback = _ref.onClickAwayCallback,
pluginInjectionApi = _ref.pluginInjectionApi;
var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(pluginInjectionApi, ['hyperlink'], selector),
timesViewed = _useSharedPluginState.timesViewed,
inputMethod = _useSharedPluginState.inputMethod,
searchSessionId = _useSharedPluginState.searchSessionId;
// This is constant rather than dynamic - because if someone's already got a hyperlink toolbar open,
// we don't want to dynamically change it on them as this would cause data loss if they've already
// started typing in the fields.
var isOffline = (0, _react.useRef)((0, _editorPluginConnectivity.isOfflineMode)(pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c = pluginInjectionApi.connectivity) === null || _pluginInjectionApi$c === void 0 || (_pluginInjectionApi$c = _pluginInjectionApi$c.sharedState.currentState()) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.mode));
return /*#__PURE__*/_react.default.createElement(_link2.HyperlinkAddToolbar, {
linkPickerOptions: linkPickerOptions,
onSubmit: onSubmit,
displayText: displayText,
displayUrl: displayUrl,
providerFactory: providerFactory,
view: view,
onCancel: onCancel,
invokeMethod: invokeMethod,
lpLinkPicker: lpLinkPicker,
onClose: onClose,
onEscapeCallback: onEscapeCallback,
onClickAwayCallback: onClickAwayCallback,
timesViewed: timesViewed,
inputMethod: inputMethod,
searchSessionId: searchSessionId,
isOffline: isOffline.current
});
}
var getToolbarConfig = exports.getToolbarConfig = function getToolbarConfig(options, pluginInjectionApi) {
return function (state, intl, providerFactory) {
var _pluginInjectionApi$c2, _pluginInjectionApi$a, _options$lpLinkPicker;
if (options.disableFloatingToolbar) {
return;
}
var isToolbarAIFCEnabled = Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.toolbar);
var linkState = _main.stateKey.getState(state);
var activeLinkMark = linkState === null || linkState === void 0 ? void 0 : linkState.activeLinkMark;
// If range selection, we don't show hyperlink floating toolbar.
// Text Formattting toolbar is shown instaed.
if (state.selection instanceof _state.TextSelection && state.selection.to !== state.selection.from && (activeLinkMark === null || activeLinkMark === void 0 ? void 0 : activeLinkMark.type) === 'EDIT' && (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1')) {
return;
}
var formatMessage = intl.formatMessage;
var editorCardActions = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c2 = pluginInjectionApi.card) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.actions;
var editorAnalyticsApi = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions;
var lpLinkPicker = (_options$lpLinkPicker = options.lpLinkPicker) !== null && _options$lpLinkPicker !== void 0 ? _options$lpLinkPicker : true;
if (activeLinkMark) {
var hyperLinkToolbar = {
title: 'Hyperlink floating controls',
nodeType: [state.schema.nodes.text, state.schema.nodes.paragraph, state.schema.nodes.heading, state.schema.nodes.taskItem, state.schema.nodes.decisionItem, state.schema.nodes.caption].filter(function (nodeType) {
return !!nodeType;
}),
// Use only the node types existing in the schema ED-6745
align: 'left',
className: activeLinkMark.type.match('INSERT|EDIT_INSERTED') ? 'hyperlink-floating-toolbar' : '',
// getDomRef by default uses view.state.selection.from to position the toolbar.
// However, when the user clicks in right after the link the view.state.selection.from references to the dom after the selection.
// So instead we want to use the activeLinkMark.pos which has been calculated as the position before the click so that would be the link node
getDomRef: activeLinkMark && (activeLinkMark.type === 'EDIT_INSERTED' || activeLinkMark.type === 'EDIT') && (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') ? function (view) {
var domRef = (0, _utils2.findDomRefAtPos)(activeLinkMark.pos, view.domAtPos.bind(view));
return domRef instanceof HTMLElement ? domRef : undefined;
} : undefined
};
switch (activeLinkMark.type) {
case 'EDIT':
{
var _pluginInjectionApi$c3, _cardActions$getStart, _cardActions$getEndin;
var pos = activeLinkMark.pos,
node = activeLinkMark.node;
var linkMark = node.marks.filter(function (mark) {
return mark.type === state.schema.marks.link;
});
var link = linkMark[0] && linkMark[0].attrs.href;
var isValidUrl = (0, _adfSchema.isSafeUrl)(link);
var labelOpenLink = formatMessage(isValidUrl ? _messages.linkMessages.openLink : _messages.linkToolbarMessages.unableToOpenLink);
// TODO: ED-14403 - investigate why these are not translating?
var labelUnlink = formatMessage(_messages.linkToolbarMessages.unlink);
var editLink = formatMessage(_messages.linkToolbarMessages.editLink);
var metadata = {
url: link,
title: ''
};
if (activeLinkMark.node.text) {
metadata.title = activeLinkMark.node.text;
}
var areAnyNewToolbarFlagsEnabled = (0, _toolbarFlagCheck.areToolbarFlagsEnabled)(Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.toolbar));
var cardActions = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c3 = pluginInjectionApi.card) === null || _pluginInjectionApi$c3 === void 0 ? void 0 : _pluginInjectionApi$c3.actions;
var startingToolbarItems = (_cardActions$getStart = cardActions === null || cardActions === void 0 ? void 0 : cardActions.getStartingToolbarItems(intl, link, (0, _commands.editInsertedLink)(editorAnalyticsApi), metadata, areAnyNewToolbarFlagsEnabled ? state : undefined)) !== null && _cardActions$getStart !== void 0 ? _cardActions$getStart : [{
id: 'editor.link.edit',
testId: 'editor.link.edit',
type: 'button',
onClick: (0, _commands.editInsertedLink)(editorAnalyticsApi),
title: editLink,
showTitle: areAnyNewToolbarFlagsEnabled ? false : true,
metadata: metadata,
icon: areAnyNewToolbarFlagsEnabled ? _edit.default : undefined
}, {
type: 'separator'
}];
var openLinkButton = {
id: 'editor.link.openLink',
testId: 'editor.link.openLink',
type: 'button',
disabled: !isValidUrl,
target: '_blank',
href: isValidUrl ? link : undefined,
onClick: visitHyperlink(editorAnalyticsApi),
title: labelOpenLink,
icon: _linkExternal.default,
className: 'hyperlink-open-link',
metadata: metadata,
tabIndex: null
};
var unlinkButton = {
id: 'editor.link.unlink',
testId: 'editor.link.unlink',
type: 'button',
onClick: (0, _card.commandWithMetadata)((0, _commands.removeLink)(pos, editorAnalyticsApi), {
inputMethod: _analytics.INPUT_METHOD.FLOATING_TB
}),
title: labelUnlink,
icon: _linkBroken.default,
tabIndex: null
};
var items = [].concat((0, _toConsumableArray2.default)(startingToolbarItems), (0, _toConsumableArray2.default)(areAnyNewToolbarFlagsEnabled ? [unlinkButton, {
type: 'separator',
fullHeight: true
}, openLinkButton, {
type: 'separator',
fullHeight: true
}] : [openLinkButton, {
type: 'separator'
}, unlinkButton, {
type: 'separator'
}]), [{
type: 'copy-button',
items: [{
state: state,
formatMessage: formatMessage,
markType: state.schema.marks.link
}]
}], (0, _toConsumableArray2.default)((_cardActions$getEndin = cardActions === null || cardActions === void 0 ? void 0 : cardActions.getEndingToolbarItems(intl, link)) !== null && _cardActions$getEndin !== void 0 ? _cardActions$getEndin : []));
return _objectSpread(_objectSpread({}, hyperLinkToolbar), {}, {
height: 32,
width: 250,
items: items,
scrollable: true
});
}
case 'EDIT_INSERTED':
case 'INSERT':
{
var _link;
if (isEditLink(activeLinkMark) && activeLinkMark.node) {
var _linkMark = activeLinkMark.node.marks.filter(function (mark) {
return mark.type === state.schema.marks.link;
});
_link = _linkMark[0] && _linkMark[0].attrs.href;
}
var displayText = isEditLink(activeLinkMark) ? getLinkText(activeLinkMark, state) : linkState.activeText;
var popupHeight = lpLinkPicker ? _ui.LINKPICKER_HEIGHT_IN_PX : _ui.RECENT_SEARCH_HEIGHT_IN_PX;
// Removing popupWidth to ensure that we the popup always positions setting positon left instead of flipping to position right
// inside of a narrow space like Preview panel
var popupWidth = !lpLinkPicker ? undefined : _ui.RECENT_SEARCH_WIDTH_IN_PX;
return _objectSpread(_objectSpread({}, hyperLinkToolbar), {}, {
preventPopupOverflow: true,
height: popupHeight,
width: popupWidth,
items: [{
type: 'custom',
fallback: [],
disableArrowNavigation: true,
render: function render(view, idx) {
if (!view) {
return null;
}
var Toolbar = /*#__PURE__*/_react.default.createElement(HyperlinkAddToolbarWithState, {
pluginInjectionApi: pluginInjectionApi,
view: view,
key: idx,
linkPickerOptions: options === null || options === void 0 ? void 0 : options.linkPicker,
lpLinkPicker: lpLinkPicker,
displayUrl: _link,
displayText: displayText || '',
providerFactory: providerFactory
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onCancel: function onCancel() {
return view.focus();
},
onEscapeCallback: (0, _commands.onEscapeCallback)(editorCardActions),
onClickAwayCallback: _commands.onClickAwayCallback
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onSubmit: function onSubmit(href) {
var _toolbarKey$getState$, _toolbarKey$getState;
var title = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
var displayText = arguments.length > 2 ? arguments[2] : undefined;
var inputMethod = arguments.length > 3 ? arguments[3] : undefined;
var analytic = arguments.length > 4 ? arguments[4] : undefined;
var isEdit = isEditLink(activeLinkMark);
var action = isEdit ? _analytics.ACTION.UPDATED : _analytics.ACTION.INSERTED;
var skipAnalytics = (_toolbarKey$getState$ = (_toolbarKey$getState = _toolbarButtons.toolbarKey.getState(state)) === null || _toolbarKey$getState === void 0 ? void 0 : _toolbarKey$getState.skipAnalytics) !== null && _toolbarKey$getState$ !== void 0 ? _toolbarKey$getState$ : false;
var command = isEdit ? (0, _card.commandWithMetadata)((0, _commands.updateLink)(href, displayText || title, activeLinkMark.pos), {
action: action,
inputMethod: inputMethod,
sourceEvent: analytic
}) : (0, _commands.insertLinkWithAnalytics)(inputMethod, activeLinkMark.from, activeLinkMark.to, href, editorCardActions, editorAnalyticsApi, title, displayText, skipAnalytics, analytic);
command(view.state, view.dispatch, view);
view.focus();
}
});
return isToolbarAIFCEnabled ? /*#__PURE__*/_react.default.createElement(_userIntent.UserIntentPopupWrapper, {
api: pluginInjectionApi
}, Toolbar) : Toolbar;
}
}]
});
}
}
}
return;
};
};