├── .gitignore ├── .travis.yml ├── History.md ├── LICENSE ├── README.md ├── demo ├── .gitignore ├── .meteor │ ├── .gitignore │ ├── identifier │ ├── packages │ ├── platforms │ ├── release │ └── versions ├── README.md ├── client │ ├── client.js │ ├── client.styl │ └── index.html ├── index.js ├── package.json ├── smart.json └── smart.lock ├── settings-example.json ├── sharejs-ace ├── .gitignore ├── History.md ├── README.md ├── ace.js ├── client.js ├── package.js └── templates.html ├── sharejs-base ├── .gitignore ├── README.md ├── loadBCSocket.js ├── package.js ├── sharejs-client.js ├── sharejs-server.js ├── sharejs-templates.html ├── tests │ └── server_test.js └── textarea.js └── sharejs-codemirror ├── .gitignore ├── History.md ├── README.md ├── client.js ├── cm.js ├── package.js └── templates.html /.gitignore: -------------------------------------------------------------------------------- 1 | .versions 2 | 3 | lib-cov 4 | *.seed 5 | *.log 6 | *.csv 7 | *.dat 8 | *.out 9 | *.pid 10 | *.gz 11 | *.idea 12 | 13 | pids 14 | logs 15 | results 16 | 17 | .npm 18 | npm-debug.log 19 | .build* 20 | 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_install: 5 | - "curl -L http://git.io/ejPSng | /bin/sh" 6 | env: 7 | - WORKING_DIR="sharejs-base" 8 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | ## vNEXT 2 | 3 | ## v0.10.0 4 | 5 | * Switched to in memory storage as the storage in the db leads to problem with the wiretiger engine of meteor 1.4 6 | 7 | ## v0.9.0 8 | 9 | * dependencies are imported as npm modules 10 | * Code is rewritten in ecmascript 11 | * used module system to reduce globals 12 | * removed the authentication system, as it added no real security 13 | * codemirrror was updated to latest version 14 | * codemirror addons and themes are loaded as static assets 15 | 16 | ## v0.8.0 17 | 18 | * supports Meteor 1.3 19 | * moved ace related stuff into sharejs-ace 20 | 21 | ## v0.7.5 22 | 23 | * Updates for Meteor 1.2. 24 | 25 | ## v0.7.4 26 | 27 | * Bump release for Windows. 28 | 29 | ## v0.7.3 30 | 31 | * Remove the unnecessary Mongo driver reference entirely. 32 | 33 | ## v0.7.2 34 | 35 | * Update references to Mongo driver to support Meteor 1.0.4. 36 | 37 | ## v0.7.1 38 | 39 | * Update format of config callbacks (caused #41 prior to being released.) 40 | 41 | ## v0.7.0 42 | 43 | * **The base ShareJS package and the Ace and CodeMirror editors are now different meteor packages.** `mizzao:sharejs` only has raw text editing, while `mizzao:sharejs-ace` provides the Ace editor and `mizzao:sharejs-codemirror` provides CodeMirror. 44 | 45 | ## v0.6.1 46 | 47 | * **Update and publish package for Meteor 0.9.2 or later**. 48 | * Create separate `onRender` and `onConnect` callbacks for the Ace editor. 49 | 50 | ## v0.6.0 51 | 52 | * Updated for Meteor 0.8.3, integrating document switching with `Blaze.View`. See the updated demo. Changing the `docId` element of the helper no longer triggers a re-render. 53 | * Bump ShareJS version to 0.6.3. 54 | * Set Ace config callback to run after the editor has been rendered. 55 | * Created a helper function to create a document on the server, and added a couple of tests (implicitly testing the rest of the stack.) 56 | * Set the default value of `opsCollectionPerDoc` to `false` for the ShareJS Mongo driver. This reduces the amount of collection namespace pollution. **However, you will need to migrate your data from the previous per-document collections if you didn't use this option in an earlier version of this package.** Otherwise, your previous documents will be empty when your app loads. You can also override this behavior and use your previous schema by including the following in `Meteor.settings`: 57 | 58 | ```json 59 | { 60 | "sharejs": { 61 | "options": { 62 | "db": { 63 | "type": "mongo", 64 | "opsCollectionPerDoc": true 65 | } 66 | } 67 | } 68 | } 69 | ``` 70 | * Store the Meteor `userId` in the ShareJS ops collection through some egregious monkey-patching. Hopefully the modularity of ShareJS 0.7 will make this easier; to make sure this happens, you should pitch in at https://github.com/share/ShareJS/issues/286. 71 | 72 | ## v0.5.2 73 | 74 | * Added built-in support for loading Ace editor extensions and themes. 75 | 76 | ## v0.5.1 77 | 78 | * Fixed an issue with the Ace submodule repository path. 79 | 80 | ## v0.5.0 81 | 82 | * Hacked up support for Meteor 0.8.0 (Blaze). Expect changes as Meteor's UI API improves. 83 | 84 | ## v0.4.0 85 | 86 | * Preliminary support for user-accounts integration. #6 87 | * More robust connection on both http and https services. 88 | * Removed any references to Redis. It's much better to use Mongo with Meteor! 89 | * Fixed a bug that prevented editing with IE. 90 | * Moved Ace into a submodule instead of using build-fetcher - this allows themes to be more easily supported in the future. 91 | 92 | ## v0.3.2 93 | 94 | * Download CDN files (ace) on the server, and serve as a Meteor file. #2, #3. 95 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Andrew Mao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | meteor-sharejs 2 | ============== 3 | 4 | 5 | Meteor smart package for transparently adding [ShareJS](https://github.com/share/ShareJS) editors to an app. Includes [CodeMirror](http://codemirror.net/) and the [Ace editor](http://ace.c9.io/). 6 | 7 | This package is only tested for meteor 1.3 or later 8 | 9 | ## Install 10 | 11 | For vanilla ShareJS with text only: 12 | 13 | ``` 14 | $ meteor add mizzao:sharejs 15 | ``` 16 | 17 | For ShareJS with the Ace editor 18 | 19 | ``` 20 | $ meteor add mizzao:sharejs-ace 21 | ``` 22 | 23 | For ShareJS with the CodeMirror editor: 24 | 25 | ``` 26 | $ meteor add mizzao:sharejs-codemirror 27 | ``` 28 | 29 | ## Usage 30 | 31 | No configuration necessary for anonymous document editing. If you want to integrate with Meteor accounts, see below. 32 | 33 | Use this helper on the client to get a `textarea` with the specified `docid` (as an argument) and with a DOM id of "editor", for example: 34 | 35 | ``` 36 | {{> sharejsText docid=docid id="editor"}} 37 | ``` 38 | 39 | Use this helper to get an Ace editor. Make sure you specify a size (via CSS) on the #editor div or you won't see anything. 40 | ``` 41 | {{> sharejsAce docid=docid id="editor"}} 42 | ``` 43 | 44 | Use this helper to get a CodeMirror editor. 45 | ``` 46 | {{> sharejsCM docid=docid id="editor"}} 47 | ``` 48 | 49 | The templates will clean themselves up when re-rendered (i.e., you have several documents and docid changes.) 50 | 51 | ## Client Configuration 52 | 53 | For the Ace and CodeMirror editors, you can define `onRender` and `onConnect` callbacks in the options hash and use it to configure the editor. `onRender` is called when the editor is initially rendered, and `onConnect` is called after each successful connection to a document. 54 | 55 | ``` 56 | {{> sharejsAce docid=document onRender=config onConnect=setMode id="editor"}} 57 | ``` 58 | 59 | All [standard Ace themes and extensions](https://github.com/ajaxorg/ace-builds/tree/master/src) are supported. Note that the helper has to return a function inside of a function: 60 | 61 | ``` 62 | Template.foo.config = function () { 63 | return function(editor) { 64 | # Set some reasonable options on the editor 65 | editor.setTheme('ace/theme/monokai') 66 | editor.getSession().setMode('ace/mode/javascript') 67 | editor.setShowPrintMargin(false) 68 | editor.getSession().setUseWrapMode(true) 69 | } 70 | }; 71 | ``` 72 | 73 | ## Server Configuration 74 | 75 | See this [example config file](settings-example.json) for the various settings that you can use. 76 | 77 | ### Persistence 78 | 79 | By default, the documents and edit operations will be persisted in Meteor's Mongo database. Mongo is the recommended usage as you don't need a separate database and user integration is supported. `"opsCollectionPerDoc": false` can be useful to set if you don't want a separate ops collection for each document. 80 | 81 | You can also use `db.type` of `none` to have all documents and operations in memory. 82 | 83 | ### Meteor User-Accounts Integration 84 | 85 | The Authorization was removed in version 0.9.0, because the current implementation did not added any security as `Meteor.userId` is not a secret token. 86 | 87 | ## Advanced 88 | 89 | You can access the [ShareJS Server API](https://github.com/share/ShareJS/wiki/Server-api) via `import { ShareJS } from 'meteor/mizzao:sharejs'`. For example, you may want to delete documents ops when the document is deleted in your app. See the demo for an example. 90 | 91 | ## Notes 92 | 93 | - When using the default mongo driver, you must not use collections called `docs` or `ops`. [These are used by ShareJS](https://github.com/share/ShareJS/blob/v0.6.2/src/server/db/mongo.coffee). 94 | - It's best to create a `Meteor.Collection` for your documents which generates good unique ids to connect to ShareJS with. Use these to render the templates above. See the [demo](demo) for examples. 95 | 96 | Please submit pull requests for better features and cooperation! 97 | 98 | ## Contributors 99 | 100 | * Andrew Mao (https://github.com/mizzao/) 101 | * Karan Batra-Daitch (https://github.com/kbdaitch) 102 | * CJ Carr (https://github.com/cortexelus) 103 | * David Sichau (https://github.com/DavidSichau) 104 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .meteor/.* 3 | packages/* 4 | node_modules/* 5 | -------------------------------------------------------------------------------- /demo/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /demo/.meteor/identifier: -------------------------------------------------------------------------------- 1 | 7uyhiiyx4wdbyf3jr -------------------------------------------------------------------------------- /demo/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # 3 | # 'meteor add' and 'meteor remove' will edit this file for you, 4 | # but you can also edit it by hand. 5 | 6 | autopublish 7 | insecure 8 | standard-app-packages 9 | stylus 10 | mizzao:sharejs 11 | mizzao:sharejs-codemirror 12 | mizzao:sharejs-ace 13 | twbs:bootstrap 14 | standard-minifiers 15 | modules 16 | ecmascript 17 | -------------------------------------------------------------------------------- /demo/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /demo/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.4.2.3 2 | -------------------------------------------------------------------------------- /demo/.meteor/versions: -------------------------------------------------------------------------------- 1 | allow-deny@1.0.5 2 | autopublish@1.0.7 3 | autoupdate@1.3.12 4 | babel-compiler@6.13.0 5 | babel-runtime@1.0.1 6 | base64@1.0.10 7 | binary-heap@1.0.10 8 | blaze@2.3.0 9 | blaze-tools@1.0.10 10 | boilerplate-generator@1.0.11 11 | caching-compiler@1.1.9 12 | caching-html-compiler@1.1.0 13 | callback-hook@1.0.10 14 | check@1.2.4 15 | ddp@1.2.5 16 | ddp-client@1.3.2 17 | ddp-common@1.2.8 18 | ddp-server@1.3.12 19 | deps@1.0.12 20 | diff-sequence@1.0.7 21 | ecmascript@0.6.1 22 | ecmascript-runtime@0.3.15 23 | ejson@1.0.13 24 | fastclick@1.0.13 25 | geojson-utils@1.0.10 26 | handlebars@1.0.7 27 | html-tools@1.0.11 28 | htmljs@1.0.11 29 | http@1.2.10 30 | id-map@1.0.9 31 | insecure@1.0.7 32 | jquery@1.11.10 33 | launch-screen@1.1.0 34 | livedata@1.0.18 35 | logging@1.1.16 36 | meteor@1.6.0 37 | meteor-platform@1.2.6 38 | minifier-css@1.2.15 39 | minifier-js@1.2.15 40 | minimongo@1.0.19 41 | mizzao:sharejs@0.9.1 42 | mizzao:sharejs-ace@1.4.1 43 | mizzao:sharejs-codemirror@5.22.2 44 | mobile-status-bar@1.0.13 45 | modules@0.7.7 46 | modules-runtime@0.7.8 47 | mongo@1.1.14 48 | mongo-id@1.0.6 49 | mongo-livedata@1.0.12 50 | npm-mongo@2.2.16_1 51 | observe-sequence@1.0.14 52 | ordered-dict@1.0.9 53 | promise@0.8.8 54 | random@1.0.10 55 | reactive-dict@1.1.8 56 | reactive-var@1.0.11 57 | reload@1.1.11 58 | retry@1.0.9 59 | routepolicy@1.0.12 60 | session@1.1.7 61 | spacebars@1.0.13 62 | spacebars-compiler@1.1.0 63 | standard-app-packages@1.0.9 64 | standard-minifier-css@1.3.2 65 | standard-minifier-js@1.2.1 66 | standard-minifiers@1.0.6 67 | stylus@2.513.8 68 | templating@1.3.0 69 | templating-compiler@1.3.0 70 | templating-runtime@1.3.0 71 | templating-tools@1.1.0 72 | tracker@1.1.1 73 | twbs:bootstrap@3.3.6 74 | ui@1.0.12 75 | underscore@1.0.10 76 | url@1.0.11 77 | webapp@1.3.12 78 | webapp-hashing@1.0.9 79 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | meteor-documents-demo 2 | ===================== 3 | 4 | Demonstration and testbed of ShareJS integration with Meteor: https://github.com/mizzao/meteor-sharejs 5 | 6 | Check it out at http://documents.meteor.com 7 | -------------------------------------------------------------------------------- /demo/client/client.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | Template.docList.helpers({ 4 | documents: function() { 5 | return Documents.find(); 6 | } 7 | }); 8 | 9 | Template.docList.events = { 10 | "click button": function() { 11 | return Documents.insert({ 12 | title: "untitled" 13 | }, function(err, id) { 14 | if (!id) { 15 | return; 16 | } 17 | return Session.set("document", id); 18 | }); 19 | } 20 | }; 21 | 22 | Template.docItem.helpers({ 23 | current: function() { 24 | return Session.equals("document", this._id); 25 | } 26 | }); 27 | 28 | Template.docItem.events = { 29 | "click a": function(e) { 30 | e.preventDefault(); 31 | return Session.set("document", this._id); 32 | } 33 | }; 34 | 35 | Session.setDefault("editorType", "ace"); 36 | 37 | Template.docTitle.helpers({ 38 | title: function() { 39 | var ref; 40 | return (ref = Documents.findOne(this + "")) != null ? ref.title : void 0; 41 | }, 42 | editorType: function(type) { 43 | return Session.equals("editorType", type); 44 | } 45 | }); 46 | 47 | Template.editor.helpers({ 48 | docid: function() { 49 | return Session.get("document"); 50 | } 51 | }); 52 | 53 | Template.editor.events = { 54 | "keydown input[name=title]": function(e) { 55 | var id; 56 | if (e.keyCode !== 13) { 57 | return; 58 | } 59 | e.preventDefault(); 60 | $(e.target).blur(); 61 | id = Session.get("document"); 62 | return Documents.update(id, { 63 | title: e.target.value 64 | }); 65 | }, 66 | "click button": function(e) { 67 | var id; 68 | e.preventDefault(); 69 | id = Session.get("document"); 70 | Session.set("document", null); 71 | return Meteor.call("deleteDocument", id); 72 | }, 73 | "change input[name=editor]": function(e) { 74 | return Session.set("editorType", e.target.value); 75 | } 76 | }; 77 | 78 | Template.editor.helpers({ 79 | textarea: function() { 80 | return Session.equals("editorType", "textarea"); 81 | }, 82 | cm: function() { 83 | return Session.equals("editorType", "cm"); 84 | }, 85 | ace: function() { 86 | return Session.equals("editorType", "ace"); 87 | }, 88 | configAce: function() { 89 | return function(ace) { 90 | ace.setTheme('ace/theme/monokai'); 91 | ace.setShowPrintMargin(false); 92 | return ace.getSession().setUseWrapMode(true); 93 | }; 94 | }, 95 | configCM: function() { 96 | return function(cm) { 97 | cm.setOption("theme", "default"); 98 | cm.setOption("lineNumbers", true); 99 | cm.setOption("lineWrapping", true); 100 | cm.setOption("smartIndent", true); 101 | return cm.setOption("indentWithTabs", true); 102 | }; 103 | } 104 | }); 105 | 106 | 107 | -------------------------------------------------------------------------------- /demo/client/client.styl: -------------------------------------------------------------------------------- 1 | @import 'nib' 2 | 3 | html, body, .full-height 4 | height: 100% 5 | 6 | body 7 | padding: 20px 50px 0px 8 | 9 | .row.upper 10 | height: 85% 11 | 12 | .row.footer 13 | height: 15% 14 | 15 | #list 16 | overflow: auto 17 | max-height: 80% 18 | 19 | .editor-container 20 | position: relative 21 | 22 | #editor, .CodeMirror 23 | box-shadow: 0px 0px 5px 5px rgba(0, 0, 0, 0.4); 24 | 25 | #editor.shareJSAce, .CodeMirror 26 | position: absolute 27 | left: 0 28 | right: 0 29 | top: 60px 30 | bottom: 0 31 | 32 | #editor.shareJSText, .CodeMirror 33 | width: 100% 34 | height: 80% 35 | -------------------------------------------------------------------------------- /demo/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | Meteor Docs! 3 | 4 | 5 | 6 | 7 | {{> forkme}} 8 | {{> main}} 9 | 10 | 11 | 16 | 17 | 36 | 37 | 47 | 48 | 53 | 54 | 68 | 69 | 88 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Created by dsichau on 11.05.16. 4 | */ 5 | 6 | import {ShareJS} from 'meteor/mizzao:sharejs'; 7 | 8 | this.Documents = new Meteor.Collection("documents"); 9 | 10 | Meteor.methods({ 11 | deleteDocument(id){ 12 | Documents.remove(id); 13 | if(Meteor.isServer) { 14 | ShareJS.model.delete(id); 15 | } 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sharejs-demo", 3 | "version": "1.0.0", 4 | "description": "meteor-documents-demo =====================", 5 | "main": "index.js", 6 | "dependencies": { 7 | "babel-runtime": "^6.20.0" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+ssh://git@github.com/mizzao/meteor-sharejs.git" 16 | }, 17 | "author": "", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/mizzao/meteor-sharejs/issues" 21 | }, 22 | "homepage": "https://github.com/mizzao/meteor-sharejs#readme" 23 | } 24 | -------------------------------------------------------------------------------- /demo/smart.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | "mizzao:sharejs": { 4 | "path": "../sharejs-base" 5 | }, 6 | "mizzao:sharejs-ace": { 7 | "path": "../sharejs-ace" 8 | }, 9 | "mizzao:sharejs-codemirror": { 10 | "path": "../sharejs-codemirror" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /demo/smart.lock: -------------------------------------------------------------------------------- 1 | { 2 | "meteor": {}, 3 | "dependencies": { 4 | "basePackages": { 5 | "mizzao:sharejs": { 6 | "path": "../sharejs-base" 7 | }, 8 | "mizzao:sharejs-ace": { 9 | "path": "../sharejs-ace" 10 | }, 11 | "mizzao:sharejs-codemirror": { 12 | "path": "../sharejs-codemirror" 13 | } 14 | }, 15 | "packages": { 16 | "mizzao:sharejs": { 17 | "path": "../sharejs-base" 18 | }, 19 | "mizzao:sharejs-ace": { 20 | "path": "../sharejs-ace" 21 | }, 22 | "mizzao:sharejs-codemirror": { 23 | "path": "../sharejs-codemirror" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /settings-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "sharejs": { 3 | "options": { 4 | "db": { 5 | "type": "mongo", 6 | "opsCollectionPerDoc": false 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sharejs-ace/.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | -------------------------------------------------------------------------------- /sharejs-ace/History.md: -------------------------------------------------------------------------------- 1 | ## v1.4.0 2 | 3 | * Ace is added as npm dependencie 4 | 5 | ## v1.3.0 6 | 7 | * Support of Meteor 1.3 8 | * Support of ace v1.2 or newer 9 | 10 | ## v1.2.0 11 | 12 | * Update to v1.2.0 of Ace. 13 | * Support Meteor 1.2 (what a coincidence!) 14 | 15 | ## v1.1.8_1 16 | 17 | * Prevent an undo from erasing the initial document contents. (#40) 18 | 19 | ## v1.1.8 20 | 21 | * Update to v1.1.8 of the Ace editor. 22 | * Remove `Loading...` text from rendered template. 23 | -------------------------------------------------------------------------------- /sharejs-ace/README.md: -------------------------------------------------------------------------------- 1 | sharejs-ace 2 | =========== 3 | 4 | Ace editor integrated with ShareJS. Please see the documentation for the `mizzao:sharejs` base package. 5 | -------------------------------------------------------------------------------- /sharejs-ace/ace.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | 3 | require('ace-builds/src-noconflict/ace'); 4 | (function() { 5 | 6 | var Range, applyToShareJS, requireImpl; 7 | 8 | requireImpl = ace.require != null ? ace.require : require; 9 | 10 | Range = requireImpl("ace/range").Range; 11 | 12 | applyToShareJS = function(editorDoc, delta, doc) { 13 | var getStartOffsetPosition, pos, text; 14 | 15 | getStartOffsetPosition = function(range) { 16 | var i, line, lines, offset, _i, _len; 17 | 18 | lines = editorDoc.getLines(0, range.start.row); 19 | offset = 0; 20 | for (i = _i = 0, _len = lines.length; _i < _len; i = ++_i) { 21 | line = lines[i]; 22 | offset += i < range.start.row ? line.length : range.start.column; 23 | } 24 | return offset + range.start.row; 25 | }; 26 | pos = getStartOffsetPosition(delta); 27 | var len = delta.lines.length; 28 | var newline = editorDoc.getNewLineCharacter(); 29 | var newlineLength = newline.length; 30 | 31 | if (delta.action === "insert" && len === 1) { 32 | doc.insert(pos, delta.lines[0]); 33 | } else if (delta.action === "insert" && len > 1) { 34 | var text = ""; 35 | for (var i = 0; i < len; i++) { 36 | text += delta.lines[i] + (i === len - 1 ? "" : newline); 37 | } 38 | doc.insert(pos, text); 39 | } else if (delta.action === "remove" && len === 1) { 40 | doc.del(pos, delta.lines[0].length); 41 | } else if (delta.action === "remove" && len > 1) { 42 | var cnt = 0; 43 | for (var i = 0; i < len; i++) { 44 | cnt += delta.lines[i].length + (i === 0 ? 0 : newlineLength); 45 | } 46 | doc.del(pos, cnt); 47 | } else { 48 | console.error("Unknown action" + delta.action); 49 | } 50 | }; 51 | 52 | window.sharejs.extendDoc('attach_ace', function(editor, keepEditorContents) { 53 | var check, deleteListener, doc, docListener, editorDoc, editorListener, insertListener, offsetToPos, refreshListener, replaceTokenizer, suppress; 54 | 55 | if (!this.provides['text']) { 56 | throw new Error('Only text documents can be attached to ace'); 57 | } 58 | doc = this; 59 | editorDoc = editor.getSession().getDocument(); 60 | editorDoc.setNewLineMode('unix'); 61 | check = function() { 62 | return window.setTimeout(function() { 63 | var editorText, otText; 64 | 65 | editorText = editorDoc.getValue(); 66 | otText = doc.getText(); 67 | if (editorText !== otText) { 68 | console.error("Text does not match!"); 69 | console.error("editor: " + editorText); 70 | return console.error("ot: " + otText); 71 | } 72 | }, 0); 73 | }; 74 | if (keepEditorContents) { 75 | doc.del(0, doc.getText().length); 76 | doc.insert(0, editorDoc.getValue()); 77 | } else { 78 | editorDoc.setValue(doc.getText()); 79 | } 80 | check(); 81 | suppress = false; 82 | editorListener = function(change) { 83 | if (suppress) { 84 | return; 85 | } 86 | applyToShareJS(editorDoc, change, doc); 87 | return check(); 88 | }; 89 | replaceTokenizer = function() { 90 | var oldGetLineTokens, oldTokenizer; 91 | 92 | oldTokenizer = editor.getSession().getMode().getTokenizer(); 93 | oldGetLineTokens = oldTokenizer.getLineTokens; 94 | return oldTokenizer.getLineTokens = function(line, state) { 95 | var cIter, docTokens, modeTokens; 96 | 97 | if ((state == null) || typeof state === "string") { 98 | cIter = doc.createIterator(0); 99 | state = { 100 | modeState: state 101 | }; 102 | } else { 103 | cIter = doc.cloneIterator(state.iter); 104 | doc.consumeIterator(cIter, 1); 105 | } 106 | modeTokens = oldGetLineTokens.apply(oldTokenizer, [line, state.modeState]); 107 | docTokens = doc.consumeIterator(cIter, line.length); 108 | if (docTokens.text !== line) { 109 | return modeTokens; 110 | } 111 | return { 112 | tokens: doc.mergeTokens(docTokens, modeTokens.tokens), 113 | state: { 114 | modeState: modeTokens.state, 115 | iter: doc.cloneIterator(cIter) 116 | } 117 | }; 118 | }; 119 | }; 120 | if (doc.getAttributes != null) { 121 | replaceTokenizer(); 122 | } 123 | editorDoc.on('change', editorListener); 124 | docListener = function(op) { 125 | suppress = true; 126 | applyToDoc(editorDoc, op); 127 | suppress = false; 128 | return check(); 129 | }; 130 | offsetToPos = function(offset) { 131 | var line, lines, row, _i, _len; 132 | 133 | lines = editorDoc.getAllLines(); 134 | row = 0; 135 | for (row = _i = 0, _len = lines.length; _i < _len; row = ++_i) { 136 | line = lines[row]; 137 | if (offset <= line.length) { 138 | break; 139 | } 140 | offset -= lines[row].length + 1; 141 | } 142 | return { 143 | row: row, 144 | column: offset 145 | }; 146 | }; 147 | doc.on('insert', insertListener = function(pos, text) { 148 | suppress = true; 149 | editorDoc.insert(offsetToPos(pos), text); 150 | suppress = false; 151 | return check(); 152 | }); 153 | doc.on('delete', deleteListener = function(pos, text) { 154 | var range; 155 | 156 | suppress = true; 157 | range = Range.fromPoints(offsetToPos(pos), offsetToPos(pos + text.length)); 158 | editorDoc.remove(range); 159 | suppress = false; 160 | return check(); 161 | }); 162 | doc.on('refresh', refreshListener = function(startoffset, length) { 163 | var range; 164 | 165 | range = Range.fromPoints(offsetToPos(startoffset), offsetToPos(startoffset + length)); 166 | return editor.getSession().bgTokenizer.start(range.start.row); 167 | }); 168 | doc.detach_ace = function() { 169 | doc.removeListener('insert', insertListener); 170 | doc.removeListener('delete', deleteListener); 171 | doc.removeListener('remoteop', docListener); 172 | doc.removeListener('refresh', refreshListener); 173 | editorDoc.removeListener('change', editorListener); 174 | return delete doc.detach_ace; 175 | }; 176 | }); 177 | 178 | }).call(this); -------------------------------------------------------------------------------- /sharejs-ace/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dsichau on 27.04.16. 3 | */ 4 | 5 | import { Template } from 'meteor/templating' 6 | import { Blaze } from 'meteor/blaze' 7 | import { ShareJSConnector } from 'meteor/mizzao:sharejs' 8 | 9 | require('ace-builds/src-noconflict/ace'); 10 | ace.require('ace/config').set('basePath', '/packages/mizzao_sharejs-ace/.npm/package/node_modules/ace-builds/src-noconflict/'); 11 | UndoManager = ace.require('ace/undomanager').UndoManager; 12 | import './ace' 13 | 14 | class ShareJSAceConnector extends ShareJSConnector { 15 | 16 | createView() { 17 | return Blaze.With(Blaze.getData(), function(){ 18 | return Template._sharejsAce; 19 | }); 20 | } 21 | rendered(element){ 22 | super.rendered(element); 23 | this.ace = ace.edit(element); 24 | this.ace.$blockScrolling = Infinity; 25 | this.ace.getSession().setValue("loading..."); 26 | if (typeof this.configCallback === "function") { 27 | this.configCallback(this.ace); 28 | } 29 | } 30 | connect() { 31 | this.ace.setReadOnly(true); 32 | super.connect(this.docIdVar.get()); 33 | } 34 | attach(doc){ 35 | super.attach(doc); 36 | doc.attach_ace(this.ace); 37 | // Reset undo stack, so that we can't undo to an empty document 38 | // XXX It seems that we should be able to use getUndoManager().reset() 39 | // here, but that doesn't seem to work: 40 | // http://japhr.blogspot.com/2012/10/ace-undomanager-and-setvalue.html 41 | this.ace.getSession().setUndoManager(new UndoManager()); 42 | this.ace.setReadOnly(false); 43 | if (typeof this.connectCallback === "function") { 44 | this.connectCallback(this.ace); 45 | } 46 | } 47 | disconnect() { 48 | const ref = this.doc; 49 | if (ref != null) { 50 | if (typeof ref.detach_ace === "function") { 51 | ref.detach_ace(); 52 | } 53 | } 54 | super.disconnect(); 55 | } 56 | destroy() { 57 | super.destroy(); 58 | // Meteor._debug "destroying textarea editor" 59 | if(this.ace) { 60 | this.ace.destroy(); 61 | } 62 | this.ace = null; 63 | } 64 | } 65 | 66 | Template.registerHelper("sharejsAce", new Template('sharejsAce',function(){ 67 | return new ShareJSAceConnector(this).create(); 68 | })); -------------------------------------------------------------------------------- /sharejs-ace/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: "mizzao:sharejs-ace", 3 | summary: "ShareJS with the Ace Editor", 4 | version: "1.4.1", 5 | git: "https://github.com/mizzao/meteor-sharejs.git" 6 | }); 7 | 8 | Npm.depends({ 9 | "ace-builds": "1.2.2" 10 | }); 11 | 12 | // Ugly-ass function stolen from http://stackoverflow.com/a/20794116/586086 13 | // TODO make this less ugly in future 14 | function getFilesFromFolder(packageName, folder){ 15 | // local imports 16 | var _ = Npm.require("underscore"); 17 | var fs = Npm.require("fs"); 18 | var path = Npm.require("path"); 19 | // helper function, walks recursively inside nested folders and return absolute filenames 20 | function walk(folder){ 21 | var filenames = []; 22 | // get relative filenames from folder 23 | var folderContent = fs.readdirSync(folder); 24 | // iterate over the folder content to handle nested folders 25 | _.each(folderContent, function(filename) { 26 | // build absolute filename 27 | var absoluteFilename = path.join(folder, filename); 28 | // get file stats 29 | var stat = fs.statSync(absoluteFilename); 30 | if(stat.isDirectory()){ 31 | // directory case => add filenames fetched from recursive call 32 | filenames = filenames.concat(walk(absoluteFilename)); 33 | } 34 | else{ 35 | // file case => simply add it 36 | filenames.push(absoluteFilename); 37 | } 38 | }); 39 | return filenames; 40 | } 41 | // save current working directory (something like "/home/user/projects/my-project") 42 | var cwd = process.cwd(); 43 | 44 | var isRunningFromApp = fs.existsSync(path.resolve("packages")); 45 | var packagePath = isRunningFromApp ? path.resolve("packages", packageName) : ""; 46 | console.log(packagePath); 47 | 48 | packagePath = path.resolve(packagePath); 49 | // chdir to our package directory 50 | process.chdir(path.join(packagePath)); 51 | // launch initial walk 52 | var result = walk(folder); 53 | // restore previous cwd 54 | process.chdir(cwd); 55 | return result; 56 | } 57 | 58 | Package.onUse(function (api) { 59 | api.versionsFrom("1.3"); 60 | 61 | api.use(['ecmascript', 'modules', 'templating']); 62 | 63 | api.use("mizzao:sharejs@0.9.0"); 64 | api.imply("mizzao:sharejs"); 65 | 66 | var _ = Npm.require("underscore"); 67 | 68 | // Add Ace files as assets that can be loaded by the client later 69 | var aceSettings = getFilesFromFolder("mizzao:sharejs-ace", ".npm/package/node_modules/ace-builds/src-noconflict"); 70 | api.addAssets(aceSettings, 'client'); 71 | 72 | api.mainModule('client.js', 'client'); 73 | api.addFiles([ 74 | 'templates.html' 75 | ], 'client'); 76 | }); 77 | -------------------------------------------------------------------------------- /sharejs-ace/templates.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /sharejs-base/.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | -------------------------------------------------------------------------------- /sharejs-base/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /sharejs-base/loadBCSocket.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dsichau on 11.05.16. 3 | */ 4 | 5 | //This is required to load BCSocket into the global scope for sharejs 6 | //http://stackoverflow.com/questions/31197220/can-i-use-an-es6-2015-module-import-to-set-a-reference-in-global-scope 7 | import {BCSocket as bc} from 'browserchannel/dist/bcsocket-uncompressed' 8 | window.BCSocket = bc; 9 | -------------------------------------------------------------------------------- /sharejs-base/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: "mizzao:sharejs", 3 | summary: "server (& client library) to allow concurrent editing of any kind of content", 4 | version: "0.10.1", 5 | git: "https://github.com/mizzao/meteor-sharejs.git" 6 | }); 7 | 8 | Npm.depends({ 9 | // Fork of 0.6.3 that doesn't require("mongodb"): 10 | // https://github.com/meteor/meteor/issues/532#issuecomment-82635979 11 | // Includes "Failed to parse" bugfix 12 | share: "https://github.com/qeek/sharejs-tmp-fork/tarball/94c059bd4da24de8e6e90fb83484dd9c7b0efd59", 13 | browserchannel: '1.2.0' 14 | }); 15 | 16 | Package.onUse(function (api) { 17 | api.versionsFrom("1.3"); 18 | 19 | api.use(['underscore', 'ecmascript', 'modules']); 20 | api.use(['handlebars', 'templating'], 'client'); 21 | api.use(['mongo-livedata', 'routepolicy', 'webapp'], 'server'); 22 | 23 | 24 | api.mainModule('sharejs-client.js', 'client'); 25 | api.mainModule('sharejs-server.js', 'server'); 26 | // Our files 27 | api.addFiles([ 28 | 'sharejs-templates.html' 29 | ], 'client'); 30 | 31 | }); 32 | 33 | Package.onTest(function (api) { 34 | api.use([ 35 | 'random', 36 | 'ecmascript', 37 | 'modules', 38 | 'tinytest', 39 | 'test-helpers' 40 | ]); 41 | 42 | api.use("mizzao:sharejs"); 43 | 44 | api.addFiles('tests/server_test.js', 'server'); 45 | }); 46 | -------------------------------------------------------------------------------- /sharejs-base/sharejs-client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dsichau on 27.04.16. 3 | */ 4 | import { Template } from 'meteor/templating' 5 | import { ReactiveVar } from 'meteor/reactive-var' 6 | import { Blaze } from 'meteor/blaze' 7 | 8 | import './loadBCSocket' 9 | import 'share/webclient/share.uncompressed' 10 | import './textarea' 11 | 12 | export class ShareJSConnector { 13 | 14 | getOptions() { 15 | return { 16 | origin: '//' + window.location.host + '/channel' 17 | }; 18 | }; 19 | 20 | constructor(parentView) { 21 | const docIdVar = new ReactiveVar(); 22 | 23 | parentView.onViewReady(function() { 24 | return this.autorun(function() { 25 | const data = Blaze.getData(); 26 | return docIdVar.set(data.docid); 27 | }); 28 | }); 29 | 30 | parentView.onViewDestroyed(()=> { 31 | this.destroy() 32 | }); 33 | 34 | this.isCreated = false; 35 | this.docIdVar = docIdVar; 36 | // Configure any callbacks if specified 37 | const params = Blaze.getData(parentView); 38 | this.configCallback = params.onRender; 39 | this.connectCallback = params.onConnect; 40 | } 41 | 42 | create() { 43 | if (this.isCreated) { 44 | throw new Error("Already created"); 45 | } 46 | const connector = this; 47 | this.isCreated = true; 48 | this.view = this.createView(); 49 | this.view.onViewReady(function() { 50 | connector.rendered(this.firstNode()); 51 | return this.autorun(function() { 52 | var docId; 53 | docId = connector.docIdVar.get(); 54 | connector.disconnect(); 55 | if (docId) { 56 | return connector.connect(docId); 57 | } 58 | }); 59 | }); 60 | return this.view; 61 | } 62 | 63 | rendered(element) { 64 | this.element = element; 65 | } 66 | 67 | connect(docId, element) { 68 | this.connectingId = docId; 69 | sharejs.open(docId, 'text', this.getOptions(), (error, doc) => { 70 | if (error) { 71 | console.log(error); 72 | return 73 | } 74 | // Don't attach if re-render happens too quickly and we' re trying to 75 | // connect to a different document now. 76 | if(this.connectingId !== doc.name) { 77 | doc.close(); 78 | } 79 | else { 80 | this.attach(doc); 81 | } 82 | }); 83 | } 84 | 85 | attach(doc) { 86 | this.doc = doc; 87 | } 88 | 89 | disconnect() { 90 | if(this.doc) { 91 | this.doc.close(); 92 | this.doc = null; 93 | } 94 | } 95 | 96 | destroy() { 97 | if(this.isDestroyed) { 98 | throw new Error("Already destroyed") 99 | } 100 | this.disconnect(); 101 | this.view = null; 102 | this.isDestroyed = true; 103 | } 104 | } 105 | 106 | class ShareJSTextConnector extends ShareJSConnector { 107 | 108 | createView() { 109 | return Blaze.With(Blaze.getData(),function() { 110 | return Template._sharejsText; 111 | }) 112 | } 113 | 114 | rendered(element) { 115 | super.rendered(element); 116 | this.textarea = element; 117 | if (typeof this.configCallback === "function") { 118 | this.configCallback(this.textarea); 119 | } 120 | } 121 | 122 | connect() { 123 | this.textarea.disabled = true; 124 | super.connect(this.docIdVar.get()); 125 | } 126 | 127 | attach(doc) { 128 | super.attach(doc); 129 | doc.attach_textarea(this.textarea); 130 | this.textarea.disabled = false; 131 | if (typeof this.connectCallback === "function") { 132 | this.connectCallback(this.textarea); 133 | } 134 | } 135 | 136 | disconnect() { 137 | const ref = this.textarea; 138 | if (ref != null) { 139 | if (typeof ref.detach_share === "function") { 140 | ref.detach_share(); 141 | } 142 | } 143 | super.disconnect(); 144 | } 145 | 146 | destroy() { 147 | super.destroy(); 148 | // Meteor._debug "destroying textarea editor" 149 | this.textarea = null 150 | } 151 | } 152 | 153 | Template.registerHelper("sharejsText", new Template('sharejsText', function() { 154 | return new ShareJSTextConnector(this).create() 155 | })); -------------------------------------------------------------------------------- /sharejs-base/sharejs-server.js: -------------------------------------------------------------------------------- 1 | // Creates a (persistent) ShareJS server 2 | // Based on https://github.com/share/ShareJS/wiki/Getting-started 3 | 4 | import { Meteor } from 'meteor/meteor'; 5 | 6 | const Future = Npm.require('fibers/future'); 7 | 8 | export const ShareJS = ShareJS || {}; 9 | // See docs for options. Uses mongo by default to enable persistence. 10 | 11 | // Using special options from https://github.com/share/ShareJS/blob/master/src/server/index.coffee 12 | const options = _.extend({ 13 | staticPath: null, 14 | db: { 15 | type: 'none', // Default option is none as it throws errors in meteor 1.4 due to issues in upstream sharejs 16 | opsCollectionPerDoc: false // A doc/op indexed collection keeps the namespace cleaner in a Meteor app. 17 | } 18 | }, (ref = Meteor.settings.sharejs) != null ? ref.options : void 0); 19 | 20 | switch (options.db.type) { 21 | case 'mongo': 22 | 23 | /* 24 | ShareJS 0.6.3 mongo driver: 25 | https://github.com/share/ShareJS/blob/v0.6.3/src/server/db/mongo.coffee 26 | It will create its own indices on the 'ops' collection. 27 | */ 28 | options.db.client = MongoInternals.defaultRemoteCollectionDriver().mongo.db; 29 | /* Disable the open command due to the bug introduced in ShareJS 0.6.3 30 | where an open database connection is not accepted 31 | https://github.com/share/ShareJS/commit/f98a4adeca396df3ec6b1d838b965ff158f452a3 32 | 33 | Meteor has already opened the database connection, so this should work, 34 | but watch monkey-patch carefully with changes in how 35 | https://github.com/meteor/meteor/blob/devel/packages/mongo-livedata/mongo_driver.js 36 | uses the API at 37 | http://mongodb.github.io/node-mongodb-native/api-generated/mongoclient.html 38 | */ 39 | options.db.client.open = function() {}; 40 | break; 41 | default: 42 | Meteor._debug("ShareJS: using unsupported db type " + options.db.type + ", falling back to in-memory."); 43 | } 44 | //Declare the path that ShareJS uses to Meteor 45 | RoutePolicy.declare('/channel/', 'network'); 46 | // Attach the sharejs REST and bcsocket interfaces as middleware to the meteor connect server 47 | Npm.require('share').server.attach(WebApp.connectHandlers, options); 48 | 49 | 50 | /* 51 | ShareJS attaches the server API to a weird place. Oh well... 52 | https://github.com/share/ShareJS/blob/v0.6.2/src/server/index.coffee 53 | */ 54 | 55 | ShareJS.model = WebApp.connectHandlers.model; 56 | // A convenience function for creating a document on the server. 57 | ShareJS.initializeDoc = function(docName, content) { 58 | return ShareJS.model.create(docName, 'text', {}, function(err) { 59 | var opData; 60 | if (err) { 61 | console.log(err); 62 | return; 63 | } 64 | //One op; insert all the content at position 0 65 | //https://github.com/share/ShareJS/wiki/Server-api 66 | opData = { 67 | op: [{ i: content, p: 0 }], 68 | v: 0, 69 | meta: {} 70 | }; 71 | return ShareJS.model.applyOp(docName, opData, function(err, res) { 72 | if (err) { 73 | return console.log(err); 74 | } 75 | }); 76 | }); 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /sharejs-base/sharejs-templates.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /sharejs-base/tests/server_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dsichau on 17.05.16. 3 | */ 4 | 5 | import {ShareJS} from 'meteor/mizzao:sharejs'; 6 | var Docs, Ops, sleep; 7 | 8 | Docs = new Meteor.Collection("docs"); 9 | 10 | Ops = new Meteor.Collection("ops"); 11 | 12 | Docs.remove({}); 13 | 14 | Ops.remove({}); 15 | 16 | sleep = Meteor.wrapAsync(function(time, cb) { 17 | return Meteor.setTimeout((function() { 18 | return cb(void 0); 19 | }), time); 20 | }); 21 | 22 | Tinytest.add("server - model initialized properly", function(test) { 23 | return test.isTrue(ShareJS.model); 24 | }); 25 | 26 | Tinytest.addAsync("server - initialize document with data", function(test, next) { 27 | var doc, docId; 28 | docId = Random.id(); 29 | ShareJS.initializeDoc(docId, "foo"); 30 | sleep(100); 31 | doc = Docs.findOne(docId); 32 | test.isTrue(doc); 33 | test.equal(doc != null ? doc._id : void 0, docId); 34 | test.isTrue(Ops.find().count() > 0); 35 | return ShareJS.model.getSnapshot(docId, function(err, res) { 36 | if (err != null) { 37 | test.fail(); 38 | } 39 | test.equal(res.snapshot, "foo"); 40 | return next(); 41 | }); 42 | }); -------------------------------------------------------------------------------- /sharejs-base/textarea.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | (function() { 3 | var applyChange; 4 | 5 | applyChange = function(doc, oldval, newval) { 6 | var commonEnd, commonStart; 7 | 8 | if (oldval === newval) { 9 | return; 10 | } 11 | commonStart = 0; 12 | while (oldval.charAt(commonStart) === newval.charAt(commonStart)) { 13 | commonStart++; 14 | } 15 | commonEnd = 0; 16 | while (oldval.charAt(oldval.length - 1 - commonEnd) === newval.charAt(newval.length - 1 - commonEnd) && commonEnd + commonStart < oldval.length && commonEnd + commonStart < newval.length) { 17 | commonEnd++; 18 | } 19 | if (oldval.length !== commonStart + commonEnd) { 20 | doc.del(commonStart, oldval.length - commonStart - commonEnd); 21 | } 22 | if (newval.length !== commonStart + commonEnd) { 23 | return doc.insert(commonStart, newval.slice(commonStart, newval.length - commonEnd)); 24 | } 25 | }; 26 | 27 | sharejs.extendDoc('attach_textarea', function(elem) { 28 | var delete_listener, doc, event, genOp, insert_listener, prevvalue, replaceText, _i, _len, _ref, 29 | _this = this; 30 | 31 | doc = this; 32 | elem.value = this.getText(); 33 | prevvalue = elem.value; 34 | replaceText = function(newText, transformCursor) { 35 | var newSelection, scrollTop; 36 | 37 | newSelection = [transformCursor(elem.selectionStart), transformCursor(elem.selectionEnd)]; 38 | scrollTop = elem.scrollTop; 39 | elem.value = newText; 40 | if (elem.scrollTop !== scrollTop) { 41 | elem.scrollTop = scrollTop; 42 | } 43 | if (window.document.activeElement === elem) { 44 | return elem.selectionStart = newSelection[0], elem.selectionEnd = newSelection[1], newSelection; 45 | } 46 | }; 47 | this.on('insert', insert_listener = function(pos, text) { 48 | var transformCursor; 49 | 50 | transformCursor = function(cursor) { 51 | if (pos < cursor) { 52 | return cursor + text.length; 53 | } else { 54 | return cursor; 55 | } 56 | }; 57 | prevvalue = elem.value.replace(/\r\n/g, '\n'); 58 | return replaceText(prevvalue.slice(0, pos) + text + prevvalue.slice(pos), transformCursor); 59 | }); 60 | this.on('delete', delete_listener = function(pos, text) { 61 | var transformCursor; 62 | 63 | transformCursor = function(cursor) { 64 | if (pos < cursor) { 65 | return cursor - Math.min(text.length, cursor - pos); 66 | } else { 67 | return cursor; 68 | } 69 | }; 70 | prevvalue = elem.value.replace(/\r\n/g, '\n'); 71 | return replaceText(prevvalue.slice(0, pos) + prevvalue.slice(pos + text.length), transformCursor); 72 | }); 73 | genOp = function(event) { 74 | var onNextTick; 75 | 76 | onNextTick = function(fn) { 77 | return setTimeout(fn, 0); 78 | }; 79 | return onNextTick(function() { 80 | if (elem.value !== prevvalue) { 81 | prevvalue = elem.value; 82 | return applyChange(doc, doc.getText(), elem.value.replace(/\r\n/g, '\n')); 83 | } 84 | }); 85 | }; 86 | _ref = ['textInput', 'keydown', 'keyup', 'select', 'cut', 'paste']; 87 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 88 | event = _ref[_i]; 89 | if (elem.addEventListener) { 90 | elem.addEventListener(event, genOp, false); 91 | } else { 92 | elem.attachEvent('on' + event, genOp); 93 | } 94 | } 95 | return elem.detach_share = function() { 96 | var _j, _len1, _ref1, _results; 97 | 98 | _this.removeListener('insert', insert_listener); 99 | _this.removeListener('delete', delete_listener); 100 | _ref1 = ['textInput', 'keydown', 'keyup', 'select', 'cut', 'paste']; 101 | _results = []; 102 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 103 | event = _ref1[_j]; 104 | if (elem.removeEventListener) { 105 | _results.push(elem.removeEventListener(event, genOp, false)); 106 | } else { 107 | _results.push(elem.detachEvent('on' + event, genOp)); 108 | } 109 | } 110 | return _results; 111 | }; 112 | }); 113 | 114 | }).call(this); 115 | -------------------------------------------------------------------------------- /sharejs-codemirror/.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | -------------------------------------------------------------------------------- /sharejs-codemirror/History.md: -------------------------------------------------------------------------------- 1 | ## v5.14.2 2 | 3 | * use Codemirror as npm dependency 4 | 5 | ## v4.12.0 6 | 7 | * Update to v4.12.0 of CodeMirror editor. 8 | * Remove `Loading...` text from rendered template. 9 | -------------------------------------------------------------------------------- /sharejs-codemirror/README.md: -------------------------------------------------------------------------------- 1 | sharejs-codemirror 2 | ================== 3 | 4 | CodeMirror editor integrated with ShareJS. Please see the documentation for the `mizzao:sharejs` base package. 5 | -------------------------------------------------------------------------------- /sharejs-codemirror/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dsichau on 29.04.16. 3 | */ 4 | import { Template } from 'meteor/templating' 5 | import { Blaze } from 'meteor/blaze' 6 | import { ShareJSConnector } from 'meteor/mizzao:sharejs' 7 | 8 | import CodeMirror from 'codemirror'; 9 | import 'codemirror/addon/fold/foldcode'; 10 | import 'codemirror/addon/fold/foldgutter'; 11 | import 'codemirror/addon/fold/indent-fold'; 12 | import 'codemirror/addon/hint/show-hint'; 13 | import 'codemirror/addon/display/placeholder'; 14 | 15 | import 'codemirror/lib/codemirror.css'; 16 | import 'codemirror/theme/monokai.css'; 17 | import 'codemirror/addon/fold/foldgutter.css'; 18 | import 'codemirror/addon/hint/show-hint.css'; 19 | 20 | import './cm' 21 | 22 | class ShareJSCMConnector extends ShareJSConnector { 23 | createView() { 24 | return Blaze.With(Blaze.getData(), function(){ 25 | return Template._sharejsCM 26 | }); 27 | } 28 | rendered(element){ 29 | super.rendered(element); 30 | this.cm = new CodeMirror(element, {readOnly: true, value: "loading..."}); 31 | if (typeof this.configCallback === "function") { 32 | this.configCallback(this.cm); 33 | } 34 | } 35 | connect() { 36 | super.connect(this.docIdVar.get()); 37 | } 38 | attach(doc){ 39 | super.attach(doc); 40 | doc.attach_cm(this.cm); 41 | this.cm.setOption("readOnly", false); 42 | if (typeof this.connectCallback === "function") { 43 | this.connectCallback(this.cm); 44 | } 45 | } 46 | disconnect() { 47 | const ref = this.doc; 48 | if (ref != null) { 49 | if (typeof ref.detach_cm === "function") { 50 | ref.detach_cm(); 51 | } 52 | } 53 | super.disconnect(); 54 | } 55 | destroy() { 56 | super.destroy(); 57 | this.cm = null; 58 | } 59 | } 60 | Template.registerHelper("sharejsCM", new Template('sharejsCM',function(){ 61 | return new ShareJSCMConnector(this).create(); 62 | })); -------------------------------------------------------------------------------- /sharejs-codemirror/cm.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | (function() { 3 | var applyToShareJS; 4 | 5 | applyToShareJS = function(editorDoc, delta, doc) { 6 | var delLen, i, rm, startPos, _i, _len, _ref; 7 | 8 | startPos = 0; 9 | i = 0; 10 | while (i < delta.from.line) { 11 | startPos += editorDoc.lineInfo(i).text.length + 1; 12 | i++; 13 | } 14 | startPos += delta.from.ch; 15 | if (delta.to.line === delta.from.line && delta.to.ch === delta.from.ch) { 16 | doc.insert(startPos, delta.text.join('\n')); 17 | } else { 18 | delLen = 0; 19 | _ref = delta.removed; 20 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 21 | rm = _ref[_i]; 22 | delLen += rm.length; 23 | } 24 | delLen += delta.removed.length - 1; 25 | doc.del(startPos, delLen); 26 | if (delta.text) { 27 | doc.insert(startPos, delta.text.join('\n')); 28 | } 29 | } 30 | if (delta.next) { 31 | return applyToShareJS(editorDoc, delta.next, doc); 32 | } 33 | }; 34 | 35 | window.sharejs.extendDoc('attach_cm', function(editor, keepEditorContents) { 36 | var check, editorListener, sharedoc, suppress; 37 | 38 | if (!this.provides.text) { 39 | throw new Error('Only text documents can be attached to CodeMirror 2 or 3'); 40 | } 41 | sharedoc = this; 42 | check = function() { 43 | return window.setTimeout(function() { 44 | var editorText, otText; 45 | 46 | editorText = editor.getValue('\n'); 47 | otText = sharedoc.getText(); 48 | if (editorText !== otText) { 49 | console.error("Text does not match!"); 50 | console.error("editor: " + editorText); 51 | console.error("ot: " + otText); 52 | return editor.setValue(sharedoc.getText()); 53 | } 54 | }, 0); 55 | }; 56 | if (keepEditorContents) { 57 | this.del(0, sharedoc.getText('\n').length); 58 | this.insert(0, editor.getValue()); 59 | } else { 60 | editor.setValue(sharedoc.getText()); 61 | } 62 | check(); 63 | suppress = false; 64 | editorListener = function(ed, change) { 65 | if (suppress) { 66 | return; 67 | } 68 | applyToShareJS(editor, change, sharedoc); 69 | return check(); 70 | }; 71 | editor.on('change', editorListener); 72 | this.on('insert', function(pos, text) { 73 | suppress = true; 74 | editor.replaceRange(text, editor.posFromIndex(pos)); 75 | suppress = false; 76 | return check(); 77 | }); 78 | this.on('delete', function(pos, text) { 79 | var from, to; 80 | 81 | suppress = true; 82 | from = editor.posFromIndex(pos); 83 | to = editor.posFromIndex(pos + text.length); 84 | editor.replaceRange('', from, to); 85 | suppress = false; 86 | return check(); 87 | }); 88 | this.detach_cm = function() { 89 | editor.off('change', editorListener); 90 | return delete this.detach_cm; 91 | }; 92 | }); 93 | 94 | }).call(this); 95 | -------------------------------------------------------------------------------- /sharejs-codemirror/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: "mizzao:sharejs-codemirror", 3 | summary: "ShareJS with the CodeMirror Editor", 4 | version: "5.22.0", 5 | git: "https://github.com/mizzao/meteor-sharejs.git" 6 | }); 7 | 8 | Npm.depends({ 9 | codemirror: "5.22.0" 10 | }); 11 | 12 | 13 | // Ugly-ass function stolen from http://stackoverflow.com/a/20794116/586086 14 | // TODO make this less ugly in future 15 | function getFilesFromFolder(packageName, folder){ 16 | // local imports 17 | var _ = Npm.require("underscore"); 18 | var fs = Npm.require("fs"); 19 | var path = Npm.require("path"); 20 | // helper function, walks recursively inside nested folders and return absolute filenames 21 | function walk(folder){ 22 | var filenames = []; 23 | // get relative filenames from folder 24 | var folderContent = fs.readdirSync(folder); 25 | // iterate over the folder content to handle nested folders 26 | _.each(folderContent, function(filename) { 27 | // build absolute filename 28 | var absoluteFilename = path.join(folder, filename); 29 | // get file stats 30 | var stat = fs.statSync(absoluteFilename); 31 | if(stat.isDirectory()){ 32 | // directory case => add filenames fetched from recursive call 33 | filenames = filenames.concat(walk(absoluteFilename)); 34 | } 35 | else{ 36 | // file case => simply add it 37 | filenames.push(absoluteFilename); 38 | } 39 | }); 40 | return filenames; 41 | } 42 | // save current working directory (something like "/home/user/projects/my-project") 43 | var cwd = process.cwd(); 44 | 45 | var isRunningFromApp = fs.existsSync(path.resolve("packages")); 46 | var packagePath = isRunningFromApp ? path.resolve("packages", packageName) : ""; 47 | console.log(packagePath); 48 | 49 | packagePath = path.resolve(packagePath); 50 | // chdir to our package directory 51 | process.chdir(path.join(packagePath)); 52 | // launch initial walk 53 | var result = walk(folder); 54 | // restore previous cwd 55 | process.chdir(cwd); 56 | return result; 57 | } 58 | 59 | Package.onUse(function (api) { 60 | api.versionsFrom("1.3.2"); 61 | 62 | api.use(['ecmascript', 'modules', 'templating']); 63 | 64 | api.use("mizzao:sharejs@0.9.0"); 65 | api.imply("mizzao:sharejs"); 66 | 67 | // Add Ace files as assets that can be loaded by the client later 68 | var codemirrorData = getFilesFromFolder("mizzao:sharejs-codemirror", ".npm/package/node_modules/codemirror"); 69 | api.addAssets(codemirrorData, 'client'); 70 | 71 | api.mainModule('client.js', 'client'); 72 | api.addFiles([ 73 | 'templates.html' 74 | ], 'client'); 75 | }); 76 | -------------------------------------------------------------------------------- /sharejs-codemirror/templates.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | --------------------------------------------------------------------------------