@@ -315,6 +315,16 @@ function RemoteFunctions(config = {}) {
315315 } ) ;
316316 }
317317
318+ /**
319+ * This function gets called when the edit hyperlink button is clicked
320+ * @param {Event } event
321+ * @param {DOMElement } element - the HTML link element
322+ */
323+ function _handleEditHyperlinkOptionClick ( event , element ) {
324+ dismissHyperlinkEditor ( ) ;
325+ _hyperlinkEditor = new HyperlinkEditor ( element ) ;
326+ }
327+
318328 /**
319329 * This function gets called when the delete button is clicked
320330 * it sends a message to the editor using postMessage to delete the element from the source code
@@ -460,6 +470,8 @@ function RemoteFunctions(config = {}) {
460470 _handleSelectParentOptionClick ( e , element ) ;
461471 } else if ( action === "edit-text" ) {
462472 startEditing ( element ) ;
473+ } else if ( action === "edit-hyperlink" ) {
474+ _handleEditHyperlinkOptionClick ( e , element ) ;
463475 } else if ( action === "duplicate" ) {
464476 _handleDuplicateOptionClick ( e , element ) ;
465477 } else if ( action === "delete" ) {
@@ -1501,6 +1513,13 @@ function RemoteFunctions(config = {}) {
15011513 <svg viewBox="0 0 24 24" fill="currentColor">
15021514 <path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
15031515 </svg>
1516+ ` ,
1517+
1518+ link : `
1519+ <svg viewBox="0 0 16 16" fill="currentColor">
1520+ <path d="M6.354 5.5H4a3 3 0 0 0 0 6h3a3 3 0 0 0 2.83-4H9c-.086 0-.17.01-.25.031A2 2 0 0 1 7 10.5H4a2 2 0 1 1 0-4h1.535c.218-.376.495-.714.82-1z"/>
1521+ <path d="M9 5.5a3 3 0 0 0-2.83 4h1.098A2 2 0 0 1 9 6.5h3a2 2 0 1 1 0 4h-1.535a4.02 4.02 0 0 1-.82 1H12a3 3 0 1 0 0-6H9z"/>
1522+ </svg>
15041523 `
15051524 } ;
15061525
@@ -1598,6 +1617,13 @@ function RemoteFunctions(config = {}) {
15981617 </span>` ;
15991618 }
16001619
1620+ // if its a link element, we show the edit hyperlink icon
1621+ if ( this . element && this . element . tagName . toLowerCase ( ) === 'a' ) {
1622+ content += `<span data-action="edit-hyperlink" title="${ config . strings . editHyperlink } ">
1623+ ${ ICONS . link }
1624+ </span>` ;
1625+ }
1626+
16011627 // if its an image element, we show the image gallery icon
16021628 if ( this . element && this . element . tagName . toLowerCase ( ) === 'img' ) {
16031629 content += `<span data-action="image-gallery" title="${ config . strings . imageGallery } ">
@@ -1749,6 +1775,143 @@ function RemoteFunctions(config = {}) {
17491775 }
17501776 } ;
17511777
1778+ /**
1779+ * This shows a floating input box above the element which allows you to edit the link of the 'a' tag
1780+ */
1781+ function HyperlinkEditor ( element ) {
1782+ this . element = element ;
1783+ this . remove = this . remove . bind ( this ) ;
1784+ this . create ( ) ;
1785+ }
1786+
1787+ HyperlinkEditor . prototype = {
1788+ create : function ( ) {
1789+ const currentHref = this . element . getAttribute ( 'href' ) || '' ;
1790+
1791+ // Create shadow DOM container
1792+ this . body = document . createElement ( 'div' ) ;
1793+ this . body . setAttribute ( 'data-phcode-internal-c15r5a9' , '1' ) ;
1794+ document . body . appendChild ( this . body ) ;
1795+
1796+ const shadow = this . body . attachShadow ( { mode : 'open' } ) ;
1797+
1798+ // Create input HTML + styles
1799+ const html = `
1800+ <style>
1801+ :host { all: initial !important; }
1802+ .hyperlink-input-box {
1803+ position: absolute;
1804+ background: white;
1805+ border: 1px solid #4285F4;
1806+ border-radius: 3px;
1807+ padding: 6px 8px;
1808+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
1809+ z-index: 2147483647;
1810+ min-width: 200px;
1811+ max-width: 400px;
1812+ box-sizing: border-box;
1813+ }
1814+ input {
1815+ width: 100%;
1816+ border: none;
1817+ outline: none;
1818+ font: 12px Monaco, Consolas, monospace;
1819+ color: #333;
1820+ background: transparent;
1821+ }
1822+ input::placeholder {
1823+ color: #999;
1824+ }
1825+ </style>
1826+ <div class="hyperlink-input-box">
1827+ <input type="text" value="${ currentHref } " placeholder="https://example.com" spellcheck="false" />
1828+ </div>
1829+ ` ;
1830+
1831+ shadow . innerHTML = html ;
1832+ this . _shadow = shadow ;
1833+
1834+ this . _positionInput ( ) ;
1835+
1836+ // setup the event listeners
1837+ const input = shadow . querySelector ( 'input' ) ;
1838+ input . focus ( ) ;
1839+ input . select ( ) ;
1840+
1841+ input . addEventListener ( 'keydown' , ( e ) => this . _handleKeydown ( e ) ) ;
1842+ input . addEventListener ( 'blur' , ( ) => this . _handleBlur ( ) ) ;
1843+ } ,
1844+
1845+ _positionInput : function ( ) {
1846+ const inputBoxElement = this . _shadow . querySelector ( '.hyperlink-input-box' ) ;
1847+ if ( ! inputBoxElement ) {
1848+ return ;
1849+ }
1850+
1851+ const boxRect = inputBoxElement . getBoundingClientRect ( ) ;
1852+ const elemBounds = this . element . getBoundingClientRect ( ) ;
1853+ const offset = _screenOffset ( this . element ) ;
1854+
1855+ let topPos = offset . top - boxRect . height - 6 ;
1856+ let leftPos = offset . left + elemBounds . width - boxRect . width ;
1857+
1858+ // If would go off top, position below
1859+ if ( elemBounds . top - boxRect . height < 6 ) {
1860+ topPos = offset . top + elemBounds . height + 6 ;
1861+ }
1862+
1863+ // If would go off left, align left
1864+ if ( leftPos < 0 ) {
1865+ leftPos = offset . left ;
1866+ }
1867+
1868+ inputBoxElement . style . left = leftPos + 'px' ;
1869+ inputBoxElement . style . top = topPos + 'px' ;
1870+ } ,
1871+
1872+ _handleKeydown : function ( event ) {
1873+ if ( event . key === 'Enter' ) {
1874+ event . preventDefault ( ) ;
1875+ this . _save ( ) ;
1876+ } else if ( event . key === 'Escape' ) {
1877+ event . preventDefault ( ) ;
1878+ dismissHyperlinkEditor ( ) ;
1879+ }
1880+ } ,
1881+
1882+ _handleBlur : function ( ) {
1883+ setTimeout ( ( ) => this . _save ( ) , 100 ) ;
1884+ } ,
1885+
1886+ _save : function ( ) {
1887+ const input = this . _shadow . querySelector ( 'input' ) ;
1888+ const newHref = input . value . trim ( ) ;
1889+ const oldHref = this . element . getAttribute ( 'href' ) || '' ;
1890+
1891+ if ( newHref !== oldHref ) {
1892+ this . element . setAttribute ( 'href' , newHref ) ;
1893+
1894+ const tagId = this . element . getAttribute ( 'data-brackets-id' ) ;
1895+ window . _Brackets_MessageBroker . send ( {
1896+ livePreviewEditEnabled : true ,
1897+ livePreviewHyperlinkEdit : true ,
1898+ element : this . element ,
1899+ tagId : Number ( tagId ) ,
1900+ newHref : newHref
1901+ } ) ;
1902+ }
1903+
1904+ dismissUIAndCleanupState ( ) ;
1905+ } ,
1906+
1907+ remove : function ( ) {
1908+ if ( this . body && this . body . parentNode ) {
1909+ this . body . parentNode . removeChild ( this . body ) ;
1910+ this . body = null ;
1911+ }
1912+ }
1913+ } ;
1914+
17521915 /**
17531916 * this is called when user clicks on the Show Ruler lines option in the more options dropdown
17541917 * @param {Event } event - click event
@@ -4191,6 +4354,7 @@ function RemoteFunctions(config = {}) {
41914354 var _moreOptionsDropdown ;
41924355 var _aiPromptBox ;
41934356 var _imageRibbonGallery ;
4357+ var _hyperlinkEditor ;
41944358 var _currentRulerLines ;
41954359 var _setup = false ;
41964360 var _hoverLockTimer = null ;
@@ -5251,6 +5415,13 @@ function RemoteFunctions(config = {}) {
52515415 }
52525416 }
52535417
5418+ function dismissHyperlinkEditor ( ) {
5419+ if ( _hyperlinkEditor ) {
5420+ _hyperlinkEditor . remove ( ) ;
5421+ _hyperlinkEditor = null ;
5422+ }
5423+ }
5424+
52545425 /**
52555426 * Helper function to dismiss all UI boxes at once
52565427 */
@@ -5260,6 +5431,7 @@ function RemoteFunctions(config = {}) {
52605431 dismissAIPromptBox ( ) ;
52615432 dismissNodeInfoBox ( ) ;
52625433 dismissImageRibbonGallery ( ) ;
5434+ dismissHyperlinkEditor ( ) ;
52635435 dismissToastMessage ( ) ;
52645436 }
52655437
0 commit comments