@wordpress/format-library
Version:
Format library for the WordPress editor.
212 lines (198 loc) • 7.06 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createLinkFormat = createLinkFormat;
exports.getFormatBoundary = getFormatBoundary;
exports.isValidHref = isValidHref;
var _url = require("@wordpress/url");
/**
* WordPress dependencies
*/
/**
* Check for issues with the provided href.
*
* @param {string} href The href.
*
* @return {boolean} Is the href invalid?
*/
function isValidHref(href) {
if (!href) {
return false;
}
const trimmedHref = href.trim();
if (!trimmedHref) {
return false;
}
// Does the href start with something that looks like a URL protocol?
if (/^\S+:/.test(trimmedHref)) {
const protocol = (0, _url.getProtocol)(trimmedHref);
if (!(0, _url.isValidProtocol)(protocol)) {
return false;
}
// Add some extra checks for http(s) URIs, since these are the most common use-case.
// This ensures URIs with an http protocol have exactly two forward slashes following the protocol.
if (protocol.startsWith('http') && !/^https?:\/\/[^\/\s]/i.test(trimmedHref)) {
return false;
}
const authority = (0, _url.getAuthority)(trimmedHref);
if (!(0, _url.isValidAuthority)(authority)) {
return false;
}
const path = (0, _url.getPath)(trimmedHref);
if (path && !(0, _url.isValidPath)(path)) {
return false;
}
const queryString = (0, _url.getQueryString)(trimmedHref);
if (queryString && !(0, _url.isValidQueryString)(queryString)) {
return false;
}
const fragment = (0, _url.getFragment)(trimmedHref);
if (fragment && !(0, _url.isValidFragment)(fragment)) {
return false;
}
}
// Validate anchor links.
if (trimmedHref.startsWith('#') && !(0, _url.isValidFragment)(trimmedHref)) {
return false;
}
return true;
}
/**
* Generates the format object that will be applied to the link text.
*
* @param {Object} options
* @param {string} options.url The href of the link.
* @param {string} options.type The type of the link.
* @param {string} options.id The ID of the link.
* @param {boolean} options.opensInNewWindow Whether this link will open in a new window.
* @param {boolean} options.nofollow Whether this link is marked as no follow relationship.
* @return {Object} The final format object.
*/
function createLinkFormat({
url,
type,
id,
opensInNewWindow,
nofollow
}) {
const format = {
type: 'core/link',
attributes: {
url
}
};
if (type) {
format.attributes.type = type;
}
if (id) {
format.attributes.id = id;
}
if (opensInNewWindow) {
format.attributes.target = '_blank';
format.attributes.rel = format.attributes.rel ? format.attributes.rel + ' noreferrer noopener' : 'noreferrer noopener';
}
if (nofollow) {
format.attributes.rel = format.attributes.rel ? format.attributes.rel + ' nofollow' : 'nofollow';
}
return format;
}
/* eslint-disable jsdoc/no-undefined-types */
/**
* Get the start and end boundaries of a given format from a rich text value.
*
*
* @param {RichTextValue} value the rich text value to interrogate.
* @param {string} format the identifier for the target format (e.g. `core/link`, `core/bold`).
* @param {number?} startIndex optional startIndex to seek from.
* @param {number?} endIndex optional endIndex to seek from.
* @return {Object} object containing start and end values for the given format.
*/
/* eslint-enable jsdoc/no-undefined-types */
function getFormatBoundary(value, format, startIndex = value.start, endIndex = value.end) {
const EMPTY_BOUNDARIES = {
start: null,
end: null
};
const {
formats
} = value;
let targetFormat;
let initialIndex;
if (!formats?.length) {
return EMPTY_BOUNDARIES;
}
// Clone formats to avoid modifying source formats.
const newFormats = formats.slice();
const formatAtStart = newFormats[startIndex]?.find(({
type
}) => type === format.type);
const formatAtEnd = newFormats[endIndex]?.find(({
type
}) => type === format.type);
const formatAtEndMinusOne = newFormats[endIndex - 1]?.find(({
type
}) => type === format.type);
if (!!formatAtStart) {
// Set values to conform to "start"
targetFormat = formatAtStart;
initialIndex = startIndex;
} else if (!!formatAtEnd) {
// Set values to conform to "end"
targetFormat = formatAtEnd;
initialIndex = endIndex;
} else if (!!formatAtEndMinusOne) {
// This is an edge case which will occur if you create a format, then place
// the caret just before the format and hit the back ARROW key. The resulting
// value object will have start and end +1 beyond the edge of the format boundary.
targetFormat = formatAtEndMinusOne;
initialIndex = endIndex - 1;
} else {
return EMPTY_BOUNDARIES;
}
const index = newFormats[initialIndex].indexOf(targetFormat);
const walkingArgs = [newFormats, initialIndex, targetFormat, index];
// Walk the startIndex "backwards" to the leading "edge" of the matching format.
startIndex = walkToStart(...walkingArgs);
// Walk the endIndex "forwards" until the trailing "edge" of the matching format.
endIndex = walkToEnd(...walkingArgs);
// Safe guard: start index cannot be less than 0.
startIndex = startIndex < 0 ? 0 : startIndex;
// // Return the indices of the "edges" as the boundaries.
return {
start: startIndex,
end: endIndex
};
}
/**
* Walks forwards/backwards towards the boundary of a given format within an
* array of format objects. Returns the index of the boundary.
*
* @param {Array} formats the formats to search for the given format type.
* @param {number} initialIndex the starting index from which to walk.
* @param {Object} targetFormatRef a reference to the format type object being sought.
* @param {number} formatIndex the index at which we expect the target format object to be.
* @param {string} direction either 'forwards' or 'backwards' to indicate the direction.
* @return {number} the index of the boundary of the given format.
*/
function walkToBoundary(formats, initialIndex, targetFormatRef, formatIndex, direction) {
let index = initialIndex;
const directions = {
forwards: 1,
backwards: -1
};
const directionIncrement = directions[direction] || 1; // invalid direction arg default to forwards
const inverseDirectionIncrement = directionIncrement * -1;
while (formats[index] && formats[index][formatIndex] === targetFormatRef) {
// Increment/decrement in the direction of operation.
index = index + directionIncrement;
}
// Restore by one in inverse direction of operation
// to avoid out of bounds.
index = index + inverseDirectionIncrement;
return index;
}
const partialRight = (fn, ...partialArgs) => (...args) => fn(...args, ...partialArgs);
const walkToStart = partialRight(walkToBoundary, 'backwards');
const walkToEnd = partialRight(walkToBoundary, 'forwards');
//# sourceMappingURL=utils.js.map
;