utils.js 5.5 KB

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