├── src ├── index.d.ts ├── background │ └── index.ts ├── options │ ├── index.html │ └── index.ts └── content │ └── index.ts ├── public ├── 0xwho-icon-48.png ├── content.css └── manifest.json ├── package.json ├── .gitignore ├── tsconfig.json ├── vite.config.ts ├── readme.md ├── .eslintrc ├── tampermonkey └── 0xwho.js └── yarn.lock /src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const chrome:any 2 | 3 | -------------------------------------------------------------------------------- /public/0xwho-icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jack-the-pug/0xwho/HEAD/public/0xwho-icon-48.png -------------------------------------------------------------------------------- /src/background/index.ts: -------------------------------------------------------------------------------- 1 | chrome.action.onClicked.addListener(() => { 2 | chrome.runtime.openOptionsPage() 3 | }) 4 | -------------------------------------------------------------------------------- /public/content.css: -------------------------------------------------------------------------------- 1 | #OXWHO-tip{ 2 | position:absolute; 3 | z-index: 2147483647; 4 | background: rgba(253,230,138,0.8); 5 | padding: 4px 5px; 6 | color: black; 7 | border-radius: 4px; 8 | margin-top: 5px; 9 | font-weight: 500; 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "0xwho", 3 | "private": true, 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "tsc && vite build", 7 | "start": "tsc && vite build --watch" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^17.0.32", 11 | "typescript": "^4.5.4", 12 | "vite": "^2.9.9", 13 | "vitest": "^0.12.9" 14 | } 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | 11 | node_modules 12 | extension 13 | dist 14 | dist-ssr 15 | *.local 16 | 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM","DOM.Iterable",], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": false, 12 | "esModuleInterop": true, 13 | "noEmit": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "skipLibCheck": true 18 | }, 19 | "include": ["src","src/index.d.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "0xWho?", 4 | "description": "Sorry, 0x who?", 5 | "version": "0.1.0", 6 | "icons": { 7 | "48":"0xwho-icon-48.png" 8 | }, 9 | "background":{ 10 | "service_worker": "js/background.js" 11 | }, 12 | "action": { 13 | 14 | }, 15 | "options_ui":{ 16 | "page":"src/options/index.html", 17 | "open_in_tab": true, 18 | "browser_style": true 19 | }, 20 | "permissions": ["storage"], 21 | "host_permissions": [ 22 | "*://*/*" 23 | ], 24 | "content_scripts": [{ 25 | "js": ["js/contentJs.js"], 26 | "css":["content.css"], 27 | "matches": [""], 28 | "run_at":"document_end" 29 | }] 30 | } 31 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vite' 2 | import {resolve} from 'path' 3 | export default defineConfig({ 4 | test:{}, 5 | build: { 6 | outDir: resolve(__dirname,'extension'), 7 | emptyOutDir: true, 8 | terserOptions: { 9 | mangle: false, 10 | }, 11 | rollupOptions: { 12 | input: { 13 | background: resolve(__dirname,'src/background/index.ts'), 14 | options: resolve(__dirname,'src/options/index.html'), 15 | optionsJs: resolve(__dirname,'src/options/index.ts'), 16 | contentJs: resolve(__dirname,'src/content/index.ts'), 17 | }, 18 | output: { 19 | entryFileNames:(chunk) => { 20 | return `js/${chunk.name}.js` 21 | } 22 | }, 23 | }, 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 0xWho? 2 | 3 | ## Features 4 | 5 | - Inline Address Labeling 6 | 7 | ![inline address labeling; before & after 0xwho](https://user-images.githubusercontent.com/84883138/174195088-141e26af-d2c7-4ecd-b949-6aafe5a2f90f.png) 8 | 9 | - Tooltip on Selection 10 | 11 | ![select a address and display a tooltip](https://user-images.githubusercontent.com/84883138/174195547-45d678dd-3c75-45c0-88d6-8efd6cd3356e.png) 12 | 13 | 14 | ## How can use it? 15 | 16 | 1. clone the repo; 17 | 2. install deps: 18 | ```bash 19 | yarn 20 | ``` 21 | 3. build it: 22 | ```bash 23 | yarn build 24 | ``` 25 | 3. go to `chrome://extensions/`; 26 | 4. enable `Developer mode`; 27 | 5. `Load unpacked` and select the `extenstion` folder; 28 | 29 | ## Why not make the extenstion available through the chrome web store? 30 | 31 | It's dangerous to use a chrome ext that can tamper the content of your webpages. 32 | 33 | So, read the code and build it yourself. And also don't forget to drink plenty of water today and always get enough sleep. 34 | ## 🐒 0xWho: lite version 35 | 36 | The 0xWho ext tries to be lightweight as much as possible, but if you are super busy, we have a even lighter version for you: 37 | 38 | Introducing: 0xWho, the Tampermonkey script version. 39 | 40 | Now available at [greasyfork.org](https://greasyfork.org/en/scripts/446598-0xwho). -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 2018, 5 | "jsx": true, 6 | "sourceType": "module", 7 | "useJSXTextNode": true 8 | }, 9 | "extends": [ 10 | "eslint-config-airbnb-base", 11 | "eslint-config-airbnb-base/rules/strict", 12 | "eslint-config-airbnb/rules/react", 13 | "plugin:@typescript-eslint/recommended", 14 | "prettier/@typescript-eslint", 15 | "plugin:prettier/recommended" 16 | ], 17 | "env": { 18 | "browser": true, 19 | "es6": true, 20 | "jest/globals": true 21 | }, 22 | "plugins": ["jest", "@typescript-eslint", "react-intl", "class-property", "simple-import-sort"], 23 | "rules": { 24 | "keyword-spacing":["error", { "before": true,"affter":true }], 25 | "react/jsx-filename-extension": "off", 26 | "max-len": "off", 27 | "semi": ["error", "never"], 28 | "comma-dangle": 0, 29 | "import/prefer-default-export": "off", 30 | "no-console": "off", 31 | "react/jsx-wrap-multilines": "off", 32 | "react/prefer-stateless-function": "off", 33 | "react/no-array-index-key": "off", 34 | "camelcase": "off", 35 | "dot-notation": "off", 36 | "no-use-before-define": "off", 37 | "arrow-parens": "off", 38 | "import/no-extraneous-dependencies": "off", 39 | "react/jsx-no-bind": "off", 40 | "consistent-return": "off", 41 | "react/sort-comp": "off", 42 | "jsx-a11y/accessible-emoji": "off", 43 | "no-plusplus": "off", 44 | "no-case-declarations": "off", 45 | "no-continue": "off", 46 | "new-cap": "off", 47 | "import/no-named-as-default": "off", 48 | "react/require-default-props": "off", 49 | "no-new": "off", 50 | "indnet": "off", 51 | "no-nested-ternary": "off", 52 | "simple-import-sort/sort": "warn", 53 | "no-underscore-dangle": "off", 54 | "no-template-curly-in-string": "off", 55 | "prefer-destructuring": "off", 56 | "react-intl/no-unwrapped-jsx-text": 2, 57 | "react/destructuring-assignment": "off", 58 | "react/prop-types": "off", 59 | "react/jsx-one-expression-per-line": "off", 60 | "@typescript-eslint/no-explicit-any": "off", 61 | "@typescript-eslint/member-delimiter-style": "off", 62 | "@typescript-eslint/explicit-function-return-type": "off", 63 | "@typescript-eslint/camelcase": "off" 64 | 65 | }, 66 | "overrides": [ 67 | { 68 | "files": ["**/*.test.ts"], 69 | "env": { 70 | "jest": true 71 | } 72 | } 73 | ], 74 | "settings": { 75 | "import/resolver": { 76 | "node": { 77 | "paths": ["src/**"], 78 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 0xWho Address Book 8 | 75 | 76 | 77 |

0xWho Address Book

78 |
79 | 80 | 81 | 82 |
83 |
84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
AddressNameAction
95 | 96 | 97 |

you sure?

98 |
99 |
100 | 101 | 102 |
103 |
104 |
105 | 106 |
107 | 108 | 109 |
110 |
111 | 112 | 113 |
114 |
115 |
116 | 117 | 118 | -------------------------------------------------------------------------------- /src/options/index.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | interface Profile { 4 | name:string 5 | address:string 6 | } 7 | interface TDialog extends HTMLDialogElement{ 8 | showModal:() => void 9 | close:() => void 10 | } 11 | const browser = chrome 12 | let currentProfile:Profile | null = null 13 | let removeDialog:TDialog | null = null 14 | let editDialog:TDialog | null = null 15 | 16 | async function getContactsFromStorage(){ 17 | const res = await browser.storage.sync.get() 18 | const arr = [] 19 | for(const key in res){ 20 | arr.push(res[key]) 21 | } 22 | return arr 23 | } 24 | 25 | function removeHandler(profile:Profile){ 26 | currentProfile = profile 27 | removeDialog && removeDialog.showModal() 28 | } 29 | 30 | function editHandler(profile:Profile){ 31 | currentProfile = profile 32 | editDialog && editDialog.showModal() 33 | const form = document.getElementById('editForm') as HTMLFormElement 34 | form.reset() 35 | // @ts-ignore 36 | form.elements.name.value = currentProfile.name 37 | // @ts-ignore 38 | form.elements.address.value = currentProfile.address 39 | } 40 | 41 | const makeProfile = (profile:Profile):HTMLDivElement => { 42 | const tr = document.createElement('tr') 43 | tr.className = 'profile' 44 | tr.innerHTML = ` 45 | ${profile.address} 46 | ${profile.name} 47 | ` 48 | const removeTd = document.createElement('td') 49 | const remove = document.createElement('button') 50 | remove.innerText = 'remove' 51 | remove.onclick = () => removeHandler(profile) 52 | removeTd.appendChild(remove) 53 | 54 | const editTd = document.createElement('td') 55 | const edit = document.createElement('button') 56 | edit.innerText = 'edit' 57 | edit.onclick = () => editHandler(profile) 58 | editTd.appendChild(edit) 59 | tr.appendChild(removeTd) 60 | tr.appendChild(editTd) 61 | return tr 62 | } 63 | 64 | async function Render(){ 65 | const list = await getContactsFromStorage() 66 | const container = document.getElementById('container') 67 | if (list.length > 0) { 68 | container!.innerHTML = '' 69 | list.forEach((p:Profile) => { 70 | const div = makeProfile(p) 71 | container!.appendChild(div) 72 | }) 73 | } else { 74 | container!.innerHTML = 'No data' 75 | } 76 | } 77 | 78 | 79 | const f = (t:string) => t.trim().toLowerCase() 80 | function handleSubmit(e:Event,form:HTMLFormElement){ 81 | e.preventDefault() 82 | const data = new FormData(form).entries() 83 | const profile:any = {} 84 | for(const [key,value] of data){ 85 | profile[key] = value 86 | } 87 | 88 | profile.address = f(profile.address) 89 | if(!/^0x[a-fA-F0-9]{40}$/.test(profile.address)){ 90 | alert('invalid address') 91 | return false 92 | } 93 | browser.storage.sync.set({[profile.address.toLowerCase()]:profile}) 94 | form.reset() 95 | Render() 96 | return true 97 | } 98 | 99 | window.onload = async () => { 100 | const form = document.getElementById('formContent') as HTMLFormElement 101 | form.addEventListener('submit', (e) => handleSubmit(e,form)) 102 | 103 | Render() 104 | 105 | removeDialog = document.getElementById('removeDialog') as TDialog 106 | removeDialog.addEventListener('close', () => currentProfile = null) 107 | removeDialog.addEventListener('cancel', () => currentProfile = null) 108 | removeDialog.addEventListener('submit', (e) => { 109 | const submiter = e.submitter 110 | if(submiter!.id === 'removeDialogConfirmBtn'){ 111 | browser.storage.sync.remove(currentProfile?.address) 112 | currentProfile = null 113 | Render() 114 | } 115 | }) 116 | 117 | 118 | editDialog = document.getElementById('editDialog') as TDialog 119 | editDialog.addEventListener('close', () => currentProfile = null) 120 | editDialog.addEventListener('cancel', () => currentProfile = null) 121 | editDialog.addEventListener('submit', (e) => { 122 | const submiter = e.submitter 123 | if(submiter!.id === 'editDialogSubmitButton'){ 124 | browser.storage.sync.remove(currentProfile?.address) 125 | const r = handleSubmit(e,document.getElementById('editForm') as HTMLFormElement) 126 | r && editDialog?.close() 127 | } 128 | }) 129 | } 130 | 131 | 132 | -------------------------------------------------------------------------------- /src/content/index.ts: -------------------------------------------------------------------------------- 1 | interface OXWHOProfile { 2 | name: string 3 | address: string 4 | } 5 | interface OXWHOTooltipPosition { 6 | x: number 7 | y: number 8 | w: number 9 | h: number 10 | } 11 | 12 | interface TElement extends Element{ 13 | OXWHO_text?: string 14 | OXWHO_address?: string 15 | } 16 | 17 | export class OXWHOInject{ 18 | public browser: any 19 | 20 | public addressMap: Map = new Map() 21 | public formatAddressMap: Map = new Map() 22 | private tooltipEl: HTMLDivElement = document.createElement('div') 23 | private tooltipTemplate = ` 24 |
{{NAME}}
25 | ` 26 | private sourceDoms:Map = new Map() 27 | constructor() { 28 | this.init() 29 | } 30 | private async init() { 31 | this.browser = chrome 32 | this.tooltipEl.id = 'OXWHO-tip' 33 | await this.setAddressMap() 34 | 35 | window.addEventListener('load', () => { 36 | let timer = setTimeout(() => { 37 | this.changeDom() 38 | clearTimeout(timer) 39 | }, 1000) 40 | }) 41 | 42 | document.addEventListener('selectionchange', () => this.handleSelectionchange()) 43 | 44 | this.browser.storage.onChanged.addListener(async () => { 45 | this.sourceDoms.forEach((_,dom) => { 46 | dom.textContent = dom.OXWHO_text! 47 | }) 48 | await this.setAddressMap() 49 | this.changeDom() 50 | }) 51 | 52 | document.addEventListener('hashchange', this.changeDom) 53 | document.addEventListener('popstate', this.changeDom) 54 | document.addEventListener('pushstate', this.changeDom) 55 | 56 | let timer:ReturnType 57 | document.addEventListener('scroll', () => { 58 | if (timer) clearTimeout(timer) 59 | timer = setTimeout(() => { 60 | this.changeDom() 61 | clearTimeout(timer) 62 | }, 200) 63 | }) 64 | } 65 | async setAddressMap() { 66 | const contacts = await this.browser.storage.sync.get() 67 | for (const addressKey in contacts) { 68 | const profile = contacts[addressKey] 69 | this.addressMap.set(profile.address,profile) 70 | const address = profile.address.toLowerCase() 71 | const formatStr = this.replaceAddressByName(address,profile?.name).toLowerCase() 72 | this.formatAddressMap.set(address,formatStr) 73 | this.formatAddressMap.set(formatStr,address) 74 | } 75 | } 76 | replaceAddressByName(address:string, name:string) { 77 | const nameLen = name.length 78 | return nameLen <= 36 ? `0x${name}_${address.substring(nameLen + 3)}` : `0x${name.substring(0,35)}_${address.substring(38)}` 79 | } 80 | handleSelectionchange () { 81 | const selection = this.getSelectedMeta() 82 | if (!selection) return 83 | let [text,el] = selection 84 | if (this.isAddress(text)) { 85 | text = text.toLowerCase() 86 | if (el.childNodes.length === 0) el = el.parentElement! 87 | const profile = this.addressMap.get(text.toLowerCase()) 88 | if(profile){ 89 | const position = this.getPosition(el) 90 | this.addTooltip(position,profile) 91 | } 92 | return 93 | } 94 | 95 | if (!this.formatAddressMap.has(text.toLowerCase())) return 96 | const address = this.formatAddressMap.get(text.toLowerCase())! 97 | 98 | if (this.isAddress(address)) { 99 | // @ts-ignore 100 | el.textContent = el.textContent?.replace(text, el.OXWHO_address)! 101 | const selc = window.getSelection() 102 | selc?.removeAllRanges() 103 | const range = document.createRange() 104 | range.selectNode(el) 105 | selc?.addRange(range) 106 | } 107 | } 108 | isAddress(text:string, strict:boolean = true): boolean { 109 | return strict ? /^0x[a-fA-F0-9]{40}$/.test(text) : /0x[a-fA-F0-9]{40}/g.test(text) 110 | } 111 | getPosition(el:HTMLElement): {x:number,y:number,h:number,w:number} { 112 | const rect = el.getBoundingClientRect() 113 | const style = window.getComputedStyle(el) 114 | return { 115 | y: rect.y + window.scrollY + rect.height - parseFloat(style.paddingBottom), 116 | x: rect.x + window.scrollX + parseFloat(style.paddingLeft), 117 | h: rect.height, 118 | w: rect.width 119 | } 120 | } 121 | private getSelectedMeta(): null | [string,HTMLElement] { 122 | const selection = window.getSelection() 123 | if (!selection) return null 124 | let text = selection.toString().trim() 125 | if (!text && document.getElementById('OXWHO-tip')) { 126 | document.body.removeChild(this.tooltipEl) 127 | return null 128 | } 129 | let el = selection.getRangeAt(0).startContainer 130 | if (el.nodeName === '#text' && el.nodeType === 3) el = el.parentElement as HTMLElement 131 | return [text,el as HTMLElement] 132 | } 133 | private getAddressDoms(): Map { 134 | const doms = document.querySelectorAll('* :not(script) :not(style) :not(a) :not(img) :not(input) :not(textarea) ') 135 | const nodeMap:Map = new Map() 136 | doms.forEach((dom:TElement) => { 137 | let text = dom.textContent?.trim() 138 | dom.OXWHO_text = text 139 | if (!text || dom.childNodes.length !== 1 || dom.firstChild?.nodeName !== '#text') return 140 | // there can be multiple matches (addresses) in one element 141 | const textIter = text.matchAll(/0x[a-fA-F0-9]{40}/g) 142 | for (let matchRes of textIter) { 143 | const address = matchRes[0] 144 | if (this.addressMap.has(address.toLowerCase())) { 145 | // save the original address, for later revertion of the inline labeling edit 146 | dom.OXWHO_address = address 147 | nodeMap.set(dom, dom.textContent!) 148 | } 149 | } 150 | }) 151 | return nodeMap 152 | } 153 | changeDom() { 154 | const doms = this.getAddressDoms() 155 | this.sourceDoms = doms 156 | doms.forEach((text:string,node:TElement,)=> { 157 | const matchTextAddress = text.matchAll(/0x[a-fA-F0-9]{40}/g) 158 | for (const matchRes of matchTextAddress) { 159 | const address = matchRes[0] 160 | if (this.formatAddressMap.has(address.toLowerCase())) { 161 | const formatAddress = this.replaceAddressByName(node.OXWHO_address!,this.addressMap.get(address.toLowerCase())!.name) 162 | text= text.replace(address,formatAddress) 163 | } 164 | } 165 | node.textContent =text 166 | }) 167 | } 168 | addTooltip(position:OXWHOTooltipPosition, profile:OXWHOProfile) { 169 | const {x,y,w} = position 170 | this.tooltipEl.style.left = `${x + w/2}px` 171 | this.tooltipEl.style.top = `${y}px` 172 | this.tooltipEl.innerHTML = this.tooltipTemplate.replace('{{NAME}}',profile.name) 173 | document.body.appendChild(this.tooltipEl) 174 | } 175 | } 176 | 177 | 178 | new OXWHOInject() -------------------------------------------------------------------------------- /tampermonkey/0xwho.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name 0xWho 3 | // @namespace https://github.com/jack-the-pug/0xwho 4 | // @version 0.1 5 | // @description Sorry, 0x who? 6 | // @author JtP 7 | // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAS1BMVEUAAAC+Ngv/ckG/NQz/b0O/Ngz/cEO/Ng3/cEOrMAuyNQ+zNhCzNhG7PBW/NgzBQRrDQhvFRBzHRR7OSyLaUyr1aT34aj77bUD/cEOHzN+4AAAACXRSTlMALy+np/Ly9PT89qvQAAAAkklEQVR42uzVQwLDQABA0bodG/e/aB3b/Ou8aHRYUcfzlVJf2O1ySl1/p7QI/HokxZlWAX9JgGs1uCUArQZ+CrADgbFoAsC3+kD+gKwN0A+g2gD/AK4NxA+IYT76l0RIzmqkjeYQcm1qAqfAP+XqAEtAGLE1AAOxWA0AEjUG812iO2h+oDQ/sl4jplIkudodPgAA1tB1J/uFI7EAAAAASUVORK5CYII= 8 | // @grant none 9 | // @match https://*/* 10 | // @run-at document-end 11 | // ==/UserScript== 12 | 13 | (function() { 14 | 'use strict'; 15 | 16 | // ==== THIS IS YOUR ADDRESS BOOK ==== 17 | const addressBook = { 18 | // "address" "name" 19 | "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045": "vitalik", 20 | } 21 | // ==== /END OF YOUR ADDRESS BOOK ==== 22 | 23 | class OXWHOInject{ 24 | constructor(){ 25 | this.addressMap = new Map() 26 | for(const address in addressBook){ 27 | this.addressMap.set(address.toLowerCase(),{ 28 | name:addressBook[address], 29 | address:address 30 | }) 31 | } 32 | this.sourceDoms= new Map() 33 | this.formatAddressMap = new Map() 34 | this.addressMap.forEach(({name}, address) => { 35 | const formatAddress = this.replaceAddressByName(address,name).toLowerCase() 36 | this.formatAddressMap.set(address,formatAddress) 37 | this.formatAddressMap.set(formatAddress,address) 38 | }) 39 | this.tooltipEl = document.createElement('div') 40 | this.tooltipEl.id = 'OXWHO-tip' 41 | this.tooltipTemplate = ` 42 |
{{NAME}}
43 | ` 44 | this.init() 45 | } 46 | init(){ 47 | this.injectStyle() 48 | window.addEventListener('load',() => { 49 | let timer = setTimeout(() => { 50 | this.changeDom() 51 | clearTimeout(timer) 52 | },1000) 53 | }) 54 | document.addEventListener('selectionchange',() => this.handleSelectionchange()) 55 | 56 | document.addEventListener('hashchange',this.changeDom) 57 | document.addEventListener('popstate',this.changeDom) 58 | document.addEventListener('pushstate',this.changeDom) 59 | 60 | let timer 61 | document.addEventListener('scroll',() => { 62 | if(timer) clearTimeout(timer) 63 | timer = setTimeout(() => { 64 | this.changeDom() 65 | clearTimeout(timer) 66 | },200) 67 | }) 68 | } 69 | replaceAddressByName(address,name){ 70 | const nameLen = name.length 71 | return nameLen <= 36 ? `0x${name}_${address.substring(nameLen + 3)}` : `0x${name.substring(0,35)}_${address.substring(38)}` 72 | } 73 | isAddress(text,strict = true){ 74 | return strict ? /^0x[a-fA-F0-9]{40}$/.test(text) : /0x[a-fA-F0-9]{40}/g.test(text) 75 | } 76 | getAddressDoms(){ 77 | const doms = document.querySelectorAll('* :not(script) :not(style) :not(a) :not(img) :not(input) :not(textarea) ') 78 | const nodeMap = new Map() 79 | doms.forEach((dom) => { 80 | let text = dom.textContent?.trim() 81 | if(!text || dom.childNodes.length !== 1 || dom.firstChild?.nodeName !== '#text') return 82 | // there can be multiple matches (addresses) in one element 83 | const textIter = text.matchAll(/0x[a-fA-F0-9]{40}/g) 84 | for(let matchRes of textIter){ 85 | const address = matchRes[0] 86 | if(this.addressMap.has(address.toLowerCase())){ 87 | // save the original address, for later revertion of the inline labeling edit 88 | dom.OXWHO_address = address 89 | nodeMap.set(dom, dom.textContent) 90 | } 91 | } 92 | }) 93 | return nodeMap 94 | } 95 | changeDom(){ 96 | const doms = this.getAddressDoms() 97 | this.sourceDoms = doms 98 | doms.forEach((text,node)=> { 99 | const matchTextAddress = text.matchAll(/0x[a-fA-F0-9]{40}/g) 100 | for(const matchRes of matchTextAddress){ 101 | const address = matchRes[0] 102 | if(this.formatAddressMap.has(address.toLowerCase())){ 103 | const formatAddress = this.replaceAddressByName(node.OXWHO_address,this.addressMap.get(address.toLowerCase())?.name) 104 | text= text.replace(address,formatAddress) 105 | } 106 | } 107 | node.textContent =text 108 | }) 109 | } 110 | getSelectedMeta(){ 111 | const selection = window.getSelection() 112 | if(!selection) return null 113 | let text = selection.toString().trim() 114 | if(!text && document.getElementById('OXWHO-tip')){ 115 | document.body.removeChild(this.tooltipEl) 116 | return null 117 | } 118 | let el = selection.getRangeAt(0).startContainer 119 | if(el.nodeName === '#text' && el.nodeType === 3) el = el.parentElement 120 | return [text,el] 121 | } 122 | handleSelectionchange () { 123 | const selection = this.getSelectedMeta() 124 | if(!selection) return 125 | let [text,el] = selection 126 | if(this.isAddress(text)){ 127 | text = text.toLowerCase() 128 | if(el.childNodes.length === 0) el = el.parentElement 129 | const profile = this.addressMap.get(text.toLowerCase()) 130 | if(profile){ 131 | const position = this.getPosition(el) 132 | this.addTooltip(position,profile) 133 | } 134 | return 135 | } 136 | 137 | if(!this.formatAddressMap.has(text.toLowerCase())) return 138 | const address = this.formatAddressMap.get(text.toLowerCase()) 139 | 140 | if (this.isAddress(address)) { 141 | // @ts-ignore 142 | // must use textContent to replace 143 | el.textContent = el.textContent?.replace(text, el.OXWHO_address) 144 | const selc = window.getSelection() 145 | selc?.removeAllRanges() 146 | const range = document.createRange() 147 | range.selectNode(el) 148 | selc?.addRange(range) 149 | } 150 | } 151 | addTooltip(position, profile){ 152 | const {x,y,w} = position 153 | this.tooltipEl.style.left = `${x + w/2}px` 154 | this.tooltipEl.style.top = `${y}px` 155 | this.tooltipEl.innerHTML = this.tooltipTemplate.replace('{{NAME}}',profile.name) 156 | document.body.appendChild(this.tooltipEl) 157 | } 158 | getPosition(el){ 159 | const rect = el.getBoundingClientRect() 160 | const style = window.getComputedStyle(el) 161 | return { 162 | y: rect.y + window.scrollY + rect.height - parseFloat(style.paddingBottom), 163 | x: rect.x + window.scrollX + parseFloat(style.paddingLeft), 164 | h: rect.height, 165 | w: rect.width 166 | } 167 | } 168 | injectStyle(){ 169 | const style = document.createElement('style') 170 | style.textContent = ` 171 | #OXWHO-tip{ 172 | position:absolute; 173 | z-index: 2147483647; 174 | background: rgba(253,230,138,0.8); 175 | padding: 4px 5px; 176 | color: black; 177 | border-radius: 4px; 178 | margin-top: 5px; 179 | font-weight: 500; 180 | font-size: 13px; 181 | } 182 | ` 183 | document.head.appendChild(style) 184 | } 185 | } 186 | 187 | 188 | new OXWHOInject() 189 | 190 | })() -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/chai-subset@^1.3.3": 6 | version "1.3.3" 7 | resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94" 8 | integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw== 9 | dependencies: 10 | "@types/chai" "*" 11 | 12 | "@types/chai@*", "@types/chai@^4.3.1": 13 | version "4.3.1" 14 | resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04" 15 | integrity sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ== 16 | 17 | "@types/node@^17.0.32": 18 | version "17.0.33" 19 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.33.tgz#3c1879b276dc63e73030bb91165e62a4509cd506" 20 | integrity sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ== 21 | 22 | assertion-error@^1.1.0: 23 | version "1.1.0" 24 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" 25 | integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== 26 | 27 | chai@^4.3.6: 28 | version "4.3.6" 29 | resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" 30 | integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q== 31 | dependencies: 32 | assertion-error "^1.1.0" 33 | check-error "^1.0.2" 34 | deep-eql "^3.0.1" 35 | get-func-name "^2.0.0" 36 | loupe "^2.3.1" 37 | pathval "^1.1.1" 38 | type-detect "^4.0.5" 39 | 40 | check-error@^1.0.2: 41 | version "1.0.2" 42 | resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 43 | integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== 44 | 45 | debug@^4.3.4: 46 | version "4.3.4" 47 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 48 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 49 | dependencies: 50 | ms "2.1.2" 51 | 52 | deep-eql@^3.0.1: 53 | version "3.0.1" 54 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" 55 | integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== 56 | dependencies: 57 | type-detect "^4.0.0" 58 | 59 | esbuild-android-64@0.14.39: 60 | version "0.14.39" 61 | resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.39.tgz#09f12a372eed9743fd77ff6d889ac14f7b340c21" 62 | integrity sha512-EJOu04p9WgZk0UoKTqLId9VnIsotmI/Z98EXrKURGb3LPNunkeffqQIkjS2cAvidh+OK5uVrXaIP229zK6GvhQ== 63 | 64 | esbuild-android-arm64@0.14.39: 65 | version "0.14.39" 66 | resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.39.tgz#f608d00ea03fe26f3b1ab92a30f99220390f3071" 67 | integrity sha512-+twajJqO7n3MrCz9e+2lVOnFplRsaGRwsq1KL/uOy7xK7QdRSprRQcObGDeDZUZsacD5gUkk6OiHiYp6RzU3CA== 68 | 69 | esbuild-darwin-64@0.14.39: 70 | version "0.14.39" 71 | resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.39.tgz#31528daa75b4c9317721ede344195163fae3e041" 72 | integrity sha512-ImT6eUw3kcGcHoUxEcdBpi6LfTRWaV6+qf32iYYAfwOeV+XaQ/Xp5XQIBiijLeo+LpGci9M0FVec09nUw41a5g== 73 | 74 | esbuild-darwin-arm64@0.14.39: 75 | version "0.14.39" 76 | resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.39.tgz#247f770d86d90a215fa194f24f90e30a0bd97245" 77 | integrity sha512-/fcQ5UhE05OiT+bW5v7/up1bDsnvaRZPJxXwzXsMRrr7rZqPa85vayrD723oWMT64dhrgWeA3FIneF8yER0XTw== 78 | 79 | esbuild-freebsd-64@0.14.39: 80 | version "0.14.39" 81 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.39.tgz#479414d294905055eb396ebe455ed42213284ee0" 82 | integrity sha512-oMNH8lJI4wtgN5oxuFP7BQ22vgB/e3Tl5Woehcd6i2r6F3TszpCnNl8wo2d/KvyQ4zvLvCWAlRciumhQg88+kQ== 83 | 84 | esbuild-freebsd-arm64@0.14.39: 85 | version "0.14.39" 86 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.39.tgz#cedeb10357c88533615921ae767a67dc870a474c" 87 | integrity sha512-1GHK7kwk57ukY2yI4ILWKJXaxfr+8HcM/r/JKCGCPziIVlL+Wi7RbJ2OzMcTKZ1HpvEqCTBT/J6cO4ZEwW4Ypg== 88 | 89 | esbuild-linux-32@0.14.39: 90 | version "0.14.39" 91 | resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.39.tgz#d9f008c4322d771f3958f59c1eee5a05cdf92485" 92 | integrity sha512-g97Sbb6g4zfRLIxHgW2pc393DjnkTRMeq3N1rmjDUABxpx8SjocK4jLen+/mq55G46eE2TA0MkJ4R3SpKMu7dg== 93 | 94 | esbuild-linux-64@0.14.39: 95 | version "0.14.39" 96 | resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.39.tgz#ba58d7f66858913aeb1ab5c6bde1bbd824731795" 97 | integrity sha512-4tcgFDYWdI+UbNMGlua9u1Zhu0N5R6u9tl5WOM8aVnNX143JZoBZLpCuUr5lCKhnD0SCO+5gUyMfupGrHtfggQ== 98 | 99 | esbuild-linux-arm64@0.14.39: 100 | version "0.14.39" 101 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.39.tgz#708785a30072702b5b1c16b65cf9c25c51202529" 102 | integrity sha512-23pc8MlD2D6Px1mV8GMglZlKgwgNKAO8gsgsLLcXWSs9lQsCYkIlMo/2Ycfo5JrDIbLdwgP8D2vpfH2KcBqrDQ== 103 | 104 | esbuild-linux-arm@0.14.39: 105 | version "0.14.39" 106 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.39.tgz#4e8b5deaa7ab60d0d28fab131244ef82b40684f4" 107 | integrity sha512-t0Hn1kWVx5UpCzAJkKRfHeYOLyFnXwYynIkK54/h3tbMweGI7dj400D1k0Vvtj2u1P+JTRT9tx3AjtLEMmfVBQ== 108 | 109 | esbuild-linux-mips64le@0.14.39: 110 | version "0.14.39" 111 | resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.39.tgz#6f3bf3023f711084e5a1e8190487d2020f39f0f7" 112 | integrity sha512-epwlYgVdbmkuRr5n4es3B+yDI0I2e/nxhKejT9H0OLxFAlMkeQZxSpxATpDc9m8NqRci6Kwyb/SfmD1koG2Zuw== 113 | 114 | esbuild-linux-ppc64le@0.14.39: 115 | version "0.14.39" 116 | resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.39.tgz#900e718a4ea3f6aedde8424828eeefdd4b48d4b9" 117 | integrity sha512-W/5ezaq+rQiQBThIjLMNjsuhPHg+ApVAdTz2LvcuesZFMsJoQAW2hutoyg47XxpWi7aEjJGrkS26qCJKhRn3QQ== 118 | 119 | esbuild-linux-riscv64@0.14.39: 120 | version "0.14.39" 121 | resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.39.tgz#dcbff622fa37047a75d2ff7a1d8d2949d80277e4" 122 | integrity sha512-IS48xeokcCTKeQIOke2O0t9t14HPvwnZcy+5baG13Z1wxs9ZrC5ig5ypEQQh4QMKxURD5TpCLHw2W42CLuVZaA== 123 | 124 | esbuild-linux-s390x@0.14.39: 125 | version "0.14.39" 126 | resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.39.tgz#3f725a7945b419406c99d93744b28552561dcdfd" 127 | integrity sha512-zEfunpqR8sMomqXhNTFEKDs+ik7HC01m3M60MsEjZOqaywHu5e5682fMsqOlZbesEAAaO9aAtRBsU7CHnSZWyA== 128 | 129 | esbuild-netbsd-64@0.14.39: 130 | version "0.14.39" 131 | resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.39.tgz#e10e40b6a765798b90d4eb85901cc85c8b7ff85e" 132 | integrity sha512-Uo2suJBSIlrZCe4E0k75VDIFJWfZy+bOV6ih3T4MVMRJh1lHJ2UyGoaX4bOxomYN3t+IakHPyEoln1+qJ1qYaA== 133 | 134 | esbuild-openbsd-64@0.14.39: 135 | version "0.14.39" 136 | resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.39.tgz#935ec143f75ce10bd9cdb1c87fee00287eb0edbc" 137 | integrity sha512-secQU+EpgUPpYjJe3OecoeGKVvRMLeKUxSMGHnK+aK5uQM3n1FPXNJzyz1LHFOo0WOyw+uoCxBYdM4O10oaCAA== 138 | 139 | esbuild-sunos-64@0.14.39: 140 | version "0.14.39" 141 | resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.39.tgz#0e7aa82b022a2e6d55b0646738b2582c2d72c3c0" 142 | integrity sha512-qHq0t5gePEDm2nqZLb+35p/qkaXVS7oIe32R0ECh2HOdiXXkj/1uQI9IRogGqKkK+QjDG+DhwiUw7QoHur/Rwg== 143 | 144 | esbuild-windows-32@0.14.39: 145 | version "0.14.39" 146 | resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.39.tgz#3f1538241f31b538545f4b5841b248cac260fa35" 147 | integrity sha512-XPjwp2OgtEX0JnOlTgT6E5txbRp6Uw54Isorm3CwOtloJazeIWXuiwK0ONJBVb/CGbiCpS7iP2UahGgd2p1x+Q== 148 | 149 | esbuild-windows-64@0.14.39: 150 | version "0.14.39" 151 | resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.39.tgz#b100c59f96d3c2da2e796e42fee4900d755d3e03" 152 | integrity sha512-E2wm+5FwCcLpKsBHRw28bSYQw0Ikxb7zIMxw3OPAkiaQhLVr3dnVO8DofmbWhhf6b97bWzg37iSZ45ZDpLw7Ow== 153 | 154 | esbuild-windows-arm64@0.14.39: 155 | version "0.14.39" 156 | resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.39.tgz#00268517e665b33c89778d61f144e4256b39f631" 157 | integrity sha512-sBZQz5D+Gd0EQ09tZRnz/PpVdLwvp/ufMtJ1iDFYddDaPpZXKqPyaxfYBLs3ueiaksQ26GGa7sci0OqFzNs7KA== 158 | 159 | esbuild@^0.14.27: 160 | version "0.14.39" 161 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.39.tgz#c926b2259fe6f6d3a94f528fb42e103c5a6d909a" 162 | integrity sha512-2kKujuzvRWYtwvNjYDY444LQIA3TyJhJIX3Yo4+qkFlDDtGlSicWgeHVJqMUP/2sSfH10PGwfsj+O2ro1m10xQ== 163 | optionalDependencies: 164 | esbuild-android-64 "0.14.39" 165 | esbuild-android-arm64 "0.14.39" 166 | esbuild-darwin-64 "0.14.39" 167 | esbuild-darwin-arm64 "0.14.39" 168 | esbuild-freebsd-64 "0.14.39" 169 | esbuild-freebsd-arm64 "0.14.39" 170 | esbuild-linux-32 "0.14.39" 171 | esbuild-linux-64 "0.14.39" 172 | esbuild-linux-arm "0.14.39" 173 | esbuild-linux-arm64 "0.14.39" 174 | esbuild-linux-mips64le "0.14.39" 175 | esbuild-linux-ppc64le "0.14.39" 176 | esbuild-linux-riscv64 "0.14.39" 177 | esbuild-linux-s390x "0.14.39" 178 | esbuild-netbsd-64 "0.14.39" 179 | esbuild-openbsd-64 "0.14.39" 180 | esbuild-sunos-64 "0.14.39" 181 | esbuild-windows-32 "0.14.39" 182 | esbuild-windows-64 "0.14.39" 183 | esbuild-windows-arm64 "0.14.39" 184 | 185 | fsevents@~2.3.2: 186 | version "2.3.2" 187 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 188 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 189 | 190 | function-bind@^1.1.1: 191 | version "1.1.1" 192 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 193 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 194 | 195 | get-func-name@^2.0.0: 196 | version "2.0.0" 197 | resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" 198 | integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== 199 | 200 | has@^1.0.3: 201 | version "1.0.3" 202 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 203 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 204 | dependencies: 205 | function-bind "^1.1.1" 206 | 207 | is-core-module@^2.8.1: 208 | version "2.9.0" 209 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" 210 | integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== 211 | dependencies: 212 | has "^1.0.3" 213 | 214 | local-pkg@^0.4.1: 215 | version "0.4.1" 216 | resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.1.tgz#e7b0d7aa0b9c498a1110a5ac5b00ba66ef38cfff" 217 | integrity sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw== 218 | 219 | loupe@^2.3.1: 220 | version "2.3.4" 221 | resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" 222 | integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ== 223 | dependencies: 224 | get-func-name "^2.0.0" 225 | 226 | ms@2.1.2: 227 | version "2.1.2" 228 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 229 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 230 | 231 | nanoid@^3.3.3: 232 | version "3.3.4" 233 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" 234 | integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== 235 | 236 | path-parse@^1.0.7: 237 | version "1.0.7" 238 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 239 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 240 | 241 | pathval@^1.1.1: 242 | version "1.1.1" 243 | resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" 244 | integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== 245 | 246 | picocolors@^1.0.0: 247 | version "1.0.0" 248 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 249 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 250 | 251 | postcss@^8.4.13: 252 | version "8.4.13" 253 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575" 254 | integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA== 255 | dependencies: 256 | nanoid "^3.3.3" 257 | picocolors "^1.0.0" 258 | source-map-js "^1.0.2" 259 | 260 | resolve@^1.22.0: 261 | version "1.22.0" 262 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" 263 | integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== 264 | dependencies: 265 | is-core-module "^2.8.1" 266 | path-parse "^1.0.7" 267 | supports-preserve-symlinks-flag "^1.0.0" 268 | 269 | rollup@^2.59.0: 270 | version "2.72.1" 271 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.72.1.tgz#861c94790537b10008f0ca0fbc60e631aabdd045" 272 | integrity sha512-NTc5UGy/NWFGpSqF1lFY8z9Adri6uhyMLI6LvPAXdBKoPRFhIIiBUpt+Qg2awixqO3xvzSijjhnb4+QEZwJmxA== 273 | optionalDependencies: 274 | fsevents "~2.3.2" 275 | 276 | source-map-js@^1.0.2: 277 | version "1.0.2" 278 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 279 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 280 | 281 | supports-preserve-symlinks-flag@^1.0.0: 282 | version "1.0.0" 283 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 284 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 285 | 286 | tinypool@^0.1.3: 287 | version "0.1.3" 288 | resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.1.3.tgz#b5570b364a1775fd403de5e7660b325308fee26b" 289 | integrity sha512-2IfcQh7CP46XGWGGbdyO4pjcKqsmVqFAPcXfPxcPXmOWt9cYkTP9HcDmGgsfijYoAEc4z9qcpM/BaBz46Y9/CQ== 290 | 291 | tinyspy@^0.3.2: 292 | version "0.3.3" 293 | resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-0.3.3.tgz#8b57f8aec7fe1bf583a3a49cb9ab30c742f69237" 294 | integrity sha512-gRiUR8fuhUf0W9lzojPf1N1euJYA30ISebSfgca8z76FOvXtVXqd5ojEIaKLWbDQhAaC3ibxZIjqbyi4ybjcTw== 295 | 296 | type-detect@^4.0.0, type-detect@^4.0.5: 297 | version "4.0.8" 298 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 299 | integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 300 | 301 | typescript@^4.5.4: 302 | version "4.6.4" 303 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" 304 | integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== 305 | 306 | vite@^2.9.9: 307 | version "2.9.9" 308 | resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.9.tgz#8b558987db5e60fedec2f4b003b73164cb081c5e" 309 | integrity sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew== 310 | dependencies: 311 | esbuild "^0.14.27" 312 | postcss "^8.4.13" 313 | resolve "^1.22.0" 314 | rollup "^2.59.0" 315 | optionalDependencies: 316 | fsevents "~2.3.2" 317 | 318 | vitest@^0.12.9: 319 | version "0.12.10" 320 | resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.12.10.tgz#c40204dce19eb713af6dfa2b75301940cb1259f0" 321 | integrity sha512-TVoI6fM7rZ1zIMDjcviY8Dg5XIaPqBwDweaI3oUwvWqUz68cbM49CIHNMkF+UVoSjl94wXiBRdNhsT4ekgWuGA== 322 | dependencies: 323 | "@types/chai" "^4.3.1" 324 | "@types/chai-subset" "^1.3.3" 325 | chai "^4.3.6" 326 | debug "^4.3.4" 327 | local-pkg "^0.4.1" 328 | tinypool "^0.1.3" 329 | tinyspy "^0.3.2" 330 | vite "^2.9.9" 331 | --------------------------------------------------------------------------------