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 | */
|
5 | import { upperFirst } from 'lodash-es';
|
6 | const ATTRIBUTE_WHITESPACES = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; // eslint-disable-line no-control-regex
|
7 | const SAFE_URL = /^(?:(?:https?|ftps?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.:-]|$))/i;
|
8 | // Simplified email test - should be run over previously found URL.
|
9 | const 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.).
|
12 | const PROTOCOL_REG_EXP = /^((\w+:(\/{2,})?)|(\W))/i;
|
13 | /**
|
14 | * A keystroke used by the {@link module:link/linkui~LinkUI link UI feature}.
|
15 | */
|
16 | export const LINK_KEYSTROKE = 'Ctrl+K';
|
17 | /**
|
18 | * Returns `true` if a given view node is the link element.
|
19 | */
|
20 | export 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 | */
|
26 | export 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 | */
|
41 | export 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 | */
|
48 | function 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 | */
|
63 | export 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 | */
|
80 | export 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 | */
|
93 | export 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 | */
|
102 | export 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 | */
|
112 | export 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 | */
|
120 | export function linkHasProtocol(link) {
|
121 | return PROTOCOL_REG_EXP.test(link);
|
122 | }
|
123 | /**
|
124 | * Opens the link in a new browser tab.
|
125 | */
|
126 | export function openLink(link) {
|
127 | window.open(link, '_blank', 'noopener');
|
128 | }
|