提交 bd1cb81d 编写于 作者: J Johannes Rieken

Merge pull request #2079 from Microsoft/joh/previewHtml

html preview part and command
......@@ -13,12 +13,13 @@ export interface IDisposable {
}
export function disposeAll<T extends IDisposable>(arr: T[]): T[] {
for (let i = 0, len = arr.length; i < len; i++) {
if (arr[i]) {
arr[i].dispose();
if (arr) {
for (let i = 0, len = arr.length; i < len; i++) {
if (arr[i]) {
arr[i].dispose();
}
}
}
return [];
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {localize} from 'vs/nls';
import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry';
import {IInstantiationService, ServicesAccessor} from 'vs/platform/instantiation/common/instantiation';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import URI from 'vs/base/common/uri';
import {HtmlInput} from '../common/htmlInput';
import {HtmlPreviewPart} from 'vs/workbench/parts/html/browser/htmlPreviewPart';
import {Registry} from 'vs/platform/platform';
import {EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions} from 'vs/workbench/browser/parts/editor/baseEditor';
import {SyncDescriptor} from 'vs/platform/instantiation/common/descriptors';
// --- Register Editor
(<IEditorRegistry>Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor(HtmlPreviewPart.ID,
localize('html.editor.label', "Html Preview"),
'vs/workbench/parts/html/browser/htmlPreviewPart',
'HtmlPreviewPart'),
[new SyncDescriptor(HtmlInput)]);
// --- Register Commands
KeybindingsRegistry.registerCommandDesc({
id: 'workbench.html.preview',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
description: {
description: 'Preview an html document.',
args: [{ name: 'uri', description: 'Uri of the document to preview.', constraint: URI }]
},
handler(accessor: ServicesAccessor, args: [URI]) {
let [resource] = args;
let name = resource.fsPath;
let input = accessor.get(IInstantiationService).createInstance(HtmlInput, name, undefined, resource);
return accessor.get(IWorkbenchEditorService).openEditor(input)
.then(editor => true);
},
context: undefined,
primary: undefined
});
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
// import 'vs/css!./media/iframeeditor';
import {localize} from 'vs/nls';
import {TPromise} from 'vs/base/common/winjs.base';
import {IModel, EventType} from 'vs/editor/common/editorCommon';
import {Dimension, Builder} from 'vs/base/browser/builder';
import {cAll} from 'vs/base/common/lifecycle';
import {EditorOptions, EditorInput} from 'vs/workbench/common/editor';
import {BaseEditor} from 'vs/workbench/browser/parts/editor/baseEditor';
import {Position} from 'vs/platform/editor/common/editor';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IStorageService, StorageEventType} from 'vs/platform/storage/common/storage';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import {ResourceEditorModel} from 'vs/workbench/common/editor/resourceEditorModel';
import {Preferences} from 'vs/workbench/common/constants';
import {HtmlInput} from 'vs/workbench/parts/html/common/htmlInput';
/**
* An implementation of editor for showing HTML content in an IFrame by leveraging the IFrameEditorInput.
*/
export class HtmlPreviewPart extends BaseEditor {
static ID: string = 'workbench.editor.htmlPreviewPart';
private _editorService: IWorkbenchEditorService;
private _storageService: IStorageService;
private _iFrameElement: HTMLIFrameElement;
private _model: IModel;
private _modelChangeUnbind: Function;
private _lastModelVersion: number;
private _themeChangeUnbind: Function;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IStorageService storageService: IStorageService
) {
super(HtmlPreviewPart.ID, telemetryService);
this._editorService = editorService;
this._storageService = storageService;
}
dispose(): void {
// remove from dome
const element = this._iFrameElement.parentElement;
element.parentElement.removeChild(element);
// unhook from model
this._modelChangeUnbind = cAll(this._modelChangeUnbind);
this._model = undefined;
this._themeChangeUnbind = cAll(this._themeChangeUnbind);
}
public createEditor(parent: Builder): void {
// IFrame
// this.iframeBuilder.removeProperty(IFrameEditor.RESOURCE_PROPERTY);
this._iFrameElement = document.createElement('iframe');
this._iFrameElement.setAttribute('frameborder', '0');
this._iFrameElement.className = 'iframe';
// Container for IFrame
const iFrameContainerElement = document.createElement('div');
iFrameContainerElement.className = 'iframe-container monaco-editor-background'; // Inherit the background color from selected theme
iFrameContainerElement.tabIndex = 0;
iFrameContainerElement.appendChild(this._iFrameElement);
parent.getHTMLElement().appendChild(iFrameContainerElement);
this._themeChangeUnbind = this._storageService.addListener(StorageEventType.STORAGE, event => {
if (event.key === Preferences.THEME && this.isVisible()) {
this._updateIFrameContent(true);
}
});
}
public layout(dimension: Dimension): void {
let {width, height} = dimension;
this._iFrameElement.parentElement.style.width = `${width}px`;
this._iFrameElement.parentElement.style.height = `${height}px`;
this._iFrameElement.style.width = `${width}px`;
this._iFrameElement.style.height = `${height}px`;
}
public focus(): void {
// this.iframeContainer.domFocus();
this._iFrameElement.focus();
}
// --- input
public getTitle(): string {
if (!this.input) {
return localize('iframeEditor', 'Preview Html');
}
return this.input.getName();
}
public setVisible(visible: boolean, position?: Position): TPromise<void> {
return super.setVisible(visible, position).then(() => {
if (visible && this._model) {
this._modelChangeUnbind = this._model.addListener(EventType.ModelContentChanged2, () => this._updateIFrameContent());
this._updateIFrameContent();
} else {
this._modelChangeUnbind = cAll(this._modelChangeUnbind);
}
})
}
public changePosition(position: Position): void {
super.changePosition(position);
// reparenting an IFRAME into another DOM element yields weird results when the contents are made
// of a string and not a URL. to be on the safe side we reload the iframe when the position changes
// and we do it using a timeout of 0 to reload only after the position has been changed in the DOM
setTimeout(() => {
this._updateIFrameContent(true);
}, 0);
}
public setInput(input: EditorInput, options: EditorOptions): TPromise<void> {
this._model = undefined;
this._modelChangeUnbind = cAll(this._modelChangeUnbind);
this._lastModelVersion = -1;
if (!(input instanceof HtmlInput)) {
return TPromise.wrapError<void>('Invalid input');
}
return this._editorService.resolveEditorModel(input).then(model => {
if (model instanceof ResourceEditorModel) {
this._model = model.textEditorModel
}
if (!this._model) {
return TPromise.wrapError<void>(localize('html.voidInput', "Invalid editor input."));
}
this._modelChangeUnbind = this._model.addListener(EventType.ModelContentChanged2, () => this._updateIFrameContent());
this._updateIFrameContent();
return super.setInput(input, options);
});
}
private _updateIFrameContent(refresh: boolean = false): void {
if (!this._model || (!refresh && this._lastModelVersion === this._model.getVersionId())) {
// nothing to do
return;
}
const html = this._model.getValue();
const iFrameDocument = this._iFrameElement.contentDocument;
if (!iFrameDocument) {
// not visible anymore
return;
}
// the very first time we load just our script
// to integrate with the outside world
if ((<HTMLElement>iFrameDocument.firstChild).innerHTML === '<head></head><body></body>') {
iFrameDocument.open('text/html', 'replace');
iFrameDocument.write(Integration.defaultHtml());
iFrameDocument.close();
}
// diff a little against the current input and the new state
const parser = new DOMParser();
const newDocument = parser.parseFromString(html, 'text/html');
const styleElement = Integration.defaultStyle(this._iFrameElement.parentElement);
if (newDocument.head.hasChildNodes()) {
newDocument.head.insertBefore(styleElement, newDocument.head.firstChild);
} else {
newDocument.head.appendChild(styleElement)
}
if (newDocument.head.innerHTML !== iFrameDocument.head.innerHTML) {
iFrameDocument.head.innerHTML = newDocument.head.innerHTML;
}
if (newDocument.body.innerHTML !== iFrameDocument.body.innerHTML) {
iFrameDocument.body.innerHTML = newDocument.body.innerHTML;
}
this._lastModelVersion = this._model.getVersionId();
}
}
namespace Integration {
'use strict';
const scriptSource = [
'var ignoredKeys = [32 /* space */, 33 /* page up */, 34 /* page down */, 38 /* up */, 40 /* down */];',
'var ignoredCtrlCmdKeys = [67 /* c */];',
'window.document.body.addEventListener("keydown", function(event) {', // Listen to keydown events in the iframe
' try {',
' if (ignoredKeys.some(function(i) { return i === event.keyCode; })) {',
' if (!event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey) {',
' return;', // we want some single keys to be supported (e.g. Page Down for scrolling)
' }',
' }',
'',
' if (ignoredCtrlCmdKeys.some(function(i) { return i === event.keyCode; })) {',
' if (event.ctrlKey || event.metaKey) {',
' return;', // we want some ctrl/cmd keys to be supported (e.g. Ctrl+C for copy)
' }',
' }',
'',
' event.preventDefault();', // very important to not get duplicate actions when this one bubbles up!
'',
' var fakeEvent = document.createEvent("KeyboardEvent");', // create a keyboard event
' Object.defineProperty(fakeEvent, "keyCode", {', // we need to set some properties that Chrome wants
' get : function() {',
' return event.keyCode;',
' }',
' });',
' Object.defineProperty(fakeEvent, "which", {',
' get : function() {',
' return event.keyCode;',
' }',
' });',
' Object.defineProperty(fakeEvent, "target", {',
' get : function() {',
' return window && window.parent.document.body;',
' }',
' });',
'',
' fakeEvent.initKeyboardEvent("keydown", true, true, document.defaultView, null, null, event.ctrlKey, event.altKey, event.shiftKey, event.metaKey);', // the API shape of this method is not clear to me, but it works ;)
'',
' window.parent.document.dispatchEvent(fakeEvent);', // dispatch the event onto the parent
' } catch (error) {}',
'});',
// disable dropping into iframe!
'window.document.addEventListener("dragover", function (e) {',
' e.preventDefault();',
'});',
'window.document.addEventListener("drop", function (e) {',
' e.preventDefault();',
'});',
'window.document.body.addEventListener("dragover", function (e) {',
' e.preventDefault();',
'});',
'window.document.body.addEventListener("drop", function (e) {',
' e.preventDefault();',
'});',
];
export function defaultHtml() {
let all = [
'<html><head></head><body><script>',
...scriptSource,
'</script></body></html>',
];
return all.join('\n');
}
export function defaultStyle(element: HTMLElement): HTMLStyleElement {
const styles = window.getComputedStyle(element);
const styleElement = document.createElement('style');
styleElement.innerHTML = `* {
color: ${styles.color};
background: ${styles.background};
font-family: ${styles.fontFamily};
font-size: ${styles.fontSize}
}`;
return styleElement;
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import URI from 'vs/base/common/uri';
import {ResourceEditorInput} from 'vs/workbench/common/editor/resourceEditorInput';
export class HtmlInput extends ResourceEditorInput {
// just a marker class
getResource(): URI {
return this.resource;
}
}
......@@ -47,6 +47,8 @@ define([
'vs/workbench/parts/debug/electron-browser/debug.contribution',
'vs/workbench/parts/html/browser/html.contribution',
'vs/workbench/parts/extensions/electron-browser/extensions.contribution',
'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen',
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册