UNPKG

@wordpress/block-library

Version:
8 lines (7 loc) 11.5 kB
{ "version": 3, "sources": ["../../../src/navigation-link/shared/update-attributes.js"], "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { escapeHTML } from '@wordpress/escape-html';\nimport { safeDecodeURI, getPath } from '@wordpress/url';\n\n/**\n * Determines if an entity link should be severed based on URL changes.\n *\n * @param {string} originalUrl - The original URL\n * @param {string} newUrl - The new URL\n * @return {boolean} True if the entity link should be severed\n */\nconst shouldSeverEntityLink = ( originalUrl, newUrl ) => {\n\tif ( ! originalUrl || ! newUrl ) {\n\t\treturn false;\n\t}\n\n\tconst normalizePath = ( path ) => {\n\t\tif ( ! path ) {\n\t\t\treturn '';\n\t\t}\n\t\treturn path.replace( /\\/+$/, '' ); // Remove trailing slashes\n\t};\n\n\t// Helper function to create URL objects with proper base handling\n\tconst createUrlObject = ( url, baseUrl = null ) => {\n\t\ttry {\n\t\t\t// Always provide a base URL - it will be ignored for absolute URLs\n\t\t\t// Use window.location.origin in browser, fallback for Node/tests\n\t\t\tconst base =\n\t\t\t\tbaseUrl ||\n\t\t\t\t( typeof window !== 'undefined'\n\t\t\t\t\t? window.location.origin\n\t\t\t\t\t: 'https://wordpress.org' );\n\t\t\treturn new URL( url, base );\n\t\t} catch ( error ) {\n\t\t\t// If URL construction still fails, it's likely an invalid URL\n\t\t\t// and we should sever the entity link\n\t\t\treturn null;\n\t\t}\n\t};\n\n\tconst originalUrlObj = createUrlObject( originalUrl );\n\tif ( ! originalUrlObj ) {\n\t\treturn true;\n\t}\n\n\tconst newUrlObj = createUrlObject( newUrl, originalUrl );\n\tif ( ! newUrlObj ) {\n\t\treturn true;\n\t}\n\n\t// Move these declarations here, after the null checks\n\tconst originalHostname = originalUrlObj.hostname;\n\tconst newHostname = newUrlObj.hostname;\n\tconst originalPath = normalizePath( getPath( originalUrlObj.toString() ) );\n\tconst newPath = normalizePath( getPath( newUrlObj.toString() ) );\n\n\t// If hostname or path changed, sever the entity link\n\tif ( originalHostname !== newHostname || originalPath !== newPath ) {\n\t\treturn true;\n\t}\n\n\t// Special handling for plain permalinks (query string post IDs)\n\tconst originalP = originalUrlObj.searchParams.get( 'p' );\n\tconst newP = newUrlObj.searchParams.get( 'p' );\n\n\t// If both are plain permalinks (with ?p= or ?page_id=), compare the IDs\n\tif ( originalP && newP && originalP !== newP ) {\n\t\treturn true;\n\t}\n\n\tconst originalPageId = originalUrlObj.searchParams.get( 'page_id' );\n\tconst newPageId = newUrlObj.searchParams.get( 'page_id' );\n\n\tif ( originalPageId && newPageId && originalPageId !== newPageId ) {\n\t\treturn true;\n\t}\n\t// If switching between ?p= and ?page_id=, or one is missing, sever\n\tif ( ( originalP && newPageId ) || ( originalPageId && newP ) ) {\n\t\treturn true;\n\t}\n\n\t// If only query string or fragment changed, preserve the entity link\n\treturn false;\n};\n\n/**\n * @typedef {'post-type'|'custom'|'taxonomy'|'post-type-archive'} WPNavigationLinkKind\n */\n/**\n * Navigation Link Block Attributes\n *\n * @typedef {Object} WPNavigationLinkBlockAttributes\n *\n * @property {string} [label] Link text.\n * @property {WPNavigationLinkKind} [kind] Kind is used to differentiate between term and post ids to check post draft status.\n * @property {string} [type] The type such as post, page, tag, category and other custom types.\n * @property {string} [rel] The relationship of the linked URL.\n * @property {number} [id] A post or term id.\n * @property {boolean} [opensInNewTab] Sets link target to _blank when true.\n * @property {string} [url] Link href.\n * @property {string} [title] Link title attribute.\n */\n/**\n * Link Control onChange handler that updates block attributes when a setting is changed.\n *\n * @param {Object} updatedValue New block attributes to update.\n * @param {Function} setAttributes Block attribute update function.\n * @param {WPNavigationLinkBlockAttributes} blockAttributes Current block attributes.\n */\n\nexport const updateAttributes = (\n\tupdatedValue = {},\n\tsetAttributes,\n\tblockAttributes = {}\n) => {\n\tconst {\n\t\tlabel: originalLabel = '',\n\t\tkind: originalKind = '',\n\t\ttype: originalType = '',\n\t} = blockAttributes;\n\n\tconst {\n\t\ttitle: newLabel = '', // the title of any provided Post.\n\t\tlabel: newLabelFromLabel = '', // alternative to title\n\t\turl: newUrl,\n\t\topensInNewTab,\n\t\tid: newID,\n\t\tkind: newKind = originalKind,\n\t\ttype: newType = originalType,\n\t} = updatedValue;\n\n\t// Use title if provided, otherwise fall back to label\n\tconst finalNewLabel = newLabel || newLabelFromLabel;\n\n\tconst newLabelWithoutHttp = finalNewLabel.replace( /http(s?):\\/\\//gi, '' );\n\tconst newUrlWithoutHttp = newUrl?.replace( /http(s?):\\/\\//gi, '' ) ?? '';\n\n\tconst useNewLabel =\n\t\tfinalNewLabel &&\n\t\tfinalNewLabel !== originalLabel &&\n\t\t// LinkControl without the title field relies\n\t\t// on the check below. Specifically, it assumes that\n\t\t// the URL is the same as a title.\n\t\t// This logic a) looks suspicious and b) should really\n\t\t// live in the LinkControl and not here. It's a great\n\t\t// candidate for future refactoring.\n\t\tnewLabelWithoutHttp !== newUrlWithoutHttp;\n\n\t// Unfortunately this causes the escaping model to be inverted.\n\t// The escaped content is stored in the block attributes (and ultimately in the database),\n\t// and then the raw data is \"recovered\" when outputting into the DOM.\n\t// It would be preferable to store the **raw** data in the block attributes and escape it in JS.\n\t// Why? Because there isn't one way to escape data. Depending on the context, you need to do\n\t// different transforms. It doesn't make sense to me to choose one of them for the purposes of storage.\n\t// See also:\n\t// - https://github.com/WordPress/gutenberg/pull/41063\n\t// - https://github.com/WordPress/gutenberg/pull/18617.\n\tconst label = useNewLabel\n\t\t? escapeHTML( finalNewLabel )\n\t\t: originalLabel || escapeHTML( newUrlWithoutHttp );\n\n\t// In https://github.com/WordPress/gutenberg/pull/24670 we decided to use \"tag\" in favor of \"post_tag\"\n\tconst type = newType === 'post_tag' ? 'tag' : newType.replace( '-', '_' );\n\n\tconst isBuiltInType =\n\t\t[ 'post', 'page', 'tag', 'category' ].indexOf( type ) > -1;\n\n\tconst isCustomLink =\n\t\t( ! newKind && ! isBuiltInType ) || newKind === 'custom';\n\tconst kind = isCustomLink ? 'custom' : newKind;\n\n\tconst attributes = {\n\t\t// Passed `url` may already be encoded. To prevent double encoding, decodeURI is executed to revert to the original string.\n\t\t...( newUrl !== undefined\n\t\t\t? { url: newUrl ? encodeURI( safeDecodeURI( newUrl ) ) : newUrl }\n\t\t\t: {} ),\n\t\t...( label && { label } ),\n\t\t...( undefined !== opensInNewTab && { opensInNewTab } ),\n\t\t...( kind && { kind } ),\n\t\t...( type && type !== 'URL' && { type } ),\n\t};\n\n\t// If the block's id is set then the menu item is linking to an entity.\n\t// Therefore, if the URL is set but a new ID is not provided, check if\n\t// the entity link should be severed based on URL changes.\n\tif ( newUrl && ! newID && blockAttributes.id ) {\n\t\tconst shouldSever = shouldSeverEntityLink(\n\t\t\tblockAttributes.url,\n\t\t\tnewUrl\n\t\t);\n\n\t\tif ( shouldSever ) {\n\t\t\tattributes.id = undefined; // explicitly \"unset\" the ID.\n\t\t\t// When URL is manually changed in a way that severs the entity link,\n\t\t\t// update kind and type to \"custom\" to indicate this is now a custom link.\n\t\t\tattributes.kind = 'custom';\n\t\t\tattributes.type = 'custom';\n\t\t}\n\t} else if ( newID && Number.isInteger( newID ) ) {\n\t\tattributes.id = newID;\n\t} else if ( blockAttributes.id ) {\n\t\t// If we have an existing ID and no URL change, ensure kind and type are preserved\n\t\tattributes.kind = kind;\n\t\tattributes.type = type;\n\t}\n\n\tsetAttributes( attributes );\n\n\t// Return metadata about the final state for binding decisions.\n\t// We need to distinguish between:\n\t// 1. Property not set in attributes (use blockAttributes fallback)\n\t// 2. Property explicitly set to undefined (means \"remove this\")\n\t// Using 'in' operator checks if property exists, even if undefined.\n\t// This is critical for severing: attributes.id = undefined means \"remove the ID\",\n\t// not \"keep the old ID from blockAttributes\".\n\tconst finalId = 'id' in attributes ? attributes.id : blockAttributes.id;\n\tconst finalKind =\n\t\t'kind' in attributes ? attributes.kind : blockAttributes.kind;\n\n\treturn {\n\t\tisEntityLink: !! finalId && finalKind !== 'custom',\n\t\tattributes, // Return the computed attributes object\n\t};\n};\n"], "mappings": "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", "names": [] }