WIP: editor
This commit is contained in:
parent
500332a426
commit
b80ad51aae
8 changed files with 193 additions and 10 deletions
6
examples/minimal/src/components/List.astro
Normal file
6
examples/minimal/src/components/List.astro
Normal file
|
@ -0,0 +1,6 @@
|
|||
<ul>
|
||||
<li>Amazing</li>
|
||||
<li>Item B?</li>
|
||||
<li>Item C<br></li>
|
||||
</ul>
|
||||
|
7
examples/minimal/src/components/Title.astro
Normal file
7
examples/minimal/src/components/Title.astro
Normal file
|
@ -0,0 +1,7 @@
|
|||
<h1>Woah...</h1>
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
|
@ -1,4 +1,6 @@
|
|||
---
|
||||
import Title from "../components/Title.astro";
|
||||
import List from "../components/List.astro";
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
|
@ -9,6 +11,7 @@
|
|||
<title>Astro</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Astro</h1>
|
||||
<Title />
|
||||
<List />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -117,6 +117,7 @@
|
|||
"kleur": "^4.1.4",
|
||||
"magic-string": "^0.25.9",
|
||||
"mime": "^3.0.0",
|
||||
"open-editor": "^4.0.0",
|
||||
"ora": "^6.1.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
/// <reference types="vite/client" />
|
||||
const attrs = {
|
||||
file: 'data-astro-source-file',
|
||||
loc: 'data-astro-source-loc',
|
||||
}
|
||||
|
||||
if (import.meta.hot) {
|
||||
initClickToComponent();
|
||||
// Vite injects `<style type="text/css">` for ESM imports of styles
|
||||
// but Astro also SSRs with `<style>` blocks. This MutationObserver
|
||||
// removes any duplicates as soon as they are hydrated client-side.
|
||||
const injectedStyles = getInjectedStyles();
|
||||
const mo = new MutationObserver((records) => {
|
||||
for (const record of records) {
|
||||
cleanNodes(record.addedNodes);
|
||||
for (const node of record.addedNodes) {
|
||||
if (isViteInjectedStyle(node)) {
|
||||
injectedStyles.get(node.innerHTML.trim())?.remove();
|
||||
|
@ -44,3 +50,120 @@ function isStyle(node: Node): node is HTMLStyleElement {
|
|||
function isViteInjectedStyle(node: Node): node is HTMLStyleElement {
|
||||
return isStyle(node) && node.getAttribute('type') === 'text/css';
|
||||
}
|
||||
|
||||
function isElement(node: EventTarget | null): node is HTMLElement {
|
||||
return !!(node as any)?.tagName;
|
||||
}
|
||||
|
||||
function getEditorInfo(el: HTMLElement) {
|
||||
const { file, loc } = (el as any).__astro;
|
||||
if (!file) return;
|
||||
return { file, loc }
|
||||
}
|
||||
|
||||
function initClickToComponent() {
|
||||
cleanNodes(document.querySelectorAll('[data-astro-source-file]'));
|
||||
const style = document.createElement('style')
|
||||
style.innerHTML = `[data-astro-edit] {
|
||||
outline: 0;
|
||||
}
|
||||
astro-edit-overlay {
|
||||
transition: opacity 300ms linear ease-out;
|
||||
--padding-inline: 4px;
|
||||
--padding-block: 2px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
transform-origin: top left;
|
||||
border: 1px solid #863BE4;
|
||||
width: calc(var(--width, 0) + (var(--padding-inline) * 2));
|
||||
height: calc(var(--height, 0) + (var(--padding-block) * 2));
|
||||
transform: translate(calc(var(--x, 0) - var(--padding-inline)), calc(var(--y, 0) - var(--padding-block)));
|
||||
border-radius: 2px;
|
||||
}
|
||||
`
|
||||
document.head.appendChild(style);
|
||||
const initialBody = document.body.innerHTML;
|
||||
let newBody = document.body.innerHTML;
|
||||
let altKey = false;
|
||||
let active = false;
|
||||
let target: HTMLElement | null = null;
|
||||
customElements.define('astro-edit-overlay', class AstroEditOverlay extends HTMLElement {})
|
||||
const overlay = document.createElement('astro-edit-overlay');
|
||||
document.body.appendChild(overlay);
|
||||
window.addEventListener('keydown', (event) => {
|
||||
if (!event.altKey) return;
|
||||
altKey = true;
|
||||
updateOverlay()
|
||||
})
|
||||
window.addEventListener('keyup', () => {
|
||||
altKey = false;
|
||||
updateOverlay();
|
||||
})
|
||||
window.addEventListener('mousemove', (event) => {
|
||||
if (!isElement(event.target)) return;
|
||||
if (event.target === document.documentElement || event.target === document.body) return;
|
||||
if (target !== event.target) {
|
||||
target = event.target
|
||||
updateOverlay();
|
||||
}
|
||||
})
|
||||
function updateOverlay() {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
if (!altKey) {
|
||||
overlay.style.setProperty('opacity', '0');
|
||||
return;
|
||||
}
|
||||
overlay.style.removeProperty('opacity');
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(target!)
|
||||
let rect = range.getClientRects()[0]
|
||||
if (!rect) return;
|
||||
overlay.style.setProperty('--x', `${rect.left - window.scrollX}px`);
|
||||
overlay.style.setProperty('--y', `${rect.top - window.scrollY}px`);
|
||||
overlay.style.setProperty('--width', `${rect.width}px`);
|
||||
overlay.style.setProperty('--height', `${rect.height}px`);
|
||||
}
|
||||
window.addEventListener('click', (event) => {
|
||||
if (!event.altKey) return;
|
||||
const el = event.target;
|
||||
if (!isElement(el)) return;
|
||||
const detail = getEditorInfo(el);
|
||||
if (detail) {
|
||||
el.contentEditable = 'plaintext-only';
|
||||
el.setAttribute('data-astro-edit', '');
|
||||
const initialText = el.innerHTML;
|
||||
let newText = initialText;
|
||||
target = event.target as HTMLElement;
|
||||
el.focus();
|
||||
el.addEventListener('blur', () => {
|
||||
el.removeAttribute('contenteditable');
|
||||
el.removeAttribute('data-astro-edit');
|
||||
altKey = false;
|
||||
target = null;
|
||||
if (initialText.trim() === newText.trim()) return;
|
||||
newBody = document.body.innerHTML;
|
||||
import.meta.hot.send('astro:edit', { ...detail, replace: [initialText.trim(), newText.trim()] });
|
||||
})
|
||||
el.addEventListener('input', (event) => {
|
||||
newText = el.innerHTML;
|
||||
updateOverlay();
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cleanNodes(nodes: NodeList) {
|
||||
for (const node of nodes) {
|
||||
if (isElement(node)) {
|
||||
(node as any).__astro = {};
|
||||
for (const [key, attr] of Object.entries(attrs)) {
|
||||
(node as any).__astro[key] = node.getAttribute(attr);
|
||||
node.removeAttribute(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ async function compile({
|
|||
internalURL: `/@fs${prependForwardSlash(
|
||||
viteID(new URL('../runtime/server/index.js', import.meta.url))
|
||||
)}`,
|
||||
annotateSourceFile: true,
|
||||
// TODO: baseline flag
|
||||
experimentalStaticExtraction: true,
|
||||
preprocessStyle: async (value: string, attrs: Record<string, string>) => {
|
||||
|
|
|
@ -7,10 +7,11 @@ import type { PluginMetadata as AstroPluginMetadata } from './types';
|
|||
import ancestor from 'common-ancestor-path';
|
||||
import esbuild from 'esbuild';
|
||||
import slash from 'slash';
|
||||
import fs from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { isRelativePath, startsWithForwardSlash } from '../core/path.js';
|
||||
import { getFileInfo } from '../vite-plugin-utils/index.js';
|
||||
import { cachedCompilation, CompileProps, getCachedSource } from './compile.js';
|
||||
import { cachedCompilation, CompileProps, getCachedSource, invalidateCompilation } from './compile.js';
|
||||
import { handleHotUpdate, trackCSSDependencies } from './hmr.js';
|
||||
import { parseAstroRequest, ParsedRequestResult } from './query.js';
|
||||
import { getViteTransform, TransformHook } from './styles.js';
|
||||
|
@ -44,7 +45,7 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
|||
// Variables for determing if an id starts with /src...
|
||||
const srcRootWeb = config.srcDir.pathname.slice(config.root.pathname.length - 1);
|
||||
const isBrowserPath = (path: string) => path.startsWith(srcRootWeb);
|
||||
|
||||
const edits: Record<string, string> = {};
|
||||
function resolveRelativeFromAstroParent(id: string, parsedFrom: ParsedRequestResult): string {
|
||||
const filename = normalizeFilename(parsedFrom.filename);
|
||||
const resolvedURL = new URL(id, `file://${filename}`);
|
||||
|
@ -64,6 +65,19 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
|||
},
|
||||
configureServer(server) {
|
||||
viteDevServer = server;
|
||||
server.ws.on('astro:edit', async ({ file: fileURL, loc, replace: [from, to] }) => {
|
||||
if (fileURL.startsWith('/@fs')) {
|
||||
fileURL = fileURL.replace('/@fs', 'file://')
|
||||
}
|
||||
const file = fileURLToPath(new URL(fileURL));
|
||||
const [line, column] = loc.split(':').map((v: string) => Number(v));
|
||||
const text = edits[file] ?? await fs.readFile(file, 'utf-8');
|
||||
const start = text.split('\n').slice(0, line - 1).join('\n');
|
||||
const end = text.split('\n').slice(line - 1).join('\n');
|
||||
const newText = end.replace(from, to);
|
||||
const output = `${start.trim() ? start + '\n' : ''}${newText}`;
|
||||
edits[file] = output;
|
||||
})
|
||||
},
|
||||
// note: don’t claim .astro files with resolveId() — it prevents Vite from transpiling the final JS (import.meta.glob, etc.)
|
||||
async resolveId(id, from, opts) {
|
||||
|
|
|
@ -236,7 +236,10 @@ importers:
|
|||
|
||||
examples/minimal:
|
||||
specifiers:
|
||||
'@astrojs/devtools': ^0.0.1
|
||||
astro: ^1.0.6
|
||||
dependencies:
|
||||
'@astrojs/devtools': link:../../packages/integrations/devtools
|
||||
devDependencies:
|
||||
astro: link:../../packages/astro
|
||||
|
||||
|
@ -438,6 +441,7 @@ importers:
|
|||
magic-string: ^0.25.9
|
||||
mime: ^3.0.0
|
||||
mocha: ^9.2.2
|
||||
open-editor: ^4.0.0
|
||||
ora: ^6.1.0
|
||||
path-browserify: ^1.0.1
|
||||
path-to-regexp: ^6.2.1
|
||||
|
@ -496,6 +500,7 @@ importers:
|
|||
kleur: 4.1.5
|
||||
magic-string: 0.25.9
|
||||
mime: 3.0.0
|
||||
open-editor: 4.0.0
|
||||
ora: 6.1.2
|
||||
path-browserify: 1.0.1
|
||||
path-to-regexp: 6.2.1
|
||||
|
@ -2167,6 +2172,14 @@ importers:
|
|||
'@astrojs/deno': link:../../..
|
||||
astro: link:../../../../../astro
|
||||
|
||||
packages/integrations/devtools:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
astro-scripts: workspace:*
|
||||
devDependencies:
|
||||
astro: link:../../astro
|
||||
astro-scripts: link:../../../scripts
|
||||
|
||||
packages/integrations/image:
|
||||
specifiers:
|
||||
'@types/etag': ^1.8.1
|
||||
|
@ -10269,7 +10282,6 @@ packages:
|
|||
/define-lazy-prop/2.0.0:
|
||||
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/define-properties/1.1.4:
|
||||
resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
|
||||
|
@ -10501,6 +10513,11 @@ packages:
|
|||
engines: {node: '>=0.12'}
|
||||
dev: true
|
||||
|
||||
/env-editor/1.1.0:
|
||||
resolution: {integrity: sha512-7AXskzN6T7Q9TFcKAGJprUbpQa4i1VsAetO9rdBqbGMGlragTziBgWt4pVYJMBWHQlLoX0buy6WFikzPH4Qjpw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dev: false
|
||||
|
||||
/eol/0.9.1:
|
||||
resolution: {integrity: sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==}
|
||||
dev: false
|
||||
|
@ -11215,7 +11232,6 @@ packages:
|
|||
onetime: 5.1.2
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
dev: true
|
||||
|
||||
/execa/6.1.0:
|
||||
resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==}
|
||||
|
@ -11983,7 +11999,6 @@ packages:
|
|||
/human-signals/2.1.0:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
dev: true
|
||||
|
||||
/human-signals/3.0.1:
|
||||
resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==}
|
||||
|
@ -12299,7 +12314,6 @@ packages:
|
|||
/is-stream/2.0.1:
|
||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/is-stream/3.0.0:
|
||||
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||
|
@ -12518,6 +12532,13 @@ packages:
|
|||
resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/line-column-path/3.0.0:
|
||||
resolution: {integrity: sha512-Atocnm7Wr9nuvAn97yEPQa3pcQI5eLQGBz+m6iTb+CVw+IOzYB9MrYK7jI7BfC9ISnT4Fu0eiwhAScV//rp4Hw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
type-fest: 2.18.0
|
||||
dev: false
|
||||
|
||||
/lines-and-columns/1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
dev: true
|
||||
|
@ -13706,7 +13727,6 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
dev: true
|
||||
|
||||
/npm-run-path/5.1.0:
|
||||
resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==}
|
||||
|
@ -13787,6 +13807,16 @@ packages:
|
|||
dependencies:
|
||||
mimic-fn: 4.0.0
|
||||
|
||||
/open-editor/4.0.0:
|
||||
resolution: {integrity: sha512-5mKZ98iFdkivozt5XTCOspoKbL3wtYu6oOoVxfWQ0qUX9NYsK8pdkHE7VUHXr+CwyC3nf6mV0S5FPsMS65innw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
env-editor: 1.1.0
|
||||
execa: 5.1.1
|
||||
line-column-path: 3.0.0
|
||||
open: 8.4.0
|
||||
dev: false
|
||||
|
||||
/open/8.4.0:
|
||||
resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -13794,7 +13824,6 @@ packages:
|
|||
define-lazy-prop: 2.0.0
|
||||
is-docker: 2.2.1
|
||||
is-wsl: 2.2.0
|
||||
dev: true
|
||||
|
||||
/optionator/0.8.3:
|
||||
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
|
||||
|
@ -15720,7 +15749,6 @@ packages:
|
|||
/strip-final-newline/2.0.0:
|
||||
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/strip-final-newline/3.0.0:
|
||||
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
|
||||
|
|
Loading…
Reference in a new issue