diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..5f081f27 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Developer Guidelines + +Please talk to people on the mailing list before you change this page + +Mailing list: https://groups.google.com/forum/?fromgroups#!forum/etherpad-lite-dev + +IRC channels: [#etherpad](irc://freenode/#etherpad) ([webchat](webchat.freenode.net?channels=etherpad)), [#etherpad-lite-dev](irc://freenode/#etherpad-lite-dev) ([webchat](webchat.freenode.net?channels=etherpad-lite-dev)) + +**Our goal is to iterate in small steps. Release often, release early. Evolution instead of a revolution** + +## General goals of Etherpad Lite +* easy to install for admins +* easy to use for people +* using less resources on server side +* easy to embed for admins +* also runable as etherpad lite only +* keep it maintainable, we don't wanna end ob as the monster Etherpad was +* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core + +## How to code: +* **Please write comments**. I don't mean you have to comment every line and every loop. I just mean, if you do anything thats a bit complex or a bit weird, please leave a comment. It's easy to do that if you do while you're writing the code. Keep in mind that you will probably leave the project at some point and that other people will read your code. Undocumented huge amounts of code are worthless +* Never ever use tabs +* Indentation: JS/CSS: 2 spaces; HTML: 4 spaces +* Don't overengineer. Don't try to solve any possible problem in one step. Try to solve problems as easy as possible and improve the solution over time +* Do generalize sooner or later - if an old solution hacked together according to the above point, poses more problems than it solves today, reengineer it, with the lessons learned taken into account. +* Keep it compatible to API-Clients/older DBs/configurations. Don't make incompatible changes the protocol/database format without good reasons + +## How to work with git +* Make a new branch for every feature you're working on. Don't work in your master branch. This ensures that you can work you can do lot of small pull requests instead of one big one with complete different features +* Don't use the online edit function of github. This only creates ugly and not working commits +* Test before you push. Sounds easy, it isn't +* Try to make clean commits that are easy readable +* Don't check in stuff that gets generated during build or runtime (like jquery, minified files, dbs etc...) +* Make pull requests from your feature branch to our develop branch once your feature is ready +* Make small pull requests that are easy to review but make sure they do add value by themselves / individually + +## Branching model in Etherpad Lite +see git flow http://nvie.com/posts/a-successful-git-branching-model/ + +* master, the stable. This is the branch everyone should use for production stuff +* develop, everything that is READY to go into master at some point in time. This stuff is tested and ready to go out +* release branches, stuff that should go into master very soon, only bugfixes go into these (see http://nvie.com/posts/a-successful-git-branching-model/ for why) +* you can set tags in the master branch, there is no real need for release branches imho +* The latest tag is not what is shown in github by default. Doing a clone of master should give you latest stable, not what is gonna be latest stable in a week, also, we should not be blocking new features to develop, just because we feel that we should be releasing it to master soon. This is the situation that release branches solve/handle. +* hotfix branches, fixes for bugs in master +* feature branches (in your own repos), these are the branches where you develop your features in. If its ready to go out, it will be merged into develop + +Over the time we pull features from feature branches into the develop branch. Every month we pull from develop into master. Bugs in master get fixed in hotfix branches. These branches will get merged into master AND develop. There should never be commits in master that aren't in develop + +## Documentation +The docs are in the `doc/` folder in the git repository, so people can easily find the suitable docs for the current git revision. + +Documentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request. + +You can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet. \ No newline at end of file diff --git a/doc/api/api.md b/doc/api/api.md index 830e5f4c..b96fa0c8 100644 --- a/doc/api/api.md +++ b/doc/api/api.md @@ -1,6 +1,6 @@ -# API @include embed_parameters @include http_api +@include hooks @include hooks_client-side @include hooks_server-side @include editorInfo diff --git a/doc/api/editorInfo.md b/doc/api/editorInfo.md index e4322e9e..05656bce 100644 --- a/doc/api/editorInfo.md +++ b/doc/api/editorInfo.md @@ -45,3 +45,26 @@ Returns the `rep` object. ## editorInfo.ace_doInsertUnorderedList(?) ## editorInfo.ace_doInsertOrderedList(?) ## editorInfo.ace_performDocumentApplyAttributesToRange() + +## editorInfo.ace_getAuthorInfos() +Returns an info object about the author. Object key = author_id and info includes author's bg color value. +Use to define your own authorship. +## editorInfo.ace_performDocumentReplaceRange(start, end, newText) +This function replaces a range (from [x1,y1] to [x2,y2]) with `newText`. +## editorInfo.ace_performDocumentReplaceCharRange(startChar, endChar, newText) +This function replaces a range (from y1 to y2) with `newText`. +## editorInfo.ace_renumberList(lineNum) +If you delete a line, calling this method will fix the line numbering. +## editorInfo.ace_doReturnKey() +Forces a return key at the current carret position. +## editorInfo.ace_isBlockElement(element) +Returns true if your passed elment is registered as a block element +## editorInfo.ace_getLineListType(lineNum) +Returns the line's html list type. +## editorInfo.ace_caretLine() +Returns X position of the caret. +## editorInfo.ace_caretColumn() +Returns Y position of the caret. +## editorInfo.ace_caretDocChar() +Returns the Y offset starting from [x=0,y=0] +## editorInfo.ace_isWordChar(?) diff --git a/doc/api/hooks.md b/doc/api/hooks.md new file mode 100644 index 00000000..c252aa84 --- /dev/null +++ b/doc/api/hooks.md @@ -0,0 +1,11 @@ +# Hooks +All hooks are called with two arguments: + +1. name - the name of the hook being called +2. context - an object with some relevant information about the context of the call + +## Return values +A hook should always return a list or undefined. Returning undefined is equivalent to returning an empty list. +All the returned lists are appended to each other, so if the return values where `[1, 2]`, `undefined`, `[3, 4,]`, `undefined` and `[5]`, the value returned by callHook would be `[1, 2, 3, 4, 5]`. + +This is, because it should never matter if you have one plugin or several plugins doing some work - a single plugin should be able to make callHook return the same value a set of plugins are able to return collectively. So, any plugin can return a list of values, of any length, not just one value. \ No newline at end of file diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index 50ec3f98..55d1da00 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -1,11 +1,6 @@ # Client-side hooks Most of these hooks are called during or in order to set up the formatting process. -All hooks registered to these events are called with two arguments: - -1. name - the name of the hook being called -2. context - an object with some relevant information about the context of the call - ## documentReady Called from: src/templates/pad.html @@ -179,3 +174,71 @@ Things in context: This hook gets called every time the client receives a message of type `name`. This can most notably be used with the new HTTP API call, "sendClientsMessage", which sends a custom message type to all clients connected to a pad. You can also use this to handle existing types. `collab_client.js` has a pretty extensive list of message types, if you want to take a look. + +##aceStartLineAndCharForPoint-aceEndLineAndCharForPoint +Called from: src/static/js/ace2_inner.js + +Things in context: + +1. callstack - a bunch of information about the current action +2. editorInfo - information about the user who is making the change +3. rep - information about where the change is being made +4. root - the span element of the current line +5. point - the starting/ending element where the cursor highlights +6. documentAttributeManager - information about attributes in the document + +This hook is provided to allow a plugin to turn DOM node selection into [line,char] selection. +The return value should be an array of [line,char] + +##aceKeyEvent +Called from: src/static/js/ace2_inner.js + +Things in context: + +1. callstack - a bunch of information about the current action +2. editorInfo - information about the user who is making the change +3. rep - information about where the change is being made +4. documentAttributeManager - information about attributes in the document +5. evt - the fired event + +This hook is provided to allow a plugin to handle key events. +The return value should be true if you have handled the event. + +##collectContentLineText +Called from: src/static/js/contentcollector.js + +Things in context: + +1. cc - the contentcollector object +2. state - the current state of the change being made +3. tname - the tag name of this node currently being processed +4. text - the text for that line + +This hook allows you to validate/manipulate the text before it's sent to the server side. +The return value should be the validated/manipulated text. + +##collectContentLineBreak +Called from: src/static/js/contentcollector.js + +Things in context: + +1. cc - the contentcollector object +2. state - the current state of the change being made +3. tname - the tag name of this node currently being processed + +This hook is provided to allow whether the br tag should induce a new magic domline or not. +The return value should be either true(break the line) or false. + +##disableAuthorColorsForThisLine +Called from: src/static/js/linestylefilter.js + +Things in context: + +1. linestylefilter - the JavaScript object that's currently processing the ace attributes +2. text - the line text +3. class - line class + +This hook is provided to allow whether a given line should be deliniated with multiple authors. +Multiple authors in one line cause the creation of magic span lines. This might not suit you and +now you can disable it and handle your own deliniation. +The return value should be either true(disable) or false. diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index c60bbe73..010bc8a3 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -1,11 +1,6 @@ # Server-side hooks These hooks are called on server-side. -All hooks registered to these events are called with two arguments: - -1. name - the name of the hook being called -2. context - an object with some relevant information about the context of the call - ## loadSettings Called from: src/node/server.js @@ -143,3 +138,16 @@ function handleMessage ( hook, context, callback ) { } }; ``` + + +## getLineHTMLForExport +Called from: src/node/utils/ExportHtml.js + +Things in context: + +1. apool - pool object +2. attribLine - line attributes +3. text - line text + +This hook will allow a plug-in developer to re-write each line when exporting to HTML. + diff --git a/doc/api/http_api.md b/doc/api/http_api.md index 058a2ba6..3afab498 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -135,6 +135,10 @@ Pads can belong to a group. The padID of grouppads is starting with a groupID li * `{code: 1, message:"pad does already exist", data: null}` * `{code: 1, message:"groupID does not exist", data: null}` +* **listAllGroups()** lists all existing groups

*Example returns:* + * `{code: 0, message:"ok", data: {groupIDs: ["g.mKjkmnAbSMtCt8eL", "g.3ADWx6sbGuAiUmCy"]}}` + * `{code: 0, message:"ok", data: {groupIDs: []}}` + ### Author These authors are bound to the attributes the users choose (color and name). diff --git a/doc/database.md b/doc/database.md index 2e06e206..de3e9f54 100644 --- a/doc/database.md +++ b/doc/database.md @@ -2,6 +2,9 @@ ## Keys and their values +### groups +A list of all existing groups (a JSON object with groupIDs as keys and `1` as values). + ### pad:$PADID Saves all informations about pads diff --git a/src/node/db/API.js b/src/node/db/API.js index c5caae0b..4979e8c6 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -35,6 +35,7 @@ var cleanText = require("./Pad").cleanText; /**GROUP FUNCTIONS*****/ /**********************/ +exports.listAllGroups = groupManager.listAllGroups; exports.createGroup = groupManager.createGroup; exports.createGroupIfNotExistsFor = groupManager.createGroupIfNotExistsFor; exports.deleteGroup = groupManager.deleteGroup; diff --git a/src/node/db/GroupManager.js b/src/node/db/GroupManager.js index bd19507f..81b0cb9e 100644 --- a/src/node/db/GroupManager.js +++ b/src/node/db/GroupManager.js @@ -26,6 +26,24 @@ var db = require("./DB").db; var async = require("async"); var padManager = require("./PadManager"); var sessionManager = require("./SessionManager"); + +exports.listAllGroups = function(callback) { + db.get("groups", function (err, groups) { + if(ERR(err, callback)) return; + + // there are no groups + if(groups == null) { + callback(null, {groupIDs: []}); + return; + } + + var groupIDs = []; + for ( var groupID in groups ) { + groupIDs.push(groupID); + } + callback(null, {groupIDs: groupIDs}); + }); +} exports.deleteGroup = function(groupID, callback) { @@ -105,6 +123,39 @@ exports.deleteGroup = function(groupID, callback) db.remove("group2sessions:" + groupID); db.remove("group:" + groupID); callback(); + }, + //unlist the group + function(callback) + { + exports.listAllGroups(function(err, groups) { + if(ERR(err, callback)) return; + groups = groups? groups.groupIDs : []; + + // it's not listed + if(groups.indexOf(groupID) == -1) { + callback(); + return; + } + + groups.splice(groups.indexOf(groupID), 1); + + // store empty groupe list + if(groups.length == 0) { + db.set("groups", {}); + callback(); + return; + } + + // regenerate group list + var newGroups = {}; + async.forEach(groups, function(group, cb) { + newGroups[group] = 1; + cb(); + },function() { + db.set("groups", newGroups); + callback(); + }); + }); } ], function(err) { @@ -130,7 +181,24 @@ exports.createGroup = function(callback) //create the group db.set("group:" + groupID, {pads: {}}); - callback(null, {groupID: groupID}); + + //list the group + exports.listAllGroups(function(err, groups) { + if(ERR(err, callback)) return; + groups = groups? groups.groupIDs : []; + + groups.push(groupID); + + // regenerate group list + var newGroups = {}; + async.forEach(groups, function(group, cb) { + newGroups[group] = 1; + cb(); + },function() { + db.set("groups", newGroups); + callback(null, {groupID: groupID}); + }); + }); } exports.createGroupIfNotExistsFor = function(groupMapper, callback) diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index 1894ee59..59e27b55 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -123,7 +123,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) } var sessionIDs = sessionCookie.split(','); - async.foreach(sessionIDs, function(sessionID) { + async.forEach(sessionIDs, function(sessionID, callback) { sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) { //skip session if it doesn't exist if(err && err.message == "sessionID does not exist") return; @@ -141,8 +141,10 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) // There is a valid session validSession = true; sessionAuthor = sessionInfo.authorID; + + callback(); }); - }, callback) + }, callback); }, //get author for token function(callback) diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index 9bffa2bc..f99762ce 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -40,6 +40,36 @@ catch(e) //a list of all functions var version = { "1": + { "createGroup" : [] + , "createGroupIfNotExistsFor" : ["groupMapper"] + , "deleteGroup" : ["groupID"] + , "listPads" : ["groupID"] + , "createPad" : ["padID", "text"] + , "createGroupPad" : ["groupID", "padName", "text"] + , "createAuthor" : ["name"] + , "createAuthorIfNotExistsFor": ["authorMapper" , "name"] + , "listPadsOfAuthor" : ["authorID"] + , "createSession" : ["groupID", "authorID", "validUntil"] + , "deleteSession" : ["sessionID"] + , "getSessionInfo" : ["sessionID"] + , "listSessionsOfGroup" : ["groupID"] + , "listSessionsOfAuthor" : ["authorID"] + , "getText" : ["padID", "rev"] + , "setText" : ["padID", "text"] + , "getHTML" : ["padID", "rev"] + , "setHTML" : ["padID", "html"] + , "getRevisionsCount" : ["padID"] + , "getLastEdited" : ["padID"] + , "deletePad" : ["padID"] + , "getReadOnlyID" : ["padID"] + , "setPublicStatus" : ["padID", "publicStatus"] + , "getPublicStatus" : ["padID"] + , "setPassword" : ["padID", "password"] + , "isPasswordProtected" : ["padID"] + , "listAuthorsOfPad" : ["padID"] + , "padUsersCount" : ["padID"] + } +, "1.1": { "createGroup" : [] , "createGroupIfNotExistsFor" : ["groupMapper"] , "deleteGroup" : ["groupID"] @@ -71,6 +101,7 @@ var version = , "getAuthorName" : ["authorID"] , "padUsers" : ["padID"] , "sendClientsMessage" : ["padID", "msg"] + , "listAllGroups" : [] } }; diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 913433b0..10b259ae 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -436,15 +436,22 @@ function handleUserInfoUpdate(client, message) } /** - * Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations - * This Method is nearly 90% copied out of the Etherpad Source Code. So I can't tell you what happens here exactly - * Look at https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges() + * Handles a USER_CHANGES message, where the client submits its local + * edits as a changeset. + * + * This handler's job is to update the incoming changeset so that it applies + * to the latest revision, then add it to the pad, broadcast the changes + * to all other clients, and send a confirmation to the submitting client. + * + * This function is based on a similar one in the original Etherpad. + * See https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges() + * * @param client the client that send this message * @param message the message from the client */ function handleUserChanges(client, message) { - //check if all ok + // Make sure all required fields are present if(message.data.baseRev == null) { messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!"); @@ -465,6 +472,9 @@ function handleUserChanges(client, message) var baseRev = message.data.baseRev; var wireApool = (new AttributePool()).fromJsonable(message.data.apool); var changeset = message.data.changeset; + // The client might disconnect between our callbacks. We should still + // finish processing the changeset, so keep a reference to the session. + var thisSession = sessioninfos[client.id]; var r, apool, pad; @@ -472,7 +482,7 @@ function handleUserChanges(client, message) //get the pad function(callback) { - padManager.getPad(sessioninfos[client.id].padId, function(err, value) + padManager.getPad(thisSession.padId, function(err, value) { if(ERR(err, callback)) return; pad = value; @@ -484,22 +494,23 @@ function handleUserChanges(client, message) { //ex. _checkChangesetAndPool - //Copied from Etherpad, don't know what it does exactly try { - //this looks like a changeset check, it throws errors sometimes + // Verify that the changeset has valid syntax and is in canonical form Changeset.checkRep(changeset); - + + // Verify that the attribute indexes used in the changeset are all + // defined in the accompanying attribute pool. Changeset.eachAttribNumber(changeset, function(n) { if (! wireApool.getAttrib(n)) { throw "Attribute pool is missing attribute "+n+" for changeset "+changeset; } }); } - //there is an error in this changeset, so just refuse it catch(e) { - console.warn("Can't apply USER_CHANGES "+changeset+", cause it faild checkRep"); + // There is an error in this changeset, so just refuse it + console.warn("Can't apply USER_CHANGES "+changeset+", because it failed checkRep"); client.json.send({disconnect:"badChangeset"}); return; } @@ -512,7 +523,10 @@ function handleUserChanges(client, message) //ex. applyUserChanges apool = pad.pool; r = baseRev; - + + // The client's changeset might not be based on the latest revision, + // since other clients are sending changes at the same time. + // Update the changeset so that it can be applied to the latest revision. //https://github.com/caolan/async#whilst async.whilst( function() { return r < pad.getHeadRevisionNumber(); }, @@ -523,9 +537,18 @@ function handleUserChanges(client, message) pad.getRevisionChangeset(r, function(err, c) { if(ERR(err, callback)) return; - + + // At this point, both "c" (from the pad) and "changeset" (from the + // client) are relative to revision r - 1. The follow function + // rebases "changeset" so that it is relative to revision r + // and can be applied after "c". changeset = Changeset.follow(c, changeset, false, apool); - callback(null); + + if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep + async.nextTick(callback); + } else { + callback(null); + } }); }, //use the callback of the series function @@ -545,15 +568,14 @@ function handleUserChanges(client, message) return; } - var thisAuthor = sessioninfos[client.id].author; - - pad.appendRevision(changeset, thisAuthor); + pad.appendRevision(changeset, thisSession.author); var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool); if (correctionChangeset) { pad.appendRevision(correctionChangeset); } - + + // Make sure the pad always ends with an empty line. if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) { var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n"); pad.appendRevision(nlChangeset); @@ -860,6 +882,13 @@ function handleClientReady(client, message) }, function(callback) { + //Check that the client is still here. It might have disconnected between callbacks. + if(sessioninfos[client.id] === undefined) + { + callback(); + return; + } + //Check if this author is already on the pad, if yes, kick the other sessions! if(pad2sessions[padIds.padId]) { @@ -874,10 +903,9 @@ function handleClientReady(client, message) } //Save in sessioninfos that this session belonges to this pad - var sessionId=String(client.id); - sessioninfos[sessionId].padId = padIds.padId; - sessioninfos[sessionId].readOnlyPadId = padIds.readOnlyPadId; - sessioninfos[sessionId].readonly = padIds.readonly; + sessioninfos[client.id].padId = padIds.padId; + sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId; + sessioninfos[client.id].readonly = padIds.readonly; //check if there is already a pad2sessions entry, if not, create one if(!pad2sessions[padIds.padId]) @@ -886,7 +914,7 @@ function handleClientReady(client, message) } //Saves in pad2sessions that this session belongs to this pad - pad2sessions[padIds.padId].push(sessionId); + pad2sessions[padIds.padId].push(client.id); //prepare all values for the wire var atext = Changeset.cloneAText(pad.atext); @@ -951,26 +979,22 @@ function handleClientReady(client, message) clientVars.userName = authorName; } - if(sessioninfos[client.id] !== undefined) + //If this is a reconnect, we don't have to send the client the ClientVars again + if(message.reconnect == true) { - //This is a reconnect, so we don't have to send the client the ClientVars again - if(message.reconnect == true) - { - //Save the revision in sessioninfos, we take the revision from the info the client send to us - sessioninfos[client.id].rev = message.client_rev; - } - //This is a normal first connect - else - { - //Send the clientVars to the Client - client.json.send({type: "CLIENT_VARS", data: clientVars}); - //Save the revision in sessioninfos - sessioninfos[client.id].rev = pad.getHeadRevisionNumber(); - } - - //Save the revision and the author id in sessioninfos - sessioninfos[client.id].author = author; + //Save the revision in sessioninfos, we take the revision from the info the client send to us + sessioninfos[client.id].rev = message.client_rev; } + //This is a normal first connect + else + { + //Send the clientVars to the Client + client.json.send({type: "CLIENT_VARS", data: clientVars}); + //Save the current revision in sessioninfos, should be the same as in clientVars + sessioninfos[client.id].rev = pad.getHeadRevisionNumber(); + } + + sessioninfos[client.id].author = author; //prepare the notification for the other users on the pad, that this user joined var messageToTheOtherUsers = { diff --git a/src/node/hooks/express/errorhandling.js b/src/node/hooks/express/errorhandling.js index 749b0427..3c595683 100644 --- a/src/node/hooks/express/errorhandling.js +++ b/src/node/hooks/express/errorhandling.js @@ -39,11 +39,8 @@ exports.expressCreateServer = function (hook_name, args, cb) { // allowing you to respond however you like res.send(500, { error: 'Sorry, something bad happened!' }); console.error(err.stack? err.stack : err.toString()); - exports.gracefulShutdown(); - next(); }) - //connect graceful shutdown with sigint and uncaughtexception if(os.type().indexOf("Windows") == -1) { //sigint is so far not working on windows diff --git a/src/package.json b/src/package.json index 67aa1037..06f7b974 100644 --- a/src/package.json +++ b/src/package.json @@ -10,7 +10,7 @@ "name": "Robin Buse" } ], "dependencies" : { - "yajsml" : "1.1.4", + "yajsml" : "1.1.5", "request" : "2.9.100", "require-kernel" : "1.0.5", "resolve" : "0.2.x", @@ -42,5 +42,5 @@ "engines" : { "node" : ">=0.6.0", "npm" : ">=1.0" }, - "version" : "1.0.0" + "version" : "1.1.2" } diff --git a/src/static/js/ace.js b/src/static/js/ace.js index db62deb4..e50f75c7 100644 --- a/src/static/js/ace.js +++ b/src/static/js/ace.js @@ -24,6 +24,8 @@ // requires: plugins // requires: undefined +var KERNEL_SOURCE = '../static/js/require-kernel.js'; + Ace2Editor.registry = { nextId: 1 }; @@ -31,6 +33,14 @@ Ace2Editor.registry = { var hooks = require('./pluginfw/hooks'); var _ = require('./underscore'); +function scriptTag(source) { + return ( + '' + ) +} + function Ace2Editor() { var ace2 = Ace2Editor; @@ -155,24 +165,6 @@ function Ace2Editor() return {embeded: embededFiles, remote: remoteFiles}; } - function pushRequireScriptTo(buffer) { - var KERNEL_SOURCE = '../static/js/require-kernel.js'; - var KERNEL_BOOT = '\ -require.setRootURI("../javascripts/src");\n\ -require.setLibraryURI("../javascripts/lib");\n\ -require.setGlobalKeyPath("require");\n\ -'; - if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) { - buffer.push('\ -'); - - iframeHTML.push('