UNPKG

5 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4 */
5import { upperFirst } from 'lodash-es';
6const ATTRIBUTE_WHITESPACES = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; // eslint-disable-line no-control-regex
7const SAFE_URL = /^(?:(?:https?|ftps?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.:-]|$))/i;
8// Simplified email test - should be run over previously found URL.
9const EMAIL_REG_EXP = /^[\S]+@((?![-_])(?:[-\w\u00a1-\uffff]{0,63}[^-_]\.))+(?:[a-z\u00a1-\uffff]{2,})$/i;
10// The regex checks for the protocol syntax ('xxxx://' or 'xxxx:')
11// or non-word characters at the beginning of the link ('/', '#' etc.).
12const PROTOCOL_REG_EXP = /^((\w+:(\/{2,})?)|(\W))/i;
13/**
14 * A keystroke used by the {@link module:link/linkui~LinkUI link UI feature}.
15 */
16export const LINK_KEYSTROKE = 'Ctrl+K';
17/**
18 * Returns `true` if a given view node is the link element.
19 */
20export function isLinkElement(node) {
21 return node.is('attributeElement') && !!node.getCustomProperty('link');
22}
23/**
24 * Creates a link {@link module:engine/view/attributeelement~AttributeElement} with the provided `href` attribute.
25 */
26export function createLinkElement(href, { writer }) {
27 // Priority 5 - https://github.com/ckeditor/ckeditor5-link/issues/121.
28 const linkElement = writer.createAttributeElement('a', { href }, { priority: 5 });
29 writer.setCustomProperty('link', true, linkElement);
30 return linkElement;
31}
32/**
33 * Returns a safe URL based on a given value.
34 *
35 * A URL is considered safe if it is safe for the user (does not contain any malicious code).
36 *
37 * If a URL is considered unsafe, a simple `"#"` is returned.
38 *
39 * @internal
40 */
41export function ensureSafeUrl(url) {
42 const urlString = String(url);
43 return isSafeUrl(urlString) ? urlString : '#';
44}
45/**
46 * Checks whether the given URL is safe for the user (does not contain any malicious code).
47 */
48function isSafeUrl(url) {
49 const normalizedUrl = url.replace(ATTRIBUTE_WHITESPACES, '');
50 return !!normalizedUrl.match(SAFE_URL);
51}
52/**
53 * Returns the {@link module:link/linkconfig~LinkConfig#decorators `config.link.decorators`} configuration processed
54 * to respect the locale of the editor, i.e. to display the {@link module:link/linkconfig~LinkDecoratorManualDefinition label}
55 * in the correct language.
56 *
57 * **Note**: Only the few most commonly used labels are translated automatically. Other labels should be manually
58 * translated in the {@link module:link/linkconfig~LinkConfig#decorators `config.link.decorators`} configuration.
59 *
60 * @param t Shorthand for {@link module:utils/locale~Locale#t Locale#t}.
61 * @param decorators The decorator reference where the label values should be localized.
62 */
63export function getLocalizedDecorators(t, decorators) {
64 const localizedDecoratorsLabels = {
65 'Open in a new tab': t('Open in a new tab'),
66 'Downloadable': t('Downloadable')
67 };
68 decorators.forEach(decorator => {
69 if ('label' in decorator && localizedDecoratorsLabels[decorator.label]) {
70 decorator.label = localizedDecoratorsLabels[decorator.label];
71 }
72 return decorator;
73 });
74 return decorators;
75}
76/**
77 * Converts an object with defined decorators to a normalized array of decorators. The `id` key is added for each decorator and
78 * is used as the attribute's name in the model.
79 */
80export function normalizeDecorators(decorators) {
81 const retArray = [];
82 if (decorators) {
83 for (const [key, value] of Object.entries(decorators)) {
84 const decorator = Object.assign({}, value, { id: `link${upperFirst(key)}` });
85 retArray.push(decorator);
86 }
87 }
88 return retArray;
89}
90/**
91 * Returns `true` if the specified `element` can be linked (the element allows the `linkHref` attribute).
92 */
93export function isLinkableElement(element, schema) {
94 if (!element) {
95 return false;
96 }
97 return schema.checkAttribute(element.name, 'linkHref');
98}
99/**
100 * Returns `true` if the specified `value` is an email.
101 */
102export function isEmail(value) {
103 return EMAIL_REG_EXP.test(value);
104}
105/**
106 * Adds the protocol prefix to the specified `link` when:
107 *
108 * * it does not contain it already, and there is a {@link module:link/linkconfig~LinkConfig#defaultProtocol `defaultProtocol` }
109 * configuration value provided,
110 * * or the link is an email address.
111 */
112export function addLinkProtocolIfApplicable(link, defaultProtocol) {
113 const protocol = isEmail(link) ? 'mailto:' : defaultProtocol;
114 const isProtocolNeeded = !!protocol && !linkHasProtocol(link);
115 return link && isProtocolNeeded ? protocol + link : link;
116}
117/**
118 * Checks if protocol is already included in the link.
119 */
120export function linkHasProtocol(link) {
121 return PROTOCOL_REG_EXP.test(link);
122}
123/**
124 * Opens the link in a new browser tab.
125 */
126export function openLink(link) {
127 window.open(link, '_blank', 'noopener');
128}