diff --git a/packages/source-ref-open-vscode/index.d.ts b/packages/source-ref-open-vscode/index.d.ts new file mode 100644 index 00000000..c9fb6c54 --- /dev/null +++ b/packages/source-ref-open-vscode/index.d.ts @@ -0,0 +1 @@ +declare module 'source-ref-open-vscode' {} diff --git a/packages/source-ref-open-vscode/index.js b/packages/source-ref-open-vscode/index.js new file mode 100644 index 00000000..f083fe2c --- /dev/null +++ b/packages/source-ref-open-vscode/index.js @@ -0,0 +1,219 @@ +(function (win, doc) { + const sourceMap = {}; + const sourceMapReverse = {}; + let cursor = 0; + let timer = null; + + function openVscode(node) { + let path = null; + if (node.dataset.sid) { + path = sidToURI(node.dataset.sid); + } + if (node.dataset.source) { + path = 'vscode://file/' + node.dataset.source; + } + if (!path) { + return console.warn('Not found data-source'); + } + win.location.href = path; + } + function sourceToId(node) { + if (!node.dataset.source) return; + const source = node.dataset.source; + const [file, row, column] = source.split(':'); + if (!sourceMap[file]) { + cursor++; + sourceMap[file] = cursor; + sourceMapReverse[cursor] = file; + } + const id = sourceMap[file]; + node.removeAttribute('data-source'); + node.setAttribute('data-sid', `${id}:${row}:${column}`); + } + function sidToURI(sid) { + const [id, row, column] = sid.split(':'); + const path = + 'vscode://file/' + sourceMapReverse[id] + ':' + row + ':' + column; + return path; + } + + class Selector { + constructor(node) { + this.sids = []; + this.containerId = '__source-ref-panel'; + this.getAncestorSids = (node) => { + const sids = []; + let cur = node; + while (cur !== doc.body) { + if (cur.dataset.sid) { + sids.push(cur.dataset.sid); + } + cur = cur.parentElement; + } + return sids; + }; + this.focusBlock = null; + this.setFocusBlock = (target) => { + if (target === null) { + // clear if target is null + if (this.focusBlock) { + doc.body.removeChild(this.focusBlock); + this.focusBlock = null; + } + return; + } + + if (!this.focusBlock) { + this.focusBlock = doc.createElement('div'); + this.focusBlock.className = '__source-ref-mask'; + this.focusBlock.style.position = 'absolute'; + this.focusBlock.style.backgroundColor = 'rgba(134, 185, 242, 0.5)'; + + doc.body.appendChild(this.focusBlock); + } + + const rect = target.getBoundingClientRect(); + + this.focusBlock.style.height = rect.height + 'px'; + this.focusBlock.style.width = rect.width + 'px'; + this.focusBlock.style.left = rect.x + 'px'; + this.focusBlock.style.top = rect.y + 'px'; + }; + this.getContainer = () => { + const container = doc.getElementById(this.containerId); + if (!container) { + const div = doc.createElement('div'); + div.id = this.containerId; + doc.body.appendChild(div); + + // Add dom red border on hover + div.addEventListener('mouseover', (e) => { + const node = e.target; + if (node.dataset.tid) { + const target = doc.querySelector( + `[data-sid="${node.dataset.tid}"]` + ); + if (target) { + target.classList.add('__source-ref-selected'); + this.setFocusBlock(target); + } + } + }); + + // Remove dom red border when leave + div.addEventListener('mouseout', (e) => { + const node = e.target; + if (node.dataset.tid) { + const target = doc.querySelector( + `[data-sid="${node.dataset.tid}"]` + ); + if (target) { + target.classList.remove('__source-ref-selected'); + } + } + }); + + const close = () => { + this.setFocusBlock(null); + doc.body.removeChild(div); + }; + + // click event + div.addEventListener('click', (e) => { + const node = e.target; + const command = node.dataset.command; + switch (command) { + case 'close': { + e.stopPropagation(); + close(); + return; + } + default: + console.warn('Unknown command', command); + } + }); + + // keyboard event + function escKeyHandler(e) { + if (e.key === 'Escape') { + e.stopPropagation(); + close(); + doc.removeEventListener('keydown', escKeyHandler); + } + } + doc.addEventListener('keydown', escKeyHandler); + + return div; + } + return container; + }; + this.renderHTML = () => { + const html = ` +
+
X
+ ${this.sids + .map((sid) => { + const uri = sidToURI(sid); + // 这里加了一个左向省略,暂时没用上,先放着 + return `source-ref: ${uri}`; + }) + .join('')} +
+ `; + const container = this.getContainer(); + container.innerHTML = html; + }; + this.sids = this.getAncestorSids(node); + } + } + function init() { + win.vscode = (node = win.$0) => { + openVscode(node); + }; + doc.body.addEventListener( + 'click', + (e) => { + if (e.altKey) { + e.preventDefault(); + e.stopPropagation(); + const selector = new Selector(e.target); + selector.renderHTML(); + } + }, + true + ); + const mo = new MutationObserver(() => { + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(() => { + // recal sid + doc + .querySelectorAll('[data-source]') + .forEach((node) => sourceToId(node)); + }, 500); + }); + mo.observe(doc.body, { + attributes: true, + childList: true, + subtree: true, + }); + } + init(); +})(window, document); diff --git a/packages/source-ref-open-vscode/package.json b/packages/source-ref-open-vscode/package.json new file mode 100644 index 00000000..9a66c46a --- /dev/null +++ b/packages/source-ref-open-vscode/package.json @@ -0,0 +1,15 @@ +{ + "name": "source-ref-open-vscode", + "version": "1.0.0", + "description": "", + "main": "index.js", + "types": "index.d.ts", + "scripts": { + "build": "tsc", + "prepare": "tsc", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "moonrailgun", + "license": "MIT" +} diff --git a/packages/source-ref-open-vscode/tsconfig.json b/packages/source-ref-open-vscode/tsconfig.json new file mode 100644 index 00000000..8999cdc0 --- /dev/null +++ b/packages/source-ref-open-vscode/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "outDir": "dist", + "module": "commonjs", + + "target": "ES2018", + "lib": ["ES2018"], + + "declaration": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/packages/source-ref-webpack-loader/tsconfig.json b/packages/source-ref-webpack-loader/tsconfig.json index 1e84caf7..8999cdc0 100644 --- a/packages/source-ref-webpack-loader/tsconfig.json +++ b/packages/source-ref-webpack-loader/tsconfig.json @@ -3,7 +3,6 @@ "outDir": "dist", "module": "commonjs", - // Node 10 "target": "ES2018", "lib": ["ES2018"], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3df216af..5df6a967 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -314,6 +314,7 @@ importers: rollup-plugin-copy: ^3.4.0 rollup-plugin-replace: ^2.2.0 socket.io-client: ^4.1.2 + source-ref-open-vscode: workspace:^1.0.0 style-loader: ^3.0.0 tailchat-design: workspace:^1.0.0 tailchat-plugin-declaration-generator: workspace:^1.0.0 @@ -364,6 +365,7 @@ importers: react-virtualized-auto-sizer: 1.0.6_sfoxds7t5ydpegc3knd667wn6m react-virtuoso: 2.11.0_sfoxds7t5ydpegc3knd667wn6m socket.io-client: 4.4.0 + source-ref-open-vscode: link:../packages/source-ref-open-vscode tailchat-design: link:../packages/design tailchat-shared: link:../shared tailwindcss: 2.2.19_hqu7j45oxtlo2g5y3qitzppg3y @@ -17051,7 +17053,7 @@ packages: serialize-javascript: 6.0.0 source-map: 0.6.1 terser: 5.13.1 - webpack: 5.72.1 + webpack: 5.72.1_webpack-cli@4.9.2 dev: true /terser/4.8.0: diff --git a/web/build/webpack.config.ts b/web/build/webpack.config.ts index 635f4092..c9b45257 100644 --- a/web/build/webpack.config.ts +++ b/web/build/webpack.config.ts @@ -225,13 +225,13 @@ const config: Configuration = { overlay: false, }, }, - // resolveLoader: { - // alias: { - // 'source-ref-loader': require.resolve( - // '../../packages/source-ref-webpack-loader/src' - // ), - // }, - // }, + resolveLoader: { + alias: { + 'source-ref-loader': require.resolve( + '../../packages/source-ref-webpack-loader/src' + ), + }, + }, module: { rules: [ { @@ -246,12 +246,12 @@ const config: Configuration = { tsconfigRaw: require('../tsconfig.json'), }, }, - // { - // loader: 'source-ref-loader', - // options: { - // available: false, - // }, - // }, + { + loader: 'source-ref-loader', + options: { + available: isDev, + }, + }, ], }, { diff --git a/web/package.json b/web/package.json index 7a86aa86..d5c04a51 100644 --- a/web/package.json +++ b/web/package.json @@ -54,6 +54,7 @@ "react-virtualized-auto-sizer": "^1.0.6", "react-virtuoso": "^2.8.3", "socket.io-client": "^4.1.2", + "source-ref-open-vscode": "workspace:^1.0.0", "tailchat-design": "workspace:^1.0.0", "tailchat-shared": "*", "tailwindcss": "^2.2.4", diff --git a/web/src/init.tsx b/web/src/init.tsx index 241bc48e..2e904c70 100644 --- a/web/src/init.tsx +++ b/web/src/init.tsx @@ -13,11 +13,16 @@ import { fetchGlobalConfig, request, isValidStr, + isDevelopment, } from 'tailchat-shared'; import { getPopupContainer } from './utils/dom-helper'; import { getUserJWT } from './utils/jwt-helper'; import _get from 'lodash/get'; +if (isDevelopment) { + import('source-ref-open-vscode'); +} + const webStorage = buildStorage(window.localStorage); setStorage(() => webStorage);