Compare commits

...

1 commit

Author SHA1 Message Date
Nate Moore
b80ad51aae WIP: editor 2022-08-17 16:16:00 -04:00
8 changed files with 193 additions and 10 deletions

View file

@ -0,0 +1,6 @@
<ul>
<li>Amazing</li>
<li>Item B?</li>
<li>Item C<br></li>
</ul>

View file

@ -0,0 +1,7 @@
<h1>Woah...</h1>
<style>
h1 {
color: green;
}
</style>

View file

@ -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>

View file

@ -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",

View file

@ -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);
}
}
}
}

View file

@ -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>) => {

View file

@ -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: dont claim .astro files with resolveId() — it prevents Vite from transpiling the final JS (import.meta.glob, etc.)
async resolveId(id, from, opts) {

View file

@ -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==}