From 9304ff38403124c8e1ad7916d7f75768ee7b8ed3 Mon Sep 17 00:00:00 2001 From: coca44 Date: Sat, 9 Jan 2016 15:00:48 +0100 Subject: [PATCH 1/3] Add Typescript editor --- plugins/default/typescript/.gitignore | 1 + plugins/default/typescript/README.md | 6 + .../typescript/data/TypescriptAsset.ts | 160 ++++++++++++++++++ plugins/default/typescript/data/index.ts | 3 + .../typescript/editors/typescript/index.jade | 20 +++ .../typescript/editors/typescript/index.styl | 10 ++ .../typescript/editors/typescript/index.ts | 63 +++++++ plugins/default/typescript/index.d.ts | 3 + plugins/default/typescript/package.json | 14 ++ .../public/editors/typescript/icon.svg | 96 +++++++++++ .../typescript/public/locales/en/plugin.json | 7 + plugins/default/typescript/tsconfig.json | 7 + 12 files changed, 390 insertions(+) create mode 100644 plugins/default/typescript/.gitignore create mode 100644 plugins/default/typescript/README.md create mode 100644 plugins/default/typescript/data/TypescriptAsset.ts create mode 100644 plugins/default/typescript/data/index.ts create mode 100644 plugins/default/typescript/editors/typescript/index.jade create mode 100644 plugins/default/typescript/editors/typescript/index.styl create mode 100644 plugins/default/typescript/editors/typescript/index.ts create mode 100644 plugins/default/typescript/index.d.ts create mode 100644 plugins/default/typescript/package.json create mode 100644 plugins/default/typescript/public/editors/typescript/icon.svg create mode 100644 plugins/default/typescript/public/locales/en/plugin.json create mode 100644 plugins/default/typescript/tsconfig.json diff --git a/plugins/default/typescript/.gitignore b/plugins/default/typescript/.gitignore new file mode 100644 index 0000000..600e365 --- /dev/null +++ b/plugins/default/typescript/.gitignore @@ -0,0 +1 @@ +**/node_modules \ No newline at end of file diff --git a/plugins/default/typescript/README.md b/plugins/default/typescript/README.md new file mode 100644 index 0000000..1f6113c --- /dev/null +++ b/plugins/default/typescript/README.md @@ -0,0 +1,6 @@ +# supWeb-typescript +A typescript plugin for superpowers web system + +Github of superpowers web system: https://github.com/superpowers/superpowers-web + +Github of superpowers: https://github.com/superpowers diff --git a/plugins/default/typescript/data/TypescriptAsset.ts b/plugins/default/typescript/data/TypescriptAsset.ts new file mode 100644 index 0000000..85beec0 --- /dev/null +++ b/plugins/default/typescript/data/TypescriptAsset.ts @@ -0,0 +1,160 @@ +/// + +import * as OT from "operational-transform"; +import * as mkdirp from "mkdirp"; +import * as dummy_fs from "fs"; +import * as dummy_path from "path"; +import * as ts from "typescript"; +import * as browserify from "browserify"; + +// Since we're doing weird things to the fs module, +// the code won't browserify properly with brfs +// so we'll only require them on the server-side +let serverRequire = require; + +let fs: typeof dummy_fs; +let path: typeof dummy_path; +if ((global).window == null) { + fs = serverRequire("fs"); + path = serverRequire("path"); +} + +interface TypescriptAssetPub { + text: string; + draft: string; + revisionId: number; +} + +export default class TypescriptAsset extends SupCore.Data.Base.Asset { + static schema: SupCore.Data.Base.Schema = { + text: { type: "string" }, + draft: { type: "string" }, + revisionId: { type: "integer" } + }; + + pub: TypescriptAssetPub; + document: OT.Document; + hasDraft: boolean; + + constructor(id: string, pub: TypescriptAssetPub, server: ProjectServer) { + super(id, pub, TypescriptAsset.schema, server); + } + + init(options: any, callback: Function) { + this.pub = { + text: "", + draft: "", + revisionId: 0 + }; + + super.init(options, callback); + } + + setup() { + this.document = new OT.Document(this.pub.draft, this.pub.revisionId); + this.hasDraft = this.pub.text !== this.pub.draft; + } + + restore() { + if (this.hasDraft) this.emit("setBadge", "draft", "info"); + } + + load(assetPath: string) { + let pub: TypescriptAssetPub; + fs.readFile(path.join(assetPath, "data.ts"), { encoding: "utf8" }, (err, text) => { + fs.readFile(path.join(assetPath, "draft.ts"), { encoding: "utf8" }, (err, draft) => { + pub = { revisionId: 0, text, draft: (draft != null) ? draft : text }; + this._onLoaded(assetPath, pub); + }); + }); + } + + save(assetPath: string, callback: (err: Error) => any) { + fs.writeFile(path.join(assetPath, "data.ts"), this.pub.text, { encoding: "utf8" }, (err) => { + if (err != null) { callback(err); return; } + + if (this.hasDraft) { + fs.writeFile(path.join(assetPath, "draft.ts"), this.pub.draft, { encoding: "utf8" }, callback); + } else { + fs.unlink(path.join(assetPath, "draft.ts"), (err) => { + if (err != null && err.code !== "ENOENT") { callback(err); return; } + callback(null); + }); + } + }); + } + + publish(buildPath: string, callback: (err: Error) => any) { + let pathFromId = this.server.data.entries.getPathFromId(this.id); + //if (pathFromId.lastIndexOf(".ts") === pathFromId.length - 5) pathFromId = pathFromId.slice(0, -5); + let outputPath = `${buildPath}/assets/${pathFromId}.js`; + let parentPath = outputPath.slice(0, outputPath.lastIndexOf("/")); + + let code = this.pub.text; + let text = ts.transpile(code); + if(text.indexOf("/// browserify") > -1) { + mkdirp(parentPath, () => { fs.writeFile(outputPath, text, + () => { + var browserify = require('browserify'); + var b = browserify(); + b.add(`${buildPath}/assets/${pathFromId}.js`); + b.bundle((err: any, data: any) => { + var StringDecoder = require('string_decoder').StringDecoder; + var decoder = new StringDecoder('utf8'); + + text = decoder.write(data); + + mkdirp(parentPath, function () { fs.writeFile(outputPath, text, callback); }); + }); + }); + }); + } else { + mkdirp(parentPath, function () { fs.writeFile(outputPath, text, callback); }); + } + + } + + server_editText(client: any, operationData: OperationData, revisionIndex: number, callback: (err: string, operationData?: any, revisionIndex?: number) => any) { + if (operationData.userId !== client.id) { callback("Invalid client id"); return; } + + let operation = new OT.TextOperation(); + if (!operation.deserialize(operationData)) { callback("Invalid operation data"); return; } + + try { operation = this.document.apply(operation, revisionIndex); } + catch (err) { callback("Operation can't be applied"); return; } + + this.pub.draft = this.document.text; + this.pub.revisionId++; + + callback(null, operation.serialize(), this.document.getRevisionId() - 1); + + if (!this.hasDraft) { + this.hasDraft = true; + this.emit("setBadge", "draft", "info"); + } + this.emit("change"); + } + + client_editText(operationData: OperationData, revisionIndex: number) { + let operation = new OT.TextOperation(); + operation.deserialize(operationData); + this.document.apply(operation, revisionIndex); + this.pub.draft = this.document.text; + this.pub.revisionId++; + } + + server_applyDraftChanges(client: any, callback: (err: string) => any) { + this.pub.text = this.pub.draft; + + callback(null); + + if (this.hasDraft) { + this.hasDraft = false; + this.emit("clearBadge", "draft"); + } + + this.emit("change"); + } + + client_applyDraftChanges() { this.pub.text = this.pub.draft; } +} diff --git a/plugins/default/typescript/data/index.ts b/plugins/default/typescript/data/index.ts new file mode 100644 index 0000000..cbcef54 --- /dev/null +++ b/plugins/default/typescript/data/index.ts @@ -0,0 +1,3 @@ +import TypescriptAsset from "./TypescriptAsset"; + +SupCore.system.data.registerAssetClass("typescript", TypescriptAsset); diff --git a/plugins/default/typescript/editors/typescript/index.jade b/plugins/default/typescript/editors/typescript/index.jade new file mode 100644 index 0000000..16a378c --- /dev/null +++ b/plugins/default/typescript/editors/typescript/index.jade @@ -0,0 +1,20 @@ +doctype html +html + head + link(rel="stylesheet",href="/styles/reset.css") + // link(rel="stylesheet",href="/styles/perfectResize.css") + link(rel="stylesheet",href="/styles/dialogs.css") + link(rel="stylesheet",href="../../../../common/textEditorWidget/widget.css") + link(rel="stylesheet",href="index.css") + + body + .text-editor-container + textarea().text-editor + + script(src="/SupCore.js") + script(src="/SupClient.js") + script(src="../../bundles/data.js") + script(src="../../../../common/textEditorWidget/bundles/data.js") + script(src="../../../../common/textEditorWidget/widget.js") + script(src="../../../../common/textEditorWidget/codemirror/mode/javascript/javascript.js") + script(src="index.js") \ No newline at end of file diff --git a/plugins/default/typescript/editors/typescript/index.styl b/plugins/default/typescript/editors/typescript/index.styl new file mode 100644 index 0000000..cd48e27 --- /dev/null +++ b/plugins/default/typescript/editors/typescript/index.styl @@ -0,0 +1,10 @@ +body + display flex + flex-direction column + flex 1 + +.text-editor-container + min-height 100px + +.CodeMirror .CodeMirror-overwrite .CodeMirror-cursor + border-left 8px solid rgba(86, 86, 232, 0.5) diff --git a/plugins/default/typescript/editors/typescript/index.ts b/plugins/default/typescript/editors/typescript/index.ts new file mode 100644 index 0000000..9b56454 --- /dev/null +++ b/plugins/default/typescript/editors/typescript/index.ts @@ -0,0 +1,63 @@ +import TypescriptAsset from "../../data/TypescriptAsset"; + +SupClient.setupHotkeys(); + +let socket: SocketIOClient.Socket; +let projectClient: SupClient.ProjectClient; +let editor: TextEditorWidget; +let asset: TypescriptAsset; + +socket = SupClient.connect(SupClient.query.project); +socket.on("welcome", onWelcome); +socket.on("disconnect", SupClient.onDisconnected); + +function onWelcome(clientId: number) { + projectClient = new SupClient.ProjectClient(socket); + setupEditor(clientId); + + let subscriber: SupClient.AssetSubscriber = { + onAssetReceived, onAssetEdited, + onAssetTrashed: SupClient.onAssetTrashed + }; + + projectClient.subAsset(SupClient.query.asset, "typescript", subscriber); +} + +function onAssetReceived(assetId: string, theAsset: TypescriptAsset) { + asset = theAsset; + editor.setText(asset.pub.draft); +} + +function onAssetEdited(assetId: string, command: string, ...args: any[]) { + if (command === "editText") { + // errorPaneStatus.classList.add("has-draft"); + editor.receiveEditText(args[0]); + } else if (command === "applyDraftChanges") { + // errorPaneStatus.classList.remove("has-draft"); + } +} + +function setupEditor(clientId: number) { + let textArea = document.querySelector(".text-editor"); + editor = new TextEditorWidget(projectClient, clientId, textArea, { + mode: "text/typescript", + extraKeys: { + "Ctrl-S": () => { applyDraftChanges(); }, + "Cmd-S": () => { applyDraftChanges(); }, + }, + editCallback: onEditText, + sendOperationCallback: onSendOperation + }); +} + +function onEditText(text: string, origin: string) { /* Ignore */ } + +function onSendOperation(operation: OperationData) { + socket.emit("edit:assets", SupClient.query.asset, "editText", operation, asset.document.getRevisionId(), (err: string) => { + if (err != null) { alert(err); SupClient.onDisconnected(); } + }); +} + +function applyDraftChanges() { + socket.emit("edit:assets", SupClient.query.asset, "applyDraftChanges", (err: string) => { if (err != null) { alert(err); SupClient.onDisconnected(); }}); +} diff --git a/plugins/default/typescript/index.d.ts b/plugins/default/typescript/index.d.ts new file mode 100644 index 0000000..3984e0b --- /dev/null +++ b/plugins/default/typescript/index.d.ts @@ -0,0 +1,3 @@ +/// +/// +/// diff --git a/plugins/default/typescript/package.json b/plugins/default/typescript/package.json new file mode 100644 index 0000000..897b0a7 --- /dev/null +++ b/plugins/default/typescript/package.json @@ -0,0 +1,14 @@ +{ + "name": "superpowers-web-cocadev-typescript-plugin", + "description": "Typescript plugin for Superpowers Web, the collaborative static site editor", + "version": "1.0.0", + "license": "ISC", + "scripts": { + "build": "gulp --gulpfile=../../../../../scripts/pluginGulpfile.js --cwd=." + }, + "dependencies": { + "browserify": "^12.0.1", + "operational-transform": "^0.2.3", + "typescript": "^1.7.5" + } +} diff --git a/plugins/default/typescript/public/editors/typescript/icon.svg b/plugins/default/typescript/public/editors/typescript/icon.svg new file mode 100644 index 0000000..631829b --- /dev/null +++ b/plugins/default/typescript/public/editors/typescript/icon.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/plugins/default/typescript/public/locales/en/plugin.json b/plugins/default/typescript/public/locales/en/plugin.json new file mode 100644 index 0000000..ec7c04c --- /dev/null +++ b/plugins/default/typescript/public/locales/en/plugin.json @@ -0,0 +1,7 @@ +{ + "editors": { + "typescript": { + "title": "Typescript" + } + } +} \ No newline at end of file diff --git a/plugins/default/typescript/tsconfig.json b/plugins/default/typescript/tsconfig.json new file mode 100644 index 0000000..d322f2a --- /dev/null +++ b/plugins/default/typescript/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "noImplicitAny": true + } +} From 5f78a7cc23548d615db2b76b25e42c1c82304640 Mon Sep 17 00:00:00 2001 From: coca44 Date: Sun, 10 Jan 2016 12:59:59 +0100 Subject: [PATCH 2/3] Update index.d.ts --- plugins/default/typescript/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/default/typescript/index.d.ts b/plugins/default/typescript/index.d.ts index 3984e0b..d275d68 100644 --- a/plugins/default/typescript/index.d.ts +++ b/plugins/default/typescript/index.d.ts @@ -1,3 +1,3 @@ /// /// -/// +/// From 75a83d4e080b033c1f07a1e4d3e78ddba568c53a Mon Sep 17 00:00:00 2001 From: coca44 Date: Sun, 10 Jan 2016 13:03:03 +0100 Subject: [PATCH 3/3] Update the reference path --- plugins/default/typescript/data/TypescriptAsset.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/default/typescript/data/TypescriptAsset.ts b/plugins/default/typescript/data/TypescriptAsset.ts index 85beec0..6f36d49 100644 --- a/plugins/default/typescript/data/TypescriptAsset.ts +++ b/plugins/default/typescript/data/TypescriptAsset.ts @@ -1,4 +1,4 @@ -/// +/// import * as OT from "operational-transform"; import * as mkdirp from "mkdirp";