├── Block └── Hints │ └── Init.php ├── Helper └── Config.php ├── LICENSE.txt ├── Plugin └── View │ └── LayoutPlugin.php ├── README.md ├── Test └── Unit │ └── LayoutPluginTest.php ├── composer.json ├── docs ├── console.gif ├── phpstorm-config.png ├── total-workings.gif └── usage.gif ├── etc ├── di.xml └── module.xml ├── registration.php └── view └── base ├── layout └── default.xml └── web ├── css └── hints.css └── js └── hints.js /Block/Hints/Init.php: -------------------------------------------------------------------------------- 1 | isHintEnabled()) { 42 | $this->assetRepository = $context->getAssetRepository(); 43 | $this->assetCollection = $assetCollection; 44 | $this->addAssets(); 45 | } 46 | 47 | return parent::__construct($context, $data); 48 | } 49 | 50 | /** 51 | * Add assets to the header required for the initialisation of the scripts 52 | * 53 | * @todo figure out how to include .less files instead of .css files for easier syntax. 54 | * http://devdocs.magento.com/guides/v2.0/architecture/view/page-assets.html#m2devgde-page-assets-api 55 | * 56 | * @return void 57 | */ 58 | public function addAssets() 59 | { 60 | $js = $this->assetRepository->createAsset('Ho_Templatehints::js/hints.js'); 61 | $this->assetCollection->add('Ho_Templatehints::js/hints.js', $js); 62 | 63 | $css = $this->assetRepository->createAsset('Ho_Templatehints::css/hints.css'); 64 | $this->assetCollection->add('cHo_Templatehints::css/hints.css', $css); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Helper/Config.php: -------------------------------------------------------------------------------- 1 | appState = $appState; 41 | $this->storeManager = $storeManager; 42 | $this->developerHelper = $developerHelper; 43 | } 44 | 45 | /** 46 | * Check if the hints can be displayed. 47 | * 48 | * It will check if the url parameter is present. 49 | * For production mode it will also check if the IP-address is in Developer Client Restrictions. 50 | * 51 | * @return bool 52 | */ 53 | public function isHintEnabled() 54 | { 55 | $isParamPresent = $this->_request->getParam('ath', false) === '1'; 56 | 57 | if ($isParamPresent) { 58 | $applicationMode = $this->appState->getMode(); 59 | $storeId = $this->storeManager->getStore()->getId(); 60 | 61 | if ($applicationMode === AppState::MODE_DEVELOPER || $this->developerHelper->isDevAllowed($storeId)) { 62 | return true; 63 | } 64 | } 65 | 66 | return false; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, H&O E-commerce specialisten B.V. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /Plugin/View/LayoutPlugin.php: -------------------------------------------------------------------------------- 1 | directoryList = $directoryList; 57 | $this->hintConfig = $hintConfig; 58 | $this->layout = $layout; 59 | 60 | $layoutReflection = new \ReflectionClass($this->layout); 61 | $structureProp = $layoutReflection->getProperty('structure'); 62 | $structureProp->setAccessible(true); 63 | $this->structure = $structureProp->getValue($this->layout); 64 | } 65 | 66 | /** 67 | * @param Layout $layout 68 | * @param Closure $proceed 69 | * @param string $name 70 | * @param bool $useCache 71 | * 72 | * @return string 73 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 74 | */ 75 | public function aroundRenderElement(Layout $layout, Closure $proceed, $name, $useCache = false) // @codingStandardsIgnoreLine 76 | { 77 | $result = $proceed($name, $useCache); 78 | if ($this->hintConfig->isHintEnabled() === false) { 79 | return $result; 80 | } 81 | 82 | return $this->_decorateElement($result, $name); 83 | } 84 | 85 | /** 86 | * @param string $result 87 | * @param string $name 88 | * 89 | * @return string 90 | */ 91 | private function _decorateElement($result, $name) 92 | { 93 | if (! $result) { 94 | $result = '
'; 95 | } 96 | 97 | if ($this->layout->isUiComponent($name)) { 98 | /** @var \Magento\Framework\View\Element\AbstractBlock $block */ 99 | $block = $this->layout->getBlock($name); 100 | $result = $this->decorateOuterElement($result, [ 101 | 'data-ho-hinttype' => 'ui-container', 102 | 'data-ho-hintdata' => $this->getBlockInfo($block) 103 | ]); 104 | } elseif ($this->layout->isBlock($name)) { 105 | $result = $this->decorateOuterElement($result, [ 106 | 'data-ho-hinttype' => 'block', 107 | 'data-ho-hintdata' => $this->getBlockInfo($this->layout->getBlock($name)) 108 | ]); 109 | } elseif ($this->layout->isContainer($name)) { 110 | $result = $this->decorateOuterElement($result, [ 111 | 'data-ho-hinttype' => 'container', 112 | 'data-ho-hintdata' => $this->getContainerInfo($name, $this->structure->getElement($name)) 113 | ]); 114 | } 115 | return $result; 116 | } 117 | 118 | /** 119 | * @param string $html 120 | * @param array $attributes 121 | * 122 | * @return string 123 | */ 124 | public function decorateOuterElement($html, $attributes) 125 | { 126 | if (! $html) { 127 | return $html; 128 | } 129 | 130 | $htmlAttr = []; 131 | foreach ($attributes as $key => $value) { 132 | $htmlAttr[] = sprintf('%s="%s"', $key, htmlspecialchars($value)); 133 | } 134 | $htmlAttr = implode(' ', $htmlAttr); 135 | 136 | $html = preg_replace( 137 | '/(<\b[^><]*)>/i', 138 | '$1'.($htmlAttr ? ' '.$htmlAttr : '').'>', 139 | $html, 140 | 1 141 | ); 142 | 143 | return $html; 144 | } 145 | 146 | /** 147 | * @param string $nameInLayout 148 | * @param array $container 149 | * 150 | * @return string 151 | */ 152 | private function getContainerInfo(string $nameInLayout, array $container) 153 | { 154 | $result = self::filterEscapeEncode([ 155 | 'info' => [ 156 | 'nameInLayout' => $nameInLayout, 157 | 'label' => $container['label'] ?? null 158 | ], 159 | 'extra' => [ 160 | 'parent' => $container['parent'] ?? null, 161 | 'children' => isset($container['children']) ? array_values($container['children']) : null, 162 | ] 163 | ]); 164 | 165 | return $result; 166 | } 167 | 168 | /** 169 | * Returns the blockInfo as a json encoded array 170 | * 171 | * @param \Magento\Framework\View\Element\AbstractBlock $block 172 | * @return string 173 | */ 174 | private function getBlockInfo(\Magento\Framework\View\Element\AbstractBlock $block) 175 | { 176 | $result = self::filterEscapeEncode([ 177 | 'info' => [ 178 | 'nameInLayout' => $block->getNameInLayout(), 179 | 'moduleName' => $block->getModuleName(), 180 | 'phpClass' => $this->getBlockClass($block), 181 | ], 182 | 'extra' => [ 183 | 'cacheKeyInfo' => @$block->getCacheKeyInfo() 184 | ], 185 | 'paths' => [ 186 | 'template' => $block->getTemplateFile(), 187 | ] + $this->getBlockPaths($block) 188 | ]); 189 | 190 | return $result; 191 | } 192 | 193 | /** 194 | * @param array $data 195 | * 196 | * @return string 197 | */ 198 | public static function filterEscapeEncode(array $data) 199 | { 200 | return json_encode(self::filterEscape($data)); 201 | } 202 | 203 | /** 204 | * Filter and escape the complete array. 205 | * 206 | * @param $data 207 | * 208 | * @return array 209 | */ 210 | private static function filterEscape($data) 211 | { 212 | return array_filter(array_map(function ($elem) { 213 | if (is_array($elem)) { 214 | return self::filterEscape($elem); 215 | } 216 | return is_null($elem) ? $elem : addslashes($elem); // @codingStandardsIgnoreLine 217 | }, $data)); 218 | } 219 | 220 | /** 221 | * @param \Magento\Framework\View\Element\AbstractBlock $block 222 | * 223 | * @return string[string] 224 | */ 225 | private function getBlockPaths(\Magento\Framework\View\Element\AbstractBlock $block) 226 | { 227 | $reflector = new \ReflectionClass($block); //@codingStandardsIgnoreLine 228 | $classFileName = $reflector->getFileName(); 229 | 230 | if ($block instanceof \Magento\Framework\Interception\InterceptorInterface) { 231 | $classFileName = $reflector->getParentClass()->getFileName(); 232 | } 233 | 234 | return ['class' => $classFileName]; 235 | } 236 | 237 | private function getBlockClass(\Magento\Framework\View\Element\AbstractBlock $block) 238 | { 239 | $className = get_class($block); 240 | 241 | if ($block instanceof \Magento\Framework\Interception\InterceptorInterface) { 242 | $reflector = new \ReflectionClass($block); //@codingStandardsIgnoreLine 243 | $className = $reflector->getParentClass()->getName(); 244 | } 245 | 246 | return $className; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # H&O Magento 2 Advanced Template Hints module 2 | 3 | ![Overview](docs/total-workings.gif) 4 | 5 | Ho_Templatehints extends the default Magento template hints. 6 | 7 | - Easily accessible with muscle memory `?ath=1`. 8 | - Shows hints for **Templates**, **AbstractBlocks** (Blocks), **Containers** and **UI Components**. 9 | - No layout interference: Using css outline instead of borders of other HTML elements, so it doesn't f'up the layout. 10 | 11 | ![Usage $0](docs/usage.gif) 12 | 13 | ## Installation 14 | 15 | ``` 16 | composer require --dev honl/magento2-templatehints 17 | php bin/magento module:enable Ho_Templatehints 18 | php bin/magento setup:upgrade 19 | ``` 20 | 21 | ### Development installation (git enabled) 22 | 23 | ``` 24 | composer require --dev honl/magento2-templatehints "dev-master" 25 | php bin/magento module:enable Ho_Templatehints 26 | php bin/magento setup:upgrade 27 | ``` 28 | 29 | ### Configure PHPStorm, allow remote calls 30 | 31 | ![Setting up PHPStorm](docs/phpstorm-config.png) 32 | 33 | ## Usage 34 | 1. Set your Magento 2 installation to developer mode (when in production mode make sure to add your IP address in Developer Client Restrictions). 35 | 2. Add `?ath=1` to your URL to activate. 36 | 3. Open up your console in you Chrome/Firefox/Safari/~IE~ devtools. 37 | 4. hold (shift) 38 | 5. Hover over the element you wish to inspect 39 | 6. Voila! Hints everywhere! 40 | 41 | ### Hints for hidden elements 42 | You can't show hints for a hidden element, for that purpose there is `hint($0)`: 43 | 44 | ```JS 45 | //Select an element in the Elements panel in your devtools, it is now available with $0 46 | hint($0) 47 | ``` 48 | 49 | ![Console $0](docs/console.gif) 50 | 51 | 52 | ## Inner Workings 53 | The module adds an additional HTML attribute to the outer most element of a layout element. 54 | 55 | ## Credits 56 | Inspiration from [Aoe_TemplateHints](https://github.com/AOEpeople/Aoe_TemplateHints) and a lot of love from H&O. 57 | 58 | ## Developer 59 | This module was developed by the H&O team. Check out more about this module on the H&O website: 60 | H&O Magento 2 Advanced Template hints module 61 | 62 | -------------------------------------------------------------------------------- /Test/Unit/LayoutPluginTest.php: -------------------------------------------------------------------------------- 1 | objectManager = new ObjectManagerHelper($this); 29 | $this->plugin = $this->objectManager->getObject(LayoutPlugin::class); 30 | } 31 | 32 | public function testDecorateOuterElementInput() 33 | { 34 | $result = $this->plugin->decorateOuterElement('
', []); 35 | $this->assertEquals('
', $result); 36 | 37 | $result = $this->plugin->decorateOuterElement('
', ['class' => 'yo']); 38 | $this->assertEquals('
', $result); 39 | 40 | $result = $this->plugin->decorateOuterElement('
', ['data-attr' => '"']); 41 | $this->assertEquals('
', $result); 42 | } 43 | 44 | 45 | public function testDecorateOuterElementMultipleElements() 46 | { 47 | $result = $this->plugin->decorateOuterElement('
', ['class' => 'myclass']); 48 | $this->assertEquals('
', $result); 49 | 50 | // $result = $this->plugin->decorateOuterElement('
', ['class' => 'myclass']); 51 | // $this->assertEquals('
', $result); 52 | } 53 | 54 | 55 | public function testFilterEscapeEncode() 56 | { 57 | $this->assertEquals('[]', LayoutPlugin::filterEscapeEncode(['elem' => null])); 58 | 59 | $this->assertEquals('[]', LayoutPlugin::filterEscapeEncode(['elem' => ['subelem' => null]])); 60 | $this->assertEquals('[]', LayoutPlugin::filterEscapeEncode(['elem' => ['subelem' => ['subsub' => null]]])); 61 | 62 | $this->assertEquals( 63 | '{"elem":{"subElem":"bla"}}', 64 | LayoutPlugin::filterEscapeEncode(['elem' => ['subElem' => 'bla']]) 65 | ); 66 | 67 | $this->assertEquals( 68 | '{"elem":"My\\\\\\\\Class"}', 69 | LayoutPlugin::filterEscapeEncode(['elem' => 'My\Class']) 70 | ); 71 | 72 | $this->assertEquals( 73 | '{"elem":{"subElem":["bla","My\\\\\\\\Class"]}}', 74 | LayoutPlugin::filterEscapeEncode(['elem' => ['subElem' => ['bla', 'My\Class']]]) 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "honl/magento2-templatehints", 3 | "replace": { 4 | "ho-nl/magento2-templatehints": "self.version" 5 | }, 6 | "description": "H&O Magento 2 Advanced Template Hints module", 7 | "homepage": "https://github.com/ho-nl/magento2-Ho_Templatehints", 8 | "authors": [ 9 | { 10 | "name": "Paul Hachmang - H&O", 11 | "email": "paul@h-o.nl" 12 | } 13 | ], 14 | "require": { 15 | "php": "~7.3||~7.4||^8.1||~8.2" 16 | }, 17 | "type": "magento2-module", 18 | "license": "BSD-2-Clause", 19 | "autoload": { 20 | "files": [ 21 | "registration.php" 22 | ], 23 | "psr-4": { 24 | "Ho\\Templatehints\\": "" 25 | } 26 | }, 27 | "extra": { 28 | "branch-alias": { 29 | "dev-master": "0.2-dev" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/console.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-nl/magento2-Ho_Templatehints/24c040aa8975c4d76d9da5ecb5e93249e6cb58cd/docs/console.gif -------------------------------------------------------------------------------- /docs/phpstorm-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-nl/magento2-Ho_Templatehints/24c040aa8975c4d76d9da5ecb5e93249e6cb58cd/docs/phpstorm-config.png -------------------------------------------------------------------------------- /docs/total-workings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-nl/magento2-Ho_Templatehints/24c040aa8975c4d76d9da5ecb5e93249e6cb58cd/docs/total-workings.gif -------------------------------------------------------------------------------- /docs/usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-nl/magento2-Ho_Templatehints/24c040aa8975c4d76d9da5ecb5e93249e6cb58cd/docs/usage.gif -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /view/base/web/css/hints.css: -------------------------------------------------------------------------------- 1 | /**! 2 | * Copyright (c) 2016 H&O E-commerce specialisten B.V. (http://www.h-o.nl/) 3 | * See LICENSE.txt for license details. 4 | */ 5 | 6 | .ho-hint-container { 7 | outline: 0 dashed darkorange; 8 | } 9 | .ho-hint-container.ho-hint-hover { 10 | /*background-color: hsla(33, 100%, 50%, 0.1);*/ 11 | } 12 | 13 | .ho-hint-block { 14 | outline: 0 dashed hsl(195, 100%, 50%); 15 | } 16 | .ho-hint-block.ho-hint-hover { 17 | /*background-color: hsla(195, 100%, 50%, 0.1);*/ 18 | } 19 | 20 | .ho-hint-ui-component, .ho-hint-knockout { 21 | outline: 0 dashed hsl(269, 50%, 40%); 22 | } 23 | .ho-hint-ui-component.ho-hint-hover, 24 | .ho-hint-knockout.ho-hint-hover { 25 | /*background-color: hsla(269, 50%, 40%, 0.1);*/ 26 | } 27 | 28 | .ho-hint-outline { 29 | outline-width: 1px; 30 | min-height: 5px; 31 | } 32 | .ho-hint-hover { 33 | outline-style: solid; 34 | } 35 | -------------------------------------------------------------------------------- /view/base/web/js/hints.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * Copyright (c) 2016 H&O E-commerce specialisten B.V. (http://www.h-o.nl/) 3 | * See LICENSE.txt for license details. 4 | */ 5 | 6 | document.addEventListener("DOMContentLoaded", function (event) { 7 | require(['jquery', 'underscore', 'ko'], function ($, _, ko) { 8 | "use strict" 9 | 10 | $(document).keydown(function (event) { 11 | if (!event.shiftKey) { 12 | return 13 | } 14 | 15 | var hintElements = $('[data-ho-hinttype], .ho-hint') 16 | 17 | hintElements.each(function () { 18 | var hintElem = $(this); 19 | var hintType = hintElem.data('ho-hinttype'); 20 | hintElem.removeAttr('data-ho-hinttype'); 21 | 22 | hintElem.data('ho-hintdata'); 23 | hintElem.removeAttr('data-ho-hintdata') 24 | 25 | hintElem.addClass('ho-hint') 26 | .addClass('ho-hint-outline') 27 | .addClass('ho-hint-' + hintType) 28 | }); 29 | 30 | $('*').each(function(){ 31 | var knockoutData = ko.dataFor(this) 32 | if (!knockoutData) { 33 | return 34 | } 35 | 36 | var elem = $(this); 37 | var parentKnockoutData = ko.dataFor(this.parentElement); 38 | if (parentKnockoutData && knockoutData.component == parentKnockoutData.component) { 39 | return; 40 | } 41 | 42 | elem.addClass('ho-hint ho-hint-outline ho-hint-knockout') 43 | elem.data('ho-hinttype', 'knockout') 44 | 45 | }); 46 | }) 47 | 48 | 49 | //Remove styles when no 50 | $(document).keyup(function (event) { 51 | if (event.shiftKey) { 52 | return 53 | } 54 | 55 | $('.ho-hint').removeClass('ho-hint-outline ho-hint-hover') 56 | }) 57 | 58 | 59 | $(document).on('mouseover', '.ho-hint', function (event) { 60 | if (!event.shiftKey) { 61 | return 62 | } 63 | 64 | $(this).addClass('ho-hint-hover') 65 | }) 66 | 67 | 68 | $(document).on('mouseout', '.ho-hint', function (event) { 69 | if (!event.shiftKey) { 70 | return 71 | } 72 | 73 | $(this).removeClass('ho-hint-hover') 74 | }) 75 | 76 | 77 | $(document).on('click', '.ho-hint', function (event) { 78 | if (!event.shiftKey) { 79 | return 80 | } 81 | 82 | hint(this); 83 | 84 | return false 85 | }) 86 | 87 | window.hint = function (elem) { 88 | var hintElem = $(elem) 89 | var hintData = hintElem.data('ho-hintdata'); 90 | hintElem.removeAttr('data-ho-hintdata') 91 | var hintType = hintElem.data('ho-hinttype'); 92 | hintElem.removeAttr('data-ho-hinttype') 93 | 94 | if (hintType == 'knockout') { 95 | var knockoutData = ko.dataFor(elem); 96 | 97 | var script = $(`[data-requiremodule="${knockoutData.component}"]`)[0]; 98 | if (script) { 99 | var url = script.src; 100 | url = url.slice(url.indexOf('pub/')); 101 | } 102 | 103 | hintData = { 104 | info: [ 105 | knockoutData.name ? knockoutData.name : knockoutData.code, 106 | knockoutData.component 107 | ], 108 | paths: { 109 | 'template': url 110 | }, 111 | extra: { 112 | 'knockout': knockoutData 113 | } 114 | } 115 | } else if (typeof hintData != 'object') { 116 | console.log('can not parse as json', hintData) 117 | return 118 | } 119 | 120 | let hintTypeStyle = { 121 | 'block': 'background-color: hsl(195, 100%, 50%); padding:3px 6px; color:#fff; font-weight:bold;', 122 | 'container': 'background-color: darkorange; padding:3px 6px; color:#fff; font-weight:bold;', 123 | 'ui-component': 'background-color: hsl(269, 50%, 40%); padding:3px 6px; color:#fff; font-weight:bold;', 124 | 'knockout': 'background-color: hsl(269, 50%, 40%); padding:3px 6px; color:#fff; font-weight:bold;', 125 | } 126 | 127 | console.groupCollapsed( 128 | '%c'+hintType + ': ' + _.values(hintData.info).join(' | '), 129 | hintTypeStyle[hintType] 130 | ) 131 | 132 | _.each(hintData.paths, function(string, pathType) { 133 | pathType = pathType.charAt(0).toUpperCase() + pathType.slice(1); 134 | console.log( 135 | `%c${pathType}`, 136 | "font-weight:bold", 137 | `http://localhost:63342/api/file/${string}` 138 | ); 139 | }); 140 | 141 | _.each(hintData.extra, function(string, type) { 142 | type = type.charAt(0).toUpperCase() + type.slice(1); 143 | console.log( 144 | `%c${type}`, 145 | "font-weight:bold", 146 | string 147 | ); 148 | }); 149 | 150 | console.log(elem); 151 | 152 | console.groupEnd() 153 | } 154 | 155 | window.openTemplate = function (elem) { 156 | var hintElem = $(elem) 157 | var hintData = hintElem.data('ho-hintdata'); 158 | hintElem.removeAttr('data-ho-hintdata') 159 | 160 | if (hintData['absolutePath']) { 161 | $.get('http://localhost:63342/api/file/' + hintData['absolutePath']); 162 | } 163 | } 164 | }) 165 | }) 166 | --------------------------------------------------------------------------------