Skip to content

Commit 85b0fd1

Browse files
committed
first working version
1 parent 14e363e commit 85b0fd1

14 files changed

+3578
-3
lines changed

.dockerignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.vscode
2+
.git*
3+
4+
Dockerfile
5+
6+
node_modules
7+
build
8+
static/dist

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.vscode
2+
3+
node_modules
4+
build
5+
static/dist

Dockerfile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
ARG IMAGE=node:14.3.0-alpine3.11
2+
3+
FROM ${IMAGE} as build
4+
5+
WORKDIR /app
6+
7+
COPY package.json package-lock.json ./
8+
9+
RUN npm install
10+
11+
COPY . .
12+
13+
RUN sh ./build.sh
14+
15+
FROM ${IMAGE}
16+
17+
WORKDIR /app
18+
19+
COPY --from=build /app/static /app/static
20+
21+
COPY --from=build /app/build/server.js /app/server.js
22+
23+
CMD ["node", "server.js"]

build.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
set -ex
2+
3+
sh ./build_client.sh
4+
5+
./node_modules/.bin/browserify \
6+
-o build/server.js \
7+
--node \
8+
server.js

build_client.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
set -ex
2+
3+
./node_modules/.bin/browserify \
4+
-t browserify-css \
5+
-o static/dist/client.js \
6+
client.js
7+
8+
./node_modules/.bin/browserify \
9+
-t browserify-css \
10+
-o static/dist/create.js \
11+
create.js

client.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@import "./node_modules/codemirror/lib/codemirror.css";
2+
3+
.CodeMirror {
4+
border: 1px solid #eee;
5+
height: auto;
6+
min-height: 300px;
7+
}

client.js

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
const sharedb = require('sharedb/lib/client');
2+
const OT = require('ot-text-unicode');
3+
const CodeMirror = require('codemirror');
4+
require('./cm.js');
5+
6+
sharedb.types.register(OT.type);
7+
8+
const ReconnectingWebSocket = require('reconnecting-websocket');
9+
const wsproto = window.location.protocol === "https:" ? 'wss://' : "ws://";
10+
const socket = new ReconnectingWebSocket(wsproto + window.location.host + "/client");
11+
const connection = new sharedb.Connection(socket);
12+
13+
const element = document.getElementById("textarea");
14+
const connStatusSpan = document.getElementById('conn-status-span');
15+
const docStatusSpan = document.getElementById('doc-status-span');
16+
connStatusSpan.innerHTML = 'Not Connected';
17+
18+
// element.style.backgroundColor = 'gray';
19+
socket.addEventListener('open', function () {
20+
console.log("socket connected");
21+
connStatusSpan.innerHTML = 'Connected';
22+
connStatusSpan.style.backgroundColor = 'white';
23+
});
24+
25+
socket.addEventListener('close', function () {
26+
console.log("socket cloed");
27+
connStatusSpan.innerHTML = 'Closed';
28+
connStatusSpan.style.backgroundColor = 'gray';
29+
});
30+
31+
socket.addEventListener('error', function () {
32+
console.log("socket error");
33+
connStatusSpan.innerHTML = 'Error';
34+
connStatusSpan.style.backgroundColor = 'red';
35+
});
36+
37+
const setDocStatus = (msg) => {
38+
docStatusSpan.innerHTML = msg;
39+
};
40+
41+
setDocStatus("initializing...");
42+
43+
let needSync = false;
44+
const codeMirror = new CodeMirror(element, {
45+
readOnly: true,
46+
lineNumbers: true,
47+
viewportMargin: Infinity,
48+
});
49+
50+
const findMode = (mode) => {
51+
for (let i = 0; i < CodeMirror.modeInfo.length; i++) {
52+
const info = CodeMirror.modeInfo[i];
53+
if (info.mode === mode) {
54+
return info;
55+
}
56+
}
57+
};
58+
59+
(() => {
60+
const sel = document.getElementById("mode");
61+
for (name in CodeMirror.modes) {
62+
const mode = findMode(name);
63+
// console.log(name, mode);
64+
if (mode) {
65+
const opt = document.createElement('option');
66+
opt.text = mode.name;
67+
opt.value = mode.mode;
68+
sel.append(opt);
69+
}
70+
}
71+
sel.addEventListener("change", (event) => {
72+
const mode = event.target.value;
73+
console.log("setting codemirror mode to", mode);
74+
codeMirror.setOption("mode", mode);
75+
});
76+
})();
77+
78+
const [addHistory, clearHistory] = (() => {
79+
const history = document.getElementById("history");
80+
81+
return [(txt) => {
82+
const record = document.createElement("div");
83+
const code = document.createElement("code");
84+
code.innerHTML = txt;
85+
record.appendChild(code);
86+
history.prepend(record);
87+
}, () => {
88+
history.innerHTML = "";
89+
}];
90+
})();
91+
92+
(() => {
93+
const clear = document.getElementById("clear");
94+
clear.addEventListener("click", clearHistory);
95+
})();
96+
97+
// Create local Doc instance mapped to 'examples' collection document with id 'textarea'
98+
const pathnames = window.location.pathname.split("/");
99+
const doc = connection.get('examples', pathnames[pathnames.length - 1]);
100+
101+
doc.subscribe(function (err) {
102+
if (err) throw err;
103+
104+
console.log("doc", doc);
105+
// console.log("doc.data", doc.data);
106+
codeMirror.setValue(doc.data);
107+
codeMirror.setOption("readOnly", false);
108+
setDocStatus("initialized");
109+
110+
doc.on('error', (err) => {
111+
console.error("error occured", err);
112+
});
113+
114+
doc.on('before op', (ops, source) => {
115+
console.log("doc before op:", ops, source);
116+
});
117+
118+
doc.on("op", (ops, source) => {
119+
console.group("doc op:", ops, source);
120+
try {
121+
if (source) {
122+
return;
123+
}
124+
OT.type.checkOp(ops);
125+
let index = 0;
126+
for (let i = 0; i < ops.length; i++) {
127+
const c = ops[i];
128+
switch (typeof c) {
129+
case 'object': {
130+
const posFrom = codeMirror.posFromIndex(index);
131+
const posTo = codeMirror.posFromIndex(index + c.d);
132+
codeMirror.replaceRange("", posFrom, posTo, "+sharedb");
133+
break;
134+
}
135+
case 'string': {
136+
const posFrom = codeMirror.posFromIndex(index);
137+
codeMirror.replaceRange(c, posFrom, posFrom, "+sharedb");
138+
index += c.length;
139+
break;
140+
}
141+
case 'number': {
142+
index += c;
143+
break;
144+
}
145+
default:
146+
throw new Error("unknow type of", c);
147+
}
148+
}
149+
} catch (thrown) {
150+
console.error(thrown.message);
151+
throw thrown;
152+
} finally {
153+
console.groupEnd();
154+
}
155+
});
156+
157+
const sync = () => {
158+
doc.fetch((err) => {
159+
if (err) {
160+
console.error("error while fetching, resync in 5 sec", err);
161+
setTimeout(sync, 5000);
162+
} else {
163+
// console.log("codeMirror.getValue()", codeMirror.getValue())
164+
if (doc.type === null) {
165+
setDocStatus("synchronization failed... please save your doc manually and refresh");
166+
// doc.create(codeMirror.getValue(), (err) => {
167+
// if (err) {
168+
// console.error("error while creating, resync in 5 sec", err);
169+
// setTimeout(sync, 5000);
170+
// } else {
171+
// console.log("doc created with local data");
172+
// needSync = false;
173+
// }
174+
// });
175+
} else {
176+
addHistory(codeMirror.getValue());
177+
codeMirror.setValue(doc.data);
178+
}
179+
}
180+
});
181+
};
182+
183+
codeMirror.on("beforeChange", (codeMirror, change) => {
184+
console.group("on codeMirror beforeChange");
185+
try {
186+
if (needSync) {
187+
console.log("need sychronize");
188+
while (change) {
189+
if (change.origin !== "setValue") {
190+
change.cancel();
191+
}
192+
change = change.next;
193+
}
194+
} else {
195+
while (change) {
196+
console.log("change", change);
197+
if (change.origin !== "+sharedb") {
198+
const indexFrom = codeMirror.indexFromPos(change.from);
199+
const indexTo = codeMirror.indexFromPos(change.to);
200+
doc.submitOp([
201+
indexFrom,
202+
{ d: indexTo - indexFrom },
203+
change.text.join("\n")
204+
], (err) => {
205+
if (err) {
206+
console.error("error while submitting", err);
207+
setDocStatus("synchronizing...");
208+
if (!needSync) {
209+
console.log("already synchronizing...");
210+
needSync = true;
211+
sync();
212+
}
213+
}
214+
});
215+
}
216+
change = change.next;
217+
}
218+
}
219+
} catch (thrown) {
220+
console.error(thrown.message);
221+
throw thrown;
222+
} finally {
223+
console.groupEnd();
224+
}
225+
});
226+
227+
codeMirror.on("change", (codeMirror, change) => {
228+
console.group("on codeMirror change");
229+
try {
230+
// console.log("doc.data", doc.data);
231+
if (needSync) {
232+
while (change) {
233+
if (change.origin === "setValue") {
234+
needSync = false;
235+
console.log("sync finished");
236+
setDocStatus("synchronization finished, old data has been saved in history");
237+
}
238+
change = change.next;
239+
}
240+
}
241+
} finally {
242+
console.groupEnd();
243+
}
244+
});
245+
});

cm.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
require('./client.css');
2+
3+
require('codemirror/mode/meta');
4+
require('codemirror/mode/clike/clike');
5+
require('codemirror/mode/cmake/cmake');
6+
require('codemirror/mode/css/css');
7+
require('codemirror/mode/dockerfile/dockerfile');
8+
require('codemirror/mode/go/go');
9+
require('codemirror/mode/htmlembedded/htmlembedded');
10+
require('codemirror/mode/htmlmixed/htmlmixed');
11+
require('codemirror/mode/http/http');
12+
require('codemirror/mode/javascript/javascript');
13+
require('codemirror/mode/jsx/jsx');
14+
require('codemirror/mode/lua/lua');
15+
require('codemirror/mode/markdown/markdown');
16+
require('codemirror/mode/nginx/nginx');
17+
require('codemirror/mode/php/php');
18+
require('codemirror/mode/protobuf/protobuf');
19+
require('codemirror/mode/python/python');
20+
require('codemirror/mode/shell/shell')
21+
require('codemirror/mode/sql/sql')
22+
require('codemirror/mode/xml/xml')
23+
require('codemirror/mode/yaml/yaml')

create.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
(() => {
2+
const element = document.getElementById("create");
3+
element.addEventListener("click", (event) => {
4+
console.log(event.target);
5+
event.target.disabled = true;
6+
fetch("/client", {
7+
method: "POST",
8+
}).then((response) => {
9+
if (response.redirected) {
10+
window.location.href = response.url;
11+
} else {
12+
event.target.disabled = false;
13+
}
14+
}).catch(() => {
15+
event.target.disabled = false;
16+
});
17+
});
18+
})();

0 commit comments

Comments
 (0)