You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailchat/packages/source-ref-open-vscode/index.js

224 lines
6.2 KiB
JavaScript

(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 splits = source.split(':');
const column = splits.pop();
const row = splits.pop();
const file = splits.join(':');
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);