sn-collab-editor 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c1b5b7258216bb3b991dc25a5d7a3f2dfc21083c
4
- data.tar.gz: 770e98e0315ccc430de4a63076ae79f58da3b83c
3
+ metadata.gz: 584e0566ecfea667d8de83f27f541eff2dff10f1
4
+ data.tar.gz: d3fd5591f13379168c8037db93a18c751635a96e
5
5
  SHA512:
6
- metadata.gz: 9e5907f3d79847ed959591ceadbb83ab55a5fa9532155c69ecd3f9668176823259db59eed50cfc034b2bf6535126bb89aa1073e2397e84be23373a4bab07c8dc
7
- data.tar.gz: d811e1bca298048531698e55447d212681bb4aab07c3f1416152144bb224c6b756d8f7d68508d95a5cec54a9117c3a228a0943a4f595526e3155ba94dc8fd49a
6
+ metadata.gz: 68145c660dd250530bceb127b67bcd402315c63ebf017da2ca08bb901a5e178cc62ed6c14c9bc682a775d8fa6ae1a18c9f2d59d2d322da53f8982a3fa398124d
7
+ data.tar.gz: 5e8e96e7a82dfb96d6d76cefdea4cf74708f4a074245e9d76f9e34d4c26a59865a54d0ae926d2ddcc6d809338d0da99a5e5e7b64be094f14dc9bd3319f1dc37d
@@ -9,35 +9,145 @@ document.addEventListener("DOMContentLoaded", function(event) {
9
9
 
10
10
  var clientId = Math.random()*100;
11
11
 
12
+ var editor;
13
+ App.registerChainpadObserver = function(inEditor) {
14
+ editor = inEditor;
15
+ }.bind(this)
16
+
17
+ var _chainpad, patchText;
18
+
19
+ function initChainpad() {
20
+ _chainpad = ChainPad.create({
21
+ checkpointInterval: 3,
22
+ logLevel: 0
23
+ });
24
+
25
+ patchText = TextPatcher.create({
26
+ realtime: _chainpad,
27
+ })
28
+
29
+ _chainpad.onChange(function (offset, toRemove, toInsert) {
30
+ var currentContent = editor.getContent();
31
+ var newContent = currentContent.substring(0, offset) + toInsert + currentContent.substring(offset + toRemove);
32
+
33
+ var op = {offset: offset, toRemove: toRemove, toInsert: toInsert};
34
+ var oldCursor = {};
35
+ oldCursor.selectionStart = cursorToPos(App.editor.getCursor('from'), currentContent);
36
+ oldCursor.selectionEnd = cursorToPos(App.editor.getCursor('to'), currentContent);
37
+
38
+ editor.setContent(newContent);
39
+
40
+ var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
41
+ return TextPatcher.transformCursor(oldCursor[attr], op);
42
+ });
43
+
44
+ if(selects[0] === selects[1]) {
45
+ App.editor.setCursor(posToCursor(selects[0], newContent));
46
+ }
47
+ else {
48
+ App.editor.setSelection(posToCursor(selects[0], newContent), posToCursor(selects[1], newContent));
49
+ }
50
+ });
51
+
52
+ _chainpad.onMessage(function(message, cb){
53
+ var success = App.socket.channel.post(message);
54
+ setTimeout(function () {
55
+ if(!success) {
56
+ console.log("Message not successful");
57
+ }
58
+ cb();
59
+ }, 1);
60
+ })
61
+
62
+ _chainpad.start();
63
+ }
64
+
65
+ function getChainpad() {
66
+ return _chainpad;
67
+ }
68
+
69
+
70
+ App.textEditorDidMakeChanges = function(text) {
71
+ if(patchText) {
72
+ patchText(text);
73
+ }
74
+ }
75
+
76
+ function posToCursor(position, newText) {
77
+ var cursor = {
78
+ line: 0,
79
+ ch: 0
80
+ };
81
+ var textLines = newText.substr(0, position).split("\n");
82
+ cursor.line = textLines.length - 1;
83
+ cursor.ch = textLines[cursor.line].length;
84
+ return cursor;
85
+ }
86
+
87
+ function cursorToPos(cursor, oldText) {
88
+ var cLine = cursor.line;
89
+ var cCh = cursor.ch;
90
+ var pos = 0;
91
+ var textLines = oldText.split("\n");
92
+ for (var line = 0; line <= cLine; line++) {
93
+ if(line < cLine) {
94
+ pos += textLines[line].length+1;
95
+ }
96
+ else if(line === cLine) {
97
+ pos += cCh;
98
+ }
99
+ }
100
+ return pos;
101
+ };
102
+
12
103
  App.socket = {};
13
104
  App.socket.cable = ActionCable.createConsumer("/collab/cable");
14
105
 
106
+ var ignoreNextMessage = false;
107
+
15
108
  App.socket.subscribeToDoc = function(docId, callback) {
16
109
  App.socket.channel = App.socket.cable.subscriptions.create({channel: "EditChannel", doc_id: docId, client_id: clientId}, {
17
110
  connected: function() {
18
- // Called when the subscription is ready for use on the server
19
- this.retrieve();
20
- },
111
+ App.socket.channel.retrieve();
112
+ }.bind(this),
21
113
 
22
114
  disconnected: function() {
23
- // Called when the subscription has been terminated by the server
24
115
  },
25
116
 
26
117
  received: function(data) {
27
- // Called when there's incoming data on the websocket for this channel
28
- if(data.client_id != clientId || data.retrieve) {
29
- var result = App.crypto.decrypt(data.message, App.encryptionKey(), data.iv, data.auth, App.authKey());
30
- callback(result);
118
+ if(data.client_id == clientId && !data.initial_retrieve) {
119
+ return;
120
+ }
121
+
122
+ var patches = [];
123
+ if(data.initial_retrieve) {
124
+ initChainpad();
125
+ patches = data.patches;
126
+ } else if(data.patch) {
127
+ patches = [data.patch];
31
128
  }
129
+
130
+ patches = patches.map(function(patch){
131
+ return App.crypto.decrypt(patch.content, App.encryptionKey(), patch.iv, patch.auth, App.authKey());
132
+ })
133
+
134
+ patches.forEach(function(patch){
135
+ getChainpad().message(patch);
136
+ })
32
137
  },
33
138
 
34
139
  retrieve: function() {
35
140
  return this.perform('retrieve', {doc_id: docId, client_id: clientId});
36
141
  },
37
142
 
38
- post: function(message) {
39
- var result = App.crypto.encrypt(message, App.encryptionKey(), App.authKey());
40
- var data = {message: result.cipher, iv: result.iv, auth: result.auth, doc_id: docId, client_id: clientId};
143
+ post: function(patch) {
144
+ if(ignoreNextMessage) {
145
+ ignoreNextMessage = false;
146
+ return;
147
+ }
148
+
149
+ var result = App.crypto.encrypt(patch, App.encryptionKey(), App.authKey());
150
+ var data = {content: result.cipher, iv: result.iv, auth: result.auth, edit_token: App.editToken, doc_id: docId, client_id: clientId};
41
151
  return this.perform('post', data);
42
152
  }
43
153
  });
@@ -3,61 +3,117 @@
3
3
 
4
4
  document.addEventListener("DOMContentLoaded", function(event) {
5
5
 
6
- var editor = document.getElementById("editor");
6
+ var textarea, editor, isSubscribedToDoc;
7
+ var isInSN = window.parent != window;
8
+
9
+ function getEditorValue() {
10
+ return editor.getDoc().getValue() || "";
11
+ }
12
+
13
+ function configureEditor() {
14
+ textarea = document.getElementById("editor");
15
+
16
+ editor = App.editor = CodeMirror.fromTextArea(textarea, {
17
+ mode: "text/html",
18
+ lineNumbers: true,
19
+ lineWrapping: true,
20
+ mode: "markdown"
21
+ });
22
+
23
+ editor.on("change", function(cm, change){
24
+ if(isSubscribedToDoc) {
25
+ App.textEditorDidMakeChanges(getEditorValue());
26
+ }
27
+ sendDocToSN()
28
+ })
29
+ }
30
+
31
+ App.registerChainpadObserver({
32
+ getContent: function() {
33
+ return getEditorValue();
34
+ },
35
+
36
+ setContent: function(content) {
37
+ editor.getDoc().setValue(content);
38
+ }
39
+ })
7
40
 
8
41
  function createNewDocument() {
9
42
  window.location.href = "/collab/doc/new";
10
43
  }
11
44
 
12
45
  function subscribeToDocId(docId) {
13
- App.socket.subscribeToDoc(docId, function(message){
14
- editor.value = message;
15
- })
46
+ configureEditor();
47
+ App.socket.subscribeToDoc(docId, function(message){})
48
+ isSubscribedToDoc = true;
49
+ refreshKey();
50
+
51
+ var incomingText = sessionStorage.getItem("sn_text");
52
+ if(incomingText) {
53
+ editor.getDoc().setValue(incomingText);
54
+ sessionStorage.removeItem("sn_text");
55
+ }
16
56
  }
17
57
 
18
58
  var location = window.location.href;
19
59
  var uuidResults = location.match(/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i);
20
60
  var uuid = uuidResults ? uuidResults[0] : null;
21
61
  var hasDocument = uuid != null;
22
- var noteId = sessionStorage.getItem("lastNoteId");
62
+ var noteId = sessionStorage.getItem("sn_lastNoteId");
23
63
 
24
64
  var key, didGenerateKey;
25
- if(location.indexOf("#key=") != -1) {
26
- key = location.split("#key=").slice(-1)[0];
27
- } else {
28
- key = App.crypto.generateRandomKey(32);
29
- didGenerateKey = true;
30
- }
31
- setKey(key);
32
65
 
33
- if(window.parent != window) {
34
- if(hasDocument) {
35
- // inform parent of new document
36
- window.parent.postMessage({text: buildParamsString({id: uuid, key: key}), id: noteId}, '*');
37
- subscribeToDocId(uuid);
66
+ function refreshKey() {
67
+ if(location.indexOf("#key=") != -1) {
68
+ key = location.split("#key=").slice(-1)[0];
38
69
  } else {
39
- window.parent.postMessage({status: "ready"}, '*');
40
- }
41
- } else {
42
- if(!hasDocument) {
43
- createNewDocument();
44
- } else {
45
- subscribeToDocId(uuid);
70
+ key = App.crypto.generateRandomKey(32);
71
+ didGenerateKey = true;
46
72
  }
73
+ setKey(key);
47
74
  }
48
75
 
49
76
  function setKey(key) {
50
77
  App.key = key;
51
- var span = document.getElementById("url-key");
52
- if(span) {
53
- span.textContent = "#key=" + key;
54
- }
55
78
 
56
79
  if(window.location.href.indexOf("#key") == -1) {
57
80
  window.history.pushState('Document', 'Document', "#key=" + key);
58
81
  }
82
+
83
+ if(textarea) {
84
+ var url = window.location.href;
85
+ var etString = "?et=";
86
+ var etIndex = url.indexOf(etString);
87
+ var editingUrl, viewingUrl, editToken;
88
+ if(etIndex != -1) {
89
+ // has edit token
90
+ var editToken = App.editToken = url.substring(etIndex + etString.length, url.indexOf("#"));
91
+ editingUrl = url;
92
+ viewingUrl = url.replace(etString + editToken, "");
93
+ } else {
94
+ // no edit token
95
+ viewingUrl = url;
96
+ }
97
+
98
+ var editingElement = document.getElementById("editing-url");
99
+ if(editingUrl) {
100
+ editingElement.innerHTML = editingUrl;
101
+ editingElement.href = editingUrl;
102
+ sendDocToSN();
103
+ } else {
104
+ var editingWrapper = document.getElementById("editing-url-wrapper");
105
+ editingWrapper.parentNode.removeChild(editingWrapper);
106
+ }
107
+
108
+ var viewingElement = document.getElementById("viewing-url");
109
+ viewingElement.innerHTML = viewingUrl;
110
+ viewingElement.href = viewingUrl;
111
+ }
59
112
  }
60
113
 
114
+
115
+ // ** Communication with Standard Notes App **
116
+
61
117
  function buildParamsString(doc) {
62
118
  var string = "";
63
119
  for(var key in doc) {
@@ -67,34 +123,68 @@ document.addEventListener("DOMContentLoaded", function(event) {
67
123
  return string;
68
124
  }
69
125
 
70
- window.addEventListener("message", function(event){
71
-
72
- var text = event.data.text || "";
73
- sessionStorage.setItem("lastNoteId", event.data.id);
126
+ function sendDocToSN() {
127
+ if(!isInSN) {
128
+ return;
129
+ }
74
130
 
75
- var paramString = text.split("%%Do not modify above this line%%")[0];
76
- var lines = paramString.split("\n");
77
- var params = {};
78
- lines.forEach(function(line){
79
- var comps = line.split(": ");
80
- var key = comps[0];
81
- var value = comps[1];
82
- params[key] = value;
83
- })
131
+ var noteBody = buildParamsString({url: window.location.href});
132
+ if(editor) {
133
+ var disclaimer = "// text you enter below will not transfer to the live editor.\n// the below is just a backup for your records";
134
+ noteBody += "\n\n" + disclaimer + "\n\n" + getEditorValue();
135
+ }
136
+ window.parent.postMessage({text: noteBody, id: noteId}, '*');
137
+ }
84
138
 
85
- let key = params["key"];
86
- let docId = params["id"]
87
- if (docId && docId.length) {
88
- window.location.href = "/collab/doc/" + docId + "#key=" + key;
139
+ if(isInSN) {
140
+ if(hasDocument) {
141
+ // inform parent of new document
142
+ sendDocToSN();
143
+ subscribeToDocId(uuid);
89
144
  } else {
90
- createNewDocument();
145
+ window.parent.postMessage({status: "ready"}, '*');
91
146
  }
92
- }, false);
147
+ window.addEventListener("message", function(event){
148
+ var text = event.data.text || "";
149
+ sessionStorage.setItem("sn_lastNoteId", event.data.id);
93
150
 
94
- if(editor) {
95
- editor.addEventListener("input", function(event){
96
- var text = event.target.value;
97
- App.socket.channel.post(text);
98
- })
151
+ var splitTarget = "%%Do not modify above this line%%";
152
+ var comps = text.split(splitTarget);
153
+ var snText;
154
+ var hasParams = text.indexOf(splitTarget) != -1;
155
+ if(hasParams) {
156
+ snText = comps[1];
157
+ } else {
158
+ snText = text;
159
+ sessionStorage.setItem("sn_text", snText)
160
+ }
161
+
162
+
163
+ var paramString = comps[0];
164
+ var params = {};
165
+ var lines = paramString.split("\n");
166
+ lines.forEach(function(line){
167
+ var comps = line.split(": ");
168
+ var key = comps[0];
169
+ var value = comps[1];
170
+ params[key] = value;
171
+ })
172
+
173
+ let url = params["url"];
174
+ if (url) {
175
+ window.location.href = url;
176
+ } else {
177
+ createNewDocument();
178
+ }
179
+ }, false);
99
180
  }
181
+
182
+ if(!isInSN){
183
+ if(!hasDocument) {
184
+ createNewDocument();
185
+ } else {
186
+ subscribeToDocId(uuid);
187
+ }
188
+ }
189
+
100
190
  })
@@ -29,22 +29,49 @@ html, body {
29
29
  flex-direction: column;
30
30
  }
31
31
 
32
- #editor {
32
+ .CodeMirror {
33
33
  flex: 1;
34
34
  width: 100%;
35
35
  height: 100%;
36
36
  resize: none;
37
- font-size: 18px;
38
- border: 1px solid rgb(226, 226, 226);
37
+ font-size: 16px;
39
38
  border-radius: 4px;
40
39
  padding: 15px;
41
40
  margin-top: 10px;
42
41
  font-family: monospace;
42
+ margin-top: 10px;
43
+ padding: 0;
43
44
  }
44
45
 
45
- #url {
46
+ .url-wrapper {
46
47
  word-wrap: break-word;
47
48
  word-break: break-all;
49
+ font-size: 10px;
50
+ margin-bottom: 3px;
51
+
52
+ opacity: 0.3;
53
+
54
+ &:hover {
55
+ opacity: 1.0;
56
+ }
57
+
58
+ .label {
59
+ font-weight: bold;
60
+ }
61
+
62
+ .url {
63
+ font-weight: bold;
64
+ color: rgba(black, 0.5);
65
+ font-size: 10px;
66
+ text-decoration: none;
67
+
68
+
69
+ &:visited { text-decoration: none; color:black; }
70
+ &:hover {
71
+ text-decoration: underline;
72
+ color: black;
73
+ }
74
+ }
48
75
  }
49
76
 
50
77
  * {