mirror of https://github.com/msgbyte/tailchat
feat: source-ref允许在vscode中快速打开
parent
2d4b7985c3
commit
1c7c84da7c
@ -0,0 +1 @@
|
|||||||
|
declare module 'source-ref-open-vscode' {}
|
@ -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 = `
|
||||||
|
<div style="
|
||||||
|
position: fixed;
|
||||||
|
background: white;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
opacity: 0.6;
|
||||||
|
border-radius: 0 10px 0 0;
|
||||||
|
">
|
||||||
|
<div style="cursor:pointer;margin:10px;text-align:right;font-size:18px;" data-command="close">X</div>
|
||||||
|
${this.sids
|
||||||
|
.map((sid) => {
|
||||||
|
const uri = sidToURI(sid);
|
||||||
|
// 这里加了一个左向省略,暂时没用上,先放着
|
||||||
|
return `<a href="${uri}" style="
|
||||||
|
display: block;
|
||||||
|
margin: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
direction: rtl;
|
||||||
|
text-align: left;
|
||||||
|
" data-tid="${sid}">source-ref: ${uri}</a>`;
|
||||||
|
})
|
||||||
|
.join('')}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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);
|
@ -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"
|
||||||
|
}
|
@ -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"]
|
||||||
|
}
|
Loading…
Reference in New Issue