sn-collab-editor 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  * {