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..6f36d49
--- /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..d275d68
--- /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 @@
+
+
+
+
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
+ }
+}