[WebApp][MM-48061]: Allow CTRL/CMD + K to insert link formatting when text is selected (#22671)
* add isTextSelectedInPostOrReply util * apply util * add test * format selected text as a markdown hyperlink if the clipboard contains a URL * fix lint and dependency --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
3dcc32bbdc
Коммит
8e6a5f6ffc
@@ -1603,6 +1603,7 @@ describe('components/AdvancedCreateComment', () => {
|
|||||||
(instance) => instance.find(AdvanceTextEditor),
|
(instance) => instance.find(AdvanceTextEditor),
|
||||||
(instance) => instance.state().draft.message,
|
(instance) => instance.state().draft.message,
|
||||||
false,
|
false,
|
||||||
|
'reply_textbox',
|
||||||
);
|
);
|
||||||
|
|
||||||
it('should blur when ESCAPE is pressed', () => {
|
it('should blur when ESCAPE is pressed', () => {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import React from 'react';
|
|||||||
|
|
||||||
import {ModalData} from 'types/actions.js';
|
import {ModalData} from 'types/actions.js';
|
||||||
|
|
||||||
|
import {isNil} from 'lodash';
|
||||||
|
|
||||||
import {sortFileInfos} from 'mattermost-redux/utils/file_utils';
|
import {sortFileInfos} from 'mattermost-redux/utils/file_utils';
|
||||||
|
|
||||||
import * as GlobalActions from 'actions/global_actions';
|
import * as GlobalActions from 'actions/global_actions';
|
||||||
@@ -25,7 +27,7 @@ import {
|
|||||||
groupsMentionedInText,
|
groupsMentionedInText,
|
||||||
mentionsMinusSpecialMentionsInText,
|
mentionsMinusSpecialMentionsInText,
|
||||||
} from 'utils/post_utils';
|
} from 'utils/post_utils';
|
||||||
import {getTable, hasHtmlLink, formatMarkdownMessage, isGitHubCodeBlock, formatGithubCodePaste} from 'utils/paste';
|
import {getTable, hasHtmlLink, formatMarkdownMessage, isGitHubCodeBlock, formatGithubCodePaste, isHttpProtocol, isHttpsProtocol} from 'utils/paste';
|
||||||
|
|
||||||
import NotifyConfirmModal from 'components/notify_confirm_modal';
|
import NotifyConfirmModal from 'components/notify_confirm_modal';
|
||||||
import {FileUpload as FileUploadClass} from 'components/file_upload/file_upload';
|
import {FileUpload as FileUploadClass} from 'components/file_upload/file_upload';
|
||||||
@@ -40,6 +42,8 @@ import {ServerError} from '@mattermost/types/errors';
|
|||||||
import {FileInfo} from '@mattermost/types/files';
|
import {FileInfo} from '@mattermost/types/files';
|
||||||
import EmojiMap from 'utils/emoji_map';
|
import EmojiMap from 'utils/emoji_map';
|
||||||
import {
|
import {
|
||||||
|
applyLinkMarkdown,
|
||||||
|
ApplyLinkMarkdownOptions,
|
||||||
applyMarkdown,
|
applyMarkdown,
|
||||||
ApplyMarkdownOptions,
|
ApplyMarkdownOptions,
|
||||||
} from 'utils/markdown/apply_markdown';
|
} from 'utils/markdown/apply_markdown';
|
||||||
@@ -425,15 +429,37 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {clipboardData} = e;
|
const {clipboardData} = e;
|
||||||
|
|
||||||
|
const target = e.target as TextboxElement;
|
||||||
|
|
||||||
|
const {selectionStart, selectionEnd, value} = target;
|
||||||
|
|
||||||
|
const hasSelection = !isNil(selectionStart) && !isNil(selectionEnd) && selectionStart < selectionEnd;
|
||||||
|
const clipboardText = clipboardData.getData('text/plain');
|
||||||
|
const isClipboardTextURL = isHttpProtocol(clipboardText) || isHttpsProtocol(clipboardText);
|
||||||
|
const shouldApplyLinkMarkdown = hasSelection && isClipboardTextURL;
|
||||||
|
|
||||||
const hasLinks = hasHtmlLink(clipboardData);
|
const hasLinks = hasHtmlLink(clipboardData);
|
||||||
let table = getTable(clipboardData);
|
let table = getTable(clipboardData);
|
||||||
if (!table && !hasLinks) {
|
if (!table && !hasLinks && !shouldApplyLinkMarkdown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
table = table as HTMLTableElement;
|
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (shouldApplyLinkMarkdown) {
|
||||||
|
this.applyLinkMarkdownWhenPaste({
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd,
|
||||||
|
message: value,
|
||||||
|
url: clipboardText,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
table = table as HTMLTableElement;
|
||||||
|
|
||||||
const draft = this.state.draft!;
|
const draft = this.state.draft!;
|
||||||
let message = draft.message;
|
let message = draft.message;
|
||||||
|
|
||||||
@@ -912,6 +938,15 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
|
|||||||
selectionEnd,
|
selectionEnd,
|
||||||
message: value,
|
message: value,
|
||||||
});
|
});
|
||||||
|
} else if (Utils.isTextSelectedInPostOrReply(e) && Keyboard.isKeyPressed(e, KeyCodes.K)) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
this.applyMarkdown({
|
||||||
|
markdownMode: 'link',
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd,
|
||||||
|
message: value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (ctrlAltCombo) {
|
} else if (ctrlAltCombo) {
|
||||||
if (Keyboard.isKeyPressed(e, KeyCodes.K)) {
|
if (Keyboard.isKeyPressed(e, KeyCodes.K)) {
|
||||||
@@ -1010,6 +1045,25 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
applyLinkMarkdownWhenPaste = (params: ApplyLinkMarkdownOptions) => {
|
||||||
|
const res = applyLinkMarkdown(params);
|
||||||
|
|
||||||
|
const draft = this.state.draft!;
|
||||||
|
const modifiedDraft = {
|
||||||
|
...draft,
|
||||||
|
message: res.message,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleDraftChange(modifiedDraft);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
draft: modifiedDraft,
|
||||||
|
}, () => {
|
||||||
|
const textbox = this.textboxRef.current?.getInputBox();
|
||||||
|
Utils.setSelectionRange(textbox, res.selectionEnd + 1, res.selectionEnd + 1);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
handleFileUploadChange = () => {
|
handleFileUploadChange = () => {
|
||||||
this.isDraftEdited = true;
|
this.isDraftEdited = true;
|
||||||
this.focusTextbox();
|
this.focusTextbox();
|
||||||
|
|||||||
@@ -1581,6 +1581,7 @@ describe('components/advanced_create_post', () => {
|
|||||||
(instance) => instance.find(AdvanceTextEditor),
|
(instance) => instance.find(AdvanceTextEditor),
|
||||||
(instance) => instance.state().message,
|
(instance) => instance.state().message,
|
||||||
false,
|
false,
|
||||||
|
'post_textbox',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import classNames from 'classnames';
|
|||||||
|
|
||||||
import {AlertCircleOutlineIcon, CheckCircleOutlineIcon} from '@mattermost/compass-icons/components';
|
import {AlertCircleOutlineIcon, CheckCircleOutlineIcon} from '@mattermost/compass-icons/components';
|
||||||
|
|
||||||
|
import {isNil} from 'lodash';
|
||||||
|
|
||||||
import {Posts} from 'mattermost-redux/constants';
|
import {Posts} from 'mattermost-redux/constants';
|
||||||
import {sortFileInfos} from 'mattermost-redux/utils/file_utils';
|
import {sortFileInfos} from 'mattermost-redux/utils/file_utils';
|
||||||
import {ActionResult} from 'mattermost-redux/types/actions';
|
import {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
@@ -33,11 +35,11 @@ import {
|
|||||||
groupsMentionedInText,
|
groupsMentionedInText,
|
||||||
mentionsMinusSpecialMentionsInText,
|
mentionsMinusSpecialMentionsInText,
|
||||||
} from 'utils/post_utils';
|
} from 'utils/post_utils';
|
||||||
import {getTable, hasHtmlLink, formatMarkdownMessage, formatGithubCodePaste, isGitHubCodeBlock} from 'utils/paste';
|
import {getTable, hasHtmlLink, formatMarkdownMessage, formatGithubCodePaste, isGitHubCodeBlock, isHttpProtocol, isHttpsProtocol} from 'utils/paste';
|
||||||
import * as UserAgent from 'utils/user_agent';
|
import * as UserAgent from 'utils/user_agent';
|
||||||
import * as Utils from 'utils/utils';
|
import * as Utils from 'utils/utils';
|
||||||
import EmojiMap from 'utils/emoji_map';
|
import EmojiMap from 'utils/emoji_map';
|
||||||
import {applyMarkdown, ApplyMarkdownOptions} from 'utils/markdown/apply_markdown';
|
import {applyLinkMarkdown, ApplyLinkMarkdownOptions, applyMarkdown, ApplyMarkdownOptions} from 'utils/markdown/apply_markdown';
|
||||||
|
|
||||||
import Tooltip from 'components/tooltip';
|
import Tooltip from 'components/tooltip';
|
||||||
import OverlayTrigger from 'components/overlay_trigger';
|
import OverlayTrigger from 'components/overlay_trigger';
|
||||||
@@ -911,15 +913,37 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
|
|||||||
|
|
||||||
const {clipboardData} = e;
|
const {clipboardData} = e;
|
||||||
|
|
||||||
|
const target = e.target as TextboxElement;
|
||||||
|
|
||||||
|
const {selectionStart, selectionEnd, value} = target;
|
||||||
|
|
||||||
|
const hasSelection = !isNil(selectionStart) && !isNil(selectionEnd) && selectionStart < selectionEnd;
|
||||||
|
const clipboardText = clipboardData.getData('text/plain');
|
||||||
|
const isClipboardTextURL = isHttpProtocol(clipboardText) || isHttpsProtocol(clipboardText);
|
||||||
|
const shouldApplyLinkMarkdown = hasSelection && isClipboardTextURL;
|
||||||
|
|
||||||
const hasLinks = hasHtmlLink(clipboardData);
|
const hasLinks = hasHtmlLink(clipboardData);
|
||||||
let table = getTable(clipboardData);
|
let table = getTable(clipboardData);
|
||||||
if (!table && !hasLinks) {
|
|
||||||
|
if (!table && !hasLinks && !shouldApplyLinkMarkdown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
table = table as HTMLTableElement;
|
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (shouldApplyLinkMarkdown) {
|
||||||
|
this.applyLinkMarkdownWhenPaste({
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd,
|
||||||
|
message: value,
|
||||||
|
url: clipboardText,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
table = table as HTMLTableElement;
|
||||||
|
|
||||||
const message = this.state.message;
|
const message = this.state.message;
|
||||||
if (table && isGitHubCodeBlock(table.className)) {
|
if (table && isGitHubCodeBlock(table.className)) {
|
||||||
const selectionStart = (e.target as any).selectionStart;
|
const selectionStart = (e.target as any).selectionStart;
|
||||||
@@ -1210,6 +1234,15 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
|
|||||||
selectionEnd,
|
selectionEnd,
|
||||||
message: value,
|
message: value,
|
||||||
});
|
});
|
||||||
|
} else if (Utils.isTextSelectedInPostOrReply(e) && Keyboard.isKeyPressed(e, KeyCodes.K)) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
this.applyMarkdown({
|
||||||
|
markdownMode: 'link',
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd,
|
||||||
|
message: value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (ctrlAltCombo) {
|
} else if (ctrlAltCombo) {
|
||||||
if (Keyboard.isKeyPressed(e, KeyCodes.K)) {
|
if (Keyboard.isKeyPressed(e, KeyCodes.K)) {
|
||||||
@@ -1362,6 +1395,24 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
applyLinkMarkdownWhenPaste = (params: ApplyLinkMarkdownOptions) => {
|
||||||
|
const res = applyLinkMarkdown(params);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
message: res.message,
|
||||||
|
}, () => {
|
||||||
|
const textbox = this.textboxRef.current?.getInputBox();
|
||||||
|
Utils.setSelectionRange(textbox, res.selectionEnd + 1, res.selectionEnd + 1);
|
||||||
|
|
||||||
|
const draft = {
|
||||||
|
...this.props.draft,
|
||||||
|
message: this.state.message,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleDraftChange(draft);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
reactToLastMessage = (e: KeyboardEvent) => {
|
reactToLastMessage = (e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default class ChannelNavigator extends React.PureComponent<Props> {
|
|||||||
|
|
||||||
handleQuickSwitchKeyPress = (e: KeyboardEvent) => {
|
handleQuickSwitchKeyPress = (e: KeyboardEvent) => {
|
||||||
if (Keyboard.cmdOrCtrlPressed(e) && !e.shiftKey && Keyboard.isKeyPressed(e, Constants.KeyCodes.K)) {
|
if (Keyboard.cmdOrCtrlPressed(e) && !e.shiftKey && Keyboard.isKeyPressed(e, Constants.KeyCodes.K)) {
|
||||||
if (!e.altKey) {
|
if (!e.altKey && !Utils.isTextSelectedInPostOrReply(e)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.toggleQuickSwitchModal();
|
this.toggleQuickSwitchModal();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function makeSelectionEvent(input, start, end) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeMarkdownHotkeyEvent(input, start, end, keycode, altKey = false) {
|
function makeMarkdownHotkeyEvent(input, start, end, keycode, altKey = false, targetId = 'root') {
|
||||||
return {
|
return {
|
||||||
preventDefault: jest.fn(),
|
preventDefault: jest.fn(),
|
||||||
stopPropagation: jest.fn(),
|
stopPropagation: jest.fn(),
|
||||||
@@ -42,6 +42,7 @@ function makeMarkdownHotkeyEvent(input, start, end, keycode, altKey = false) {
|
|||||||
selectionStart: start,
|
selectionStart: start,
|
||||||
selectionEnd: end,
|
selectionEnd: end,
|
||||||
value: input,
|
value: input,
|
||||||
|
id: targetId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -66,6 +67,10 @@ export function makeItalicHotkeyEvent(input, start, end) {
|
|||||||
return makeMarkdownHotkeyEvent(input, start, end, Constants.KeyCodes.I);
|
return makeMarkdownHotkeyEvent(input, start, end, Constants.KeyCodes.I);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeLinkHotKeyWithoutAltKeyEvent(input, start, end, targetId) {
|
||||||
|
return makeMarkdownHotkeyEvent(input, start, end, Constants.KeyCodes.K, false, targetId);
|
||||||
|
}
|
||||||
|
|
||||||
function makeLinkHotKeyEvent(input, start, end) {
|
function makeLinkHotKeyEvent(input, start, end) {
|
||||||
return makeMarkdownHotkeyEvent(input, start, end, Constants.KeyCodes.K, true);
|
return makeMarkdownHotkeyEvent(input, start, end, Constants.KeyCodes.K, true);
|
||||||
}
|
}
|
||||||
@@ -77,7 +82,7 @@ function makeLinkHotKeyEvent(input, start, end) {
|
|||||||
* @param {function} getValue - single parameter for the React Component instance
|
* @param {function} getValue - single parameter for the React Component instance
|
||||||
* NOTE: runs Jest tests
|
* NOTE: runs Jest tests
|
||||||
*/
|
*/
|
||||||
export function testComponentForMarkdownHotkeys(generateInstance, initRefs, find, getValue, intlInjected = true) {
|
export function testComponentForMarkdownHotkeys(generateInstance, initRefs, find, getValue, intlInjected = true, targetId) {
|
||||||
const shallowRender = intlInjected ? shallowWithIntl : shallow;
|
const shallowRender = intlInjected ? shallowWithIntl : shallow;
|
||||||
test('component adds bold markdown', () => {
|
test('component adds bold markdown', () => {
|
||||||
// "Fafda" is selected with ctrl + B hotkey
|
// "Fafda" is selected with ctrl + B hotkey
|
||||||
@@ -143,6 +148,29 @@ export function testComponentForMarkdownHotkeys(generateInstance, initRefs, find
|
|||||||
expect(setSelectionRange).toHaveBeenCalled();
|
expect(setSelectionRange).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('component adds link markdown with hitting Ctrl + K when something is selected and event target has specific targetId', () => {
|
||||||
|
// "Fafda" is selected with ctrl + K hotkey
|
||||||
|
const input = 'Jalebi Fafda & Sambharo';
|
||||||
|
const e = makeLinkHotKeyWithoutAltKeyEvent(input, 7, 12, targetId);
|
||||||
|
|
||||||
|
const instance = shallowRender(generateInstance(input));
|
||||||
|
|
||||||
|
let selectionStart = -1;
|
||||||
|
let selectionEnd = -1;
|
||||||
|
const setSelectionRange = jest.fn((start, end) => {
|
||||||
|
selectionStart = start;
|
||||||
|
selectionEnd = end;
|
||||||
|
});
|
||||||
|
initRefs(instance, setSelectionRange);
|
||||||
|
|
||||||
|
find(instance).props().onKeyDown?.(e);
|
||||||
|
find(instance).props().handleKeyDown?.(e);
|
||||||
|
expect(getValue(instance)).toBe('Jalebi [Fafda](url) & Sambharo');
|
||||||
|
expect(setSelectionRange).toHaveBeenCalled();
|
||||||
|
expect(selectionStart).toBe(15);
|
||||||
|
expect(selectionEnd).toBe(18);
|
||||||
|
});
|
||||||
|
|
||||||
test('component adds link markdown when something is selected', () => {
|
test('component adds link markdown when something is selected', () => {
|
||||||
// "Fafda" is selected with ctrl + alt + K hotkey
|
// "Fafda" is selected with ctrl + alt + K hotkey
|
||||||
const input = 'Jalebi Fafda & Sambharo';
|
const input = 'Jalebi Fafda & Sambharo';
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ type ApplySpecificMarkdownOptions = ApplyMarkdownReturnValue & {
|
|||||||
delimiter?: string;
|
delimiter?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ApplyLinkMarkdownOptions = ApplySpecificMarkdownOptions & {
|
||||||
|
url?: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export function applyMarkdown(options: ApplyMarkdownOptions): ApplyMarkdownReturnValue {
|
export function applyMarkdown(options: ApplyMarkdownOptions): ApplyMarkdownReturnValue {
|
||||||
const {selectionEnd, selectionStart, message, markdownMode} = options;
|
const {selectionEnd, selectionStart, message, markdownMode} = options;
|
||||||
|
|
||||||
@@ -396,14 +401,14 @@ function applyBoldItalicMarkdown({selectionEnd, selectionStart, message, markdow
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyLinkMarkdown({selectionEnd, selectionStart, message}: ApplySpecificMarkdownOptions) {
|
export function applyLinkMarkdown({selectionEnd, selectionStart, message, url = 'url'}: ApplyLinkMarkdownOptions) {
|
||||||
// <prefix> <selection> <suffix>
|
// <prefix> <selection> <suffix>
|
||||||
const prefix = message.slice(0, selectionStart);
|
const prefix = message.slice(0, selectionStart);
|
||||||
const selection = message.slice(selectionStart, selectionEnd);
|
const selection = message.slice(selectionStart, selectionEnd);
|
||||||
const suffix = message.slice(selectionEnd);
|
const suffix = message.slice(selectionEnd);
|
||||||
|
|
||||||
const delimiterStart = '[';
|
const delimiterStart = '[';
|
||||||
const delimiterEnd = '](url)';
|
const delimiterEnd = `](${url})`;
|
||||||
|
|
||||||
// Does the selection have link markdown?
|
// Does the selection have link markdown?
|
||||||
const hasMarkdown = prefix.endsWith(delimiterStart) && suffix.startsWith(delimiterEnd);
|
const hasMarkdown = prefix.endsWith(delimiterStart) && suffix.startsWith(delimiterEnd);
|
||||||
@@ -431,7 +436,7 @@ function applyLinkMarkdown({selectionEnd, selectionStart, message}: ApplySpecifi
|
|||||||
// there is something selected; put markdown around it and preserve selection
|
// there is something selected; put markdown around it and preserve selection
|
||||||
newValue = prefix + delimiterStart + selection + delimiterEnd + suffix;
|
newValue = prefix + delimiterStart + selection + delimiterEnd + suffix;
|
||||||
newStart = selectionEnd + urlShift;
|
newStart = selectionEnd + urlShift;
|
||||||
newEnd = newStart + urlShift;
|
newEnd = newStart + url.length;
|
||||||
} else {
|
} else {
|
||||||
// nothing is selected
|
// nothing is selected
|
||||||
const spaceBefore = prefix.charAt(prefix.length - 1) === ' ';
|
const spaceBefore = prefix.charAt(prefix.length - 1) === ' ';
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ export function isGitHubCodeBlock(tableClassName: string): boolean {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isHttpProtocol(url: string): boolean {
|
||||||
|
return url.startsWith('http://');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isHttpsProtocol(url: string): boolean {
|
||||||
|
return url.startsWith('https://');
|
||||||
|
}
|
||||||
|
|
||||||
function isHeaderlessTable(table: HTMLTableElement): boolean {
|
function isHeaderlessTable(table: HTMLTableElement): boolean {
|
||||||
return table.querySelectorAll('th').length === 0;
|
return table.querySelectorAll('th').length === 0;
|
||||||
}
|
}
|
||||||
@@ -85,9 +93,7 @@ export function formatMarkdownMessage(clipboardData: DataTransfer, message?: str
|
|||||||
|
|
||||||
export function formatGithubCodePaste({message, clipboardData, selectionStart, selectionEnd}: FormatCodeOptions): {formattedMessage: string; formattedCodeBlock: string} {
|
export function formatGithubCodePaste({message, clipboardData, selectionStart, selectionEnd}: FormatCodeOptions): {formattedMessage: string; formattedCodeBlock: string} {
|
||||||
const textSelected = selectionStart !== selectionEnd;
|
const textSelected = selectionStart !== selectionEnd;
|
||||||
const {firstPiece, lastPiece} = textSelected ?
|
const {firstPiece, lastPiece} = textSelected ? splitMessageBasedOnTextSelection(selectionStart ?? message.length, selectionEnd ?? message.length, message) : splitMessageBasedOnCaretPosition(selectionStart ?? message.length, message);
|
||||||
splitMessageBasedOnTextSelection(selectionStart ?? message.length, selectionEnd ?? message.length, message) :
|
|
||||||
splitMessageBasedOnCaretPosition(selectionStart ?? message.length, message);
|
|
||||||
|
|
||||||
// Add new lines if content exists before or after the cursor.
|
// Add new lines if content exists before or after the cursor.
|
||||||
const requireStartLF = firstPiece === '' ? '' : '\n';
|
const requireStartLF = firstPiece === '' ? '' : '\n';
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import type {Locale} from 'date-fns';
|
|||||||
|
|
||||||
import {getName} from 'country-list';
|
import {getName} from 'country-list';
|
||||||
|
|
||||||
|
import {isNil} from 'lodash';
|
||||||
|
|
||||||
import Constants, {FileTypes, ValidationErrors, A11yCustomEventTypes, A11yFocusEventDetail} from 'utils/constants';
|
import Constants, {FileTypes, ValidationErrors, A11yCustomEventTypes, A11yFocusEventDetail} from 'utils/constants';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -1661,6 +1663,26 @@ function isSelection() {
|
|||||||
return selection!.type === 'Range';
|
return selection!.type === 'Range';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isTextSelectedInPostOrReply(e: React.KeyboardEvent | KeyboardEvent) {
|
||||||
|
const {id} = e.target as HTMLElement;
|
||||||
|
|
||||||
|
const isTypingInPost = id === 'post_textbox';
|
||||||
|
const isTypingInReply = id === 'reply_textbox';
|
||||||
|
|
||||||
|
if (!isTypingInPost && !isTypingInReply) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd,
|
||||||
|
} = e.target as TextboxElement;
|
||||||
|
|
||||||
|
const hasSelection = !isNil(selectionStart) && !isNil(selectionEnd) && selectionStart < selectionEnd;
|
||||||
|
|
||||||
|
return hasSelection;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns false when the element clicked or its ancestors
|
* Returns false when the element clicked or its ancestors
|
||||||
* is a potential click target (link, button, image, etc..)
|
* is a potential click target (link, button, image, etc..)
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user