Skip to content

Commit 1bd0076

Browse files
committed
🎉 Add settings to control scripts and element movement
1 parent 92a3c7a commit 1bd0076

File tree

3 files changed

+111
-67
lines changed

3 files changed

+111
-67
lines changed

‎package.json‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"ui"
1010
],
1111
"icon": "images/icon-logo.png",
12-
"version": "0.1.4",
12+
"version": "0.1.5",
1313
"engines": {
1414
"vscode": "^1.92.0"
1515
},
@@ -143,6 +143,22 @@
143143
"group": "navigation@3"
144144
}
145145
]
146+
},
147+
"configuration": {
148+
"type": "object",
149+
"title": "Web Visual Editor",
150+
"properties": {
151+
"webVisualEditor.allowScript": {
152+
"type": "boolean",
153+
"default": true,
154+
"description": "Enable JavaScript in preview."
155+
},
156+
"webVisualEditor.enableMovingElements": {
157+
"type": "boolean",
158+
"default": true,
159+
"description": "Enable moving position of elements in preview."
160+
}
161+
}
146162
}
147163
}
148164
}

‎src/visualEditor.ts‎

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ export class VisualEditorProvider implements vscode.CustomTextEditorProvider {
1717
constructor(private readonly ec: vscode.ExtensionContext) {
1818
this.context = ec;
1919
// Get and update indentation setting
20-
const config = vscode.workspace.getConfiguration('editor', { languageId: 'html' });
21-
const insertSpaces = config.get<boolean>('insertSpaces');
22-
const indentSize = config.get<number>('tabSize')!;
20+
const editorConfig = vscode.workspace.getConfiguration('editor', { languageId: 'html' });
21+
const insertSpaces = editorConfig.get<boolean>('insertSpaces');
22+
const indentSize = editorConfig.get<number>('tabSize')!;
2323
Object.assign(this.editorOptions, {
2424
insertSpaces,
2525
indentSize,
@@ -269,26 +269,29 @@ export class VisualEditorProvider implements vscode.CustomTextEditorProvider {
269269

270270
// Reflect content of source code to WebView
271271
private updateWebview(webview: vscode.Webview, code: vscode.TextDocument) {
272+
const config = vscode.workspace.getConfiguration('webVisualEditor');
272273
const dom = new JSDOM(code.getText(), { includeNodeLocations: true });
273274
const document = dom.window.document;
274-
// Disable scripts in code
275-
document.querySelectorAll('script').forEach(el => { el.remove(); });
276-
document.querySelectorAll('body *, body').forEach(el => {
277-
// Remove event attributes
278-
el.removeAttribute('disabled');
279-
const nameToRemove = [];
280-
for (const attr of el.attributes) {
281-
if (attr.name.startsWith('on')) {
282-
nameToRemove.push(attr.name);
275+
if (!config.get<boolean>('webVisualEditor.allowScript')) {
276+
// Disable scripts in code
277+
document.querySelectorAll('script').forEach(el => { el.remove(); });
278+
document.querySelectorAll('body *, body').forEach(el => {
279+
// Remove event attributes
280+
el.removeAttribute('disabled');
281+
const nameToRemove = [];
282+
for (const attr of el.attributes) {
283+
if (attr.name.startsWith('on')) {
284+
nameToRemove.push(attr.name);
285+
}
283286
}
284-
}
285-
nameToRemove.forEach(name => el.removeAttribute(name));
286-
// Add source code location information to all elements in body
287-
const location = dom.nodeLocation(el);
288-
if (!location) { throw Error(`Failed to get nodeLocation of element ${el}`); }
289-
el.setAttribute('data-wve-code-start', location.startOffset.toString());
290-
el.setAttribute('data-wve-code-end', location.endOffset.toString());
291-
});
287+
nameToRemove.forEach(name => el.removeAttribute(name));
288+
// Add source code location information to all elements in body
289+
const location = dom.nodeLocation(el);
290+
if (!location) { throw Error(`Failed to get nodeLocation of element ${el}`); }
291+
el.setAttribute('data-wve-code-start', location.startOffset.toString());
292+
el.setAttribute('data-wve-code-end', location.endOffset.toString());
293+
});
294+
}
292295
// Disable links and file selection inputs
293296
document.body.querySelectorAll('a[href]').forEach(
294297
el => el.setAttribute('onclick', 'event.preventDefault(), event.stopPropagation()')
@@ -301,22 +304,19 @@ export class VisualEditorProvider implements vscode.CustomTextEditorProvider {
301304
if (el.tagName === 'A') { return; }
302305
const uri = el.getAttribute(attr)!;
303306
if (!this.isRelativePath(uri)) { return; }
304-
const filepath = path.join(path.dirname(code.uri.fsPath), uri);
305-
if (this.resources.has(filepath)) {
306-
this.resources.get(filepath)?.add(code);
307-
} else {
308-
this.resources.set(filepath, new Set([code]));
309-
}
307+
this.addToResources(code, uri);
310308
const safeUri = webview.asWebviewUri(
311309
vscode.Uri.file(path.join(path.dirname(code.uri.fsPath), uri))
312310
).toString();
313311
el.setAttribute(attr, safeUri);
314312
});
315313
});
316314
// Add code id
317-
const codeId = document.createElement('script');
318-
codeId.textContent = `const codeId = '${code.uri.toString()}';`;
319-
document.head.appendChild(codeId);
315+
const embeddedScript = document.createElement('script');
316+
embeddedScript.textContent = `const wve = ${JSON.stringify({
317+
codeId: code.uri.toString(), config
318+
})}`;
319+
document.head.appendChild(embeddedScript);
320320
// Incorporate CSS files into layer and lower their priority
321321
const style = document.createElement('style');
322322
document.querySelectorAll('link[href][rel=stylesheet]').forEach(el => {
@@ -345,7 +345,7 @@ export class VisualEditorProvider implements vscode.CustomTextEditorProvider {
345345
// NOTE WebView has HTML cache, and if the same string is set consecutively,
346346
// it will not reflect it even if actual HTML on the WebView has been updated.
347347
const timestamp = document.createElement('meta');
348-
timestamp.setAttribute('name', 'timestamp');
348+
timestamp.setAttribute('name', 'wve-timestamp');
349349
timestamp.setAttribute('value', (new Date()).toISOString());
350350
document.head.appendChild(timestamp);
351351
webview.html = dom.serialize();
@@ -367,6 +367,15 @@ export class VisualEditorProvider implements vscode.CustomTextEditorProvider {
367367
});
368368
}
369369

370+
private addToResources(code: vscode.TextDocument, uri: string) {
371+
const filepath = path.join(path.dirname(code.uri.fsPath), uri);
372+
if (this.resources.has(filepath)) {
373+
this.resources.get(filepath)?.add(code);
374+
} else {
375+
this.resources.set(filepath, new Set([code]));
376+
}
377+
}
378+
370379
private isRelativePath(path: string) {
371380
try {
372381
new URL(path);

‎webview/webview.js‎

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
class App {
1+
class WebVisualEditor {
22
codeEdits = [];
33
operation = '';
44
keyboard = {
@@ -38,7 +38,7 @@ class App {
3838
htmlParser = null;
3939

4040
constructor() {
41-
const state = JSON.parse(sessionStorage.getItem(codeId) ?? '{}');
41+
const state = JSON.parse(sessionStorage.getItem(wve.codeId) ?? '{}');
4242
this.zoom = state.zoom ?? '1';
4343
this.linkCode = state.linkCode ?? true;
4444
}
@@ -94,7 +94,7 @@ class App {
9494
toolbarZoomOut: 'wve-zoom-out',
9595
toolbarGroupAlign: 'wve-group-align',
9696
};
97-
this.toolbar.innerHTML = `
97+
let toolbarHtml = (`
9898
<fieldset>
9999
<label class="wve-button" title="Link selections with editor">
100100
<input id="${controls.toolbarLinkCode}" type="checkbox">
@@ -104,30 +104,36 @@ class App {
104104
<span id="${controls.toolbarZoomValue}">100%</span>
105105
<button id="${controls.toolbarZoomOut}" type="button" class="wve-button">zoom_out</button>
106106
</fieldset>
107-
<fieldset id="${controls.toolbarGroupAlign}" disabled>
108-
<button type="button" class="wve-button" id="align-horizontal-left">align_horizontal_left</button>
109-
<button type="button" class="wve-button" id="align-horizontal-center">align_horizontal_center</button>
110-
<button type="button" class="wve-button" id="align-horizontal-right">align_horizontal_right</button>
111-
<button type="button" class="wve-button" id="align-vertical-top">align_vertical_top</button>
112-
<button type="button" class="wve-button" id="align-vertical-center">align_vertical_center</button>
113-
<button type="button" class="wve-button" id="align-vertical-bottom">align_vertical_bottom</button>
114-
<button type="button" class="wve-button" id="align-horizontal-justify">align_justify_space_even</button>
115-
<button type="button" class="wve-button" id="align-vertical-justify">align_space_even</button>
116-
</fieldset>
117-
`;
107+
`);
108+
if (wve.config.enableMovingElements) {
109+
toolbarHtml += `
110+
<fieldset id="${controls.toolbarGroupAlign}" disabled>
111+
<button type="button" class="wve-button" id="align-horizontal-left">align_horizontal_left</button>
112+
<button type="button" class="wve-button" id="align-horizontal-center">align_horizontal_center</button>
113+
<button type="button" class="wve-button" id="align-horizontal-right">align_horizontal_right</button>
114+
<button type="button" class="wve-button" id="align-vertical-top">align_vertical_top</button>
115+
<button type="button" class="wve-button" id="align-vertical-center">align_vertical_center</button>
116+
<button type="button" class="wve-button" id="align-vertical-bottom">align_vertical_bottom</button>
117+
<button type="button" class="wve-button" id="align-horizontal-justify">align_justify_space_even</button>
118+
<button type="button" class="wve-button" id="align-vertical-justify">align_space_even</button>
119+
</fieldset>`;
120+
}
121+
this.toolbar.innerHTML = toolbarHtml;
118122
Object.entries(controls).forEach(([key, id]) => {
119123
this[key] = fragment.getElementById(id);
120124
});
121-
this.toolbarZoomIn.addEventListener('click', event => { this.updateZoom(1); });
122-
this.toolbarZoomOut.addEventListener('click', event => { this.updateZoom(-1); });
123-
this.toolbarGroupAlign.addEventListener('click', this.onClickGroupAlign);
124125
this.toolbarLinkCode.addEventListener('change', event => {
125126
this.linkCode = event.target.checked;
126127
this.saveState();
127128
});
129+
this.toolbarZoomIn.addEventListener('click', event => { this.updateZoom(1); });
130+
this.toolbarZoomOut.addEventListener('click', event => { this.updateZoom(-1); });
128131
this.toolbarRefresh.addEventListener('click', event => {
129132
vscode.postMessage({ type: 'refresh' });
130133
});
134+
if (wve.config.enableMovingElements) {
135+
this.toolbarGroupAlign.addEventListener('click', this.onClickGroupAlign);
136+
}
131137
document.body.appendChild(fragment);
132138
}
133139

@@ -167,7 +173,7 @@ class App {
167173
const state = Object.fromEntries(
168174
['zoom', 'linkCode'].map(key => [key, this[key]])
169175
);
170-
sessionStorage.setItem(codeId, JSON.stringify(state));
176+
sessionStorage.setItem(wve.codeId, JSON.stringify(state));
171177
vscode.postMessage({ type: 'state', data: state });
172178
}
173179

@@ -213,7 +219,9 @@ class App {
213219
});
214220
vscode.postMessage({ type: 'edit', data });
215221
this.codeEdits = [];
216-
this.moversBeforeEdit.clear();
222+
if (wve.config.enableMovingElements) {
223+
this.moversBeforeEdit.clear();
224+
}
217225
}
218226

219227
emitSelectionChange() {
@@ -245,10 +253,12 @@ class App {
245253
}
246254
this.selected.add(element);
247255
element.setAttribute('wve-selected', '');
248-
if (element.hasAttribute('wve-movable')) {
249-
this.movers.add(element);
256+
if (wve.config.enableMovingElements) {
257+
if (element.hasAttribute('wve-movable')) {
258+
this.movers.add(element);
259+
}
260+
if (this.movers.size > 1) { this.toolbarGroupAlign.removeAttribute('disabled'); }
250261
}
251-
if (this.movers.size > 1) { this.toolbarGroupAlign.removeAttribute('disabled'); }
252262
if (emit) { this.emitSelectionChange(); }
253263
}
254264
// Deselect element
@@ -265,8 +275,10 @@ class App {
265275
}
266276
this.selected.delete(element);
267277
element.removeAttribute('wve-selected');
268-
this.movers.delete(element);
269-
if (this.movers.size < 2) { this.toolbarGroupAlign.setAttribute('disabled', ''); }
278+
if (wve.config.enableMovingElements) {
279+
this.movers.delete(element);
280+
if (this.movers.size < 2) { this.toolbarGroupAlign.setAttribute('disabled', ''); }
281+
}
270282
this.emitSelectionChange();
271283
}
272284
// Deselect if the element is selected, otherwise select it
@@ -278,9 +290,12 @@ class App {
278290
}
279291
}
280292
beginStyleEdit() {
281-
this.moversBeforeEdit = new Map(this.movers.values().map(el => [el, el.cloneNode(true)]));
293+
if (wve.config.enableMovingElements) {
294+
this.moversBeforeEdit = new Map(this.movers.values().map(el => [el, el.cloneNode(true)]));
295+
}
282296
}
283297
finishStyleEdit(type) {
298+
if (!wve.config.enableMovingElements) { return; }
284299
this.movers.forEach(element => {
285300
const style = element.getAttribute('style');
286301
if (style === this.moversBeforeEdit.get(element).getAttribute('style')) { return; }
@@ -348,14 +363,16 @@ class App {
348363
if (!prev.Control && kbd.Control) {
349364
document.body.classList.add('wve-adding-selection');
350365
}
351-
if (this.operation === '') {
352-
if (!kbd.arrow || this.movers.size === 0) { return; }
353-
if (!prev.arrow) { this.beginStyleEdit(); }
354-
const dx = kbd.ArrowRight ? 1 : kbd.ArrowLeft ? -1 : 0;
355-
const dy = kbd.ArrowDown ? 1 : kbd.ArrowUp ? -1 : 0;
356-
this.movers.forEach(el => { this.moveElement(el, dx, dy); });
357-
// Disable scroll
358-
event.preventDefault();
366+
if (wve.config.enableMovingElements) {
367+
if (this.operation === '') {
368+
if (!kbd.arrow || this.movers.size === 0) { return; }
369+
if (!prev.arrow) { this.beginStyleEdit(); }
370+
const dx = kbd.ArrowRight ? 1 : kbd.ArrowLeft ? -1 : 0;
371+
const dy = kbd.ArrowDown ? 1 : kbd.ArrowUp ? -1 : 0;
372+
this.movers.forEach(el => { this.moveElement(el, dx, dy); });
373+
// Disable scroll
374+
event.preventDefault();
375+
}
359376
}
360377
};
361378

@@ -375,7 +392,7 @@ class App {
375392
if (prev.Control && !this.keyboard.Control) {
376393
document.body.classList.remove('wve-adding-selection');
377394
}
378-
if (prev.arrow && !this.keyboard.arrow) {
395+
if (wve.config.enableMovingElements && prev.arrow && !this.keyboard.arrow) {
379396
this.finishStyleEdit('move');
380397
this.emitCodeEdits();
381398
}
@@ -609,15 +626,17 @@ const vscode = acquireVsCodeApi();
609626

610627
// Initial display
611628
document.addEventListener('DOMContentLoaded', async () => {
612-
const app = new App();
629+
const app = new WebVisualEditor();
613630
// Remove Visual Studio Code default styles
614631
document.getElementById('_defaultStyles')?.remove();
615632
// Incorporate styles into the user-layer
616633
// NOTE Implement here rather than Extension Host due to JSDOM's lack of @layer support
617634
document.querySelectorAll('style:not(#wve-user-css-imports)').forEach(el => {
618635
el.textContent = `\n@layer user-style {\n${el.textContent}\n}`;
619636
});
620-
app.initMovables();
637+
if (wve.config.enableMovingElements) {
638+
app.initMovables();
639+
}
621640
app.initSelector();
622641
app.initToolbar();
623642
app.updateZoom();

0 commit comments

Comments
 (0)