Merge branch 'develop' of github.com:Pita/etherpad-lite into feature/frontend-tests

This commit is contained in:
Peter 'Pita' Martischka 2012-10-27 16:32:37 +01:00
commit f59b643aa6
25 changed files with 378 additions and 83 deletions

View file

@ -1,16 +0,0 @@
So, a plugin is an npm package whose name starts with ep_ and that contains a file ep.json
require("ep_etherpad-lite/static/js/plugingfw/plugins").update() will use npm to list all installed modules and read their ep.json files. These will contain registrations for hooks which are loaded
A hook registration is a pairs of a hook name and a function reference (filename for require() plus function name)
require("ep_etherpad-lite/static/js/plugingfw/hooks").callAll("hook_name", {argname:value}) will call all hook functions registered for hook_name
That is the basis.
Ok, so that was a slight simplification: inside ep.json, hook registrations are grouped into groups called "parts". Parts from all plugins are ordered using a topological sort according to "pre" and "post" pointers to other plugins/parts (just like dependencies, but non-installed plugins are silently ignored).
This ordering is honored when you do callAll(hook_name) - hook functions for that hook_name are called in that order
Ordering between plugins is undefined, only parts are ordered.
A plugin usually has one part, but it van have multiple.
This is so that it can insert some hook registration before that of another plugin, and another one after.
This is important for e.g. registering URL-handlers for the express webserver, if you have some very generic and some very specific url-regexps
So, that's basically it... apart from client-side hooks
which works the same way, but uses a separate member of the part (part.client_hooks vs part.hooks), and where the hook function must obviously reside in a file require():able from the client...
One thing more: The main etherpad tree is actually a plugin itself, called ep_etherpad-lite, and it has it's own ep.json...
was that clear?

View file

@ -1,9 +1,9 @@
var startTime = new Date().getTime();
var fs = require("fs");
var ueberDB = require("ueberDB");
var mysql = require("mysql");
var async = require("async");
var ueberDB = require("../src/node_modules/ueberDB");
var mysql = require("../src/node_modules/ueberDB/node_modules/mysql");
var async = require("../src/node_modules/async");
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");

View file

@ -0,0 +1,11 @@
var dirty = require("../src/node_modules/ueberDB/node_modules/dirty")('var/dirty.db');
var db = require("../src/node/db/DB");
db.init(function() {
db = db.db;
dirty.on("load", function() {
dirty.forEach(function(key, value) {
db.set(key, value);
});
});
});

View file

@ -1,3 +1,4 @@
@include documentation
@include api/api
@include plugins
@include database

View file

@ -4,4 +4,5 @@
@include hooks_client-side
@include hooks_server-side
@include editorInfo
@include changeset_library
@include changeset_library
@include pluginfw

View file

@ -65,6 +65,42 @@ This hook gets called upon the rendering of an ejs template block. For any speci
Have a look at `src/templates/pad.html` and `src/templates/timeslider.html` to see which blocks are available.
## padCreate
Called from: src/node/db/Pad.js
Things in context:
1. pad - the pad instance
This hook gets called when a new pad was created.
## padLoad
Called from: src/node/db/Pad.js
Things in context:
1. pad - the pad instance
This hook gets called when an pad was loaded. If a new pad was created and loaded this event will be emitted too.
## padUpdate
Called from: src/node/db/Pad.js
Things in context:
1. pad - the pad instance
This hook gets called when an existing pad was updated.
## padRemove
Called from: src/node/db/Pad.js
Things in context:
1. padID
This hook gets called when an existing pad was removed/deleted.
## socketio
Called from: src/node/hooks/express/socketio.js

14
doc/api/pluginfw.md Normal file
View file

@ -0,0 +1,14 @@
# Plugin Framework
`require("ep_etherpad-lite/static/js/plugingfw/plugins")`
## plugins.update
`require("ep_etherpad-lite/static/js/plugingfw/plugins").update()` will use npm to list all installed modules and read their ep.json files, registering the contained hooks.
A hook registration is a pairs of a hook name and a function reference (filename for require() plus function name)
## hooks.callAll
`require("ep_etherpad-lite/static/js/plugingfw/hooks").callAll("hook_name", {argname:value})` will call all hook functions registered for `hook_name` with `{argname:value}`.
## hooks.aCallAll
?
## ...

106
doc/plugins.md Normal file
View file

@ -0,0 +1,106 @@
# Plugins
Etherpad-Lite allows you to extend its functionality with plugins. A plugin registers hooks (functions) for certain events (thus certain features) in Etherpad-lite to execute its own functionality based on these events.
Publicly available plugins can be found in the npm registry (see <http://npmjs.org>). Etherpad-lite's naming convention for plugins is to prefix your plugins with `ep_`. So, e.g. it's `ep_flubberworms`. Thus you can install plugins from npm, using `npm install ep_flubberworm` in etherpad-lite's root directory.
You can also browse to `http://yourEtherpadInstan.ce/admin/plugins`, which will list all installed plugins and those available on npm. It even provides functionality to search through all available plugins.
## Folder structure
A basic plugin usually has the following folder structure:
```
ep_<plugin>/
| static/
| templates/
+ ep.json
+ package.json
```
If your plugin includes client-side hooks, put them in `static/js/`. If you're adding in CSS or image files, you should put those files in `static/css/ `and `static/image/`, respectively, and templates go into `templates/`.
A Standard directory structure like this makes it easier to navigate through your code. That said, do note, that this is not actually *required* to make your plugin run.
## Plugin definition
Your plugin definition goes into `ep.json`. In this file you register your hooks, indicate the parts of your plugin and the order of execution. (A documentation of all available events to hook into can be found in chapter [hooks](#all_hooks).)
A hook registration is a pairs of a hook name and a function reference (filename to require() + exported function name)
```json
{
"parts": [
{
"name": "nameThisPartHoweverYouWant",
"hooks": {
"authenticate" : "ep_<plugin>/<file>:FUNCTIONNAME1",
"expressCreateServer": "ep_<plugin>/<file>:FUNCTIONNAME2"
},
"client_hooks": {
"acePopulateDOMLine": "ep_plugin/<file>:FUNCTIONNAME3"
}
}
]
}
```
Etherpad-lite will expect the part of the hook definition before the colon to be a javascript file and will try to require it. The part after the colon is expected to be a valid function identifier of that module. So, you have to export your hooks, using [`module.exports`](http://nodejs.org/docs/latest/api/modules.html#modules_modules) and register it in `ep.json` as `ep_<plugin>/path/to/<file>:FUNCTIONNAME`.
You can omit the `FUNCTIONNAME` part, if the exported function has got the same name as the hook. So `"authorize" : "ep_flubberworm/foo"` will call the function `exports.authorize` in `ep_flubberworm/foo.js`
### Client hooks and server hooks
There are server hooks, which will be executed on the server (e.g. `expressCreateServer`), and there are client hooks, which are executed on the client (e.g. `acePopulateDomLine`). Be sure to not make assumptions about the environment your code is running in, e.g. don't try to access `process`, if you know your code will be run on the client, and likewise, don't try to access `window` on the server...
### Parts
As your plugins become more and more complex, you will find yourself in the need to manage dependencies between plugins. E.g. you want the hooks of a certain plugin to be executed before (or after) yours. You can also manage these dependencies in your plugin definition file `ep.json`:
```javascript
{
"parts": [
{
"name": "onepart",
"pre": [],
"post": ["ep_onemoreplugin/partone"]
"hooks": {
"storeBar": "ep_monospace/plugin:storeBar",
"getFoo": "ep_monospace/plugin:getFoo",
}
},
{
"name": "otherpart",
"pre": ["ep_my_example/somepart", "ep_otherplugin/main"],
"post": [],
"hooks": {
"someEvent": "ep_my_example/otherpart:someEvent",
"another": "ep_my_example/otherpart:another"
}
}
]
}
```
Usually a plugin will add only one functionality at a time, so it will probably only use one `part` definition to register its hooks. However, sometimes you have to put different (unrelated) functionalities into one plugin. For this you will want use parts, so other plugins can depend on them.
#### pre/post
The `"pre"` and `"post"` definitions, affect the order in which parts of a plugin are executed. This ensures that plugins and their hooks are executed in the correct order.
`"pre"` lists parts that must be executed *before* the defining part. `"post"` lists parts that must be executed *after* the defining part.
You can, on a basic level, think of this as double-ended dependency listing. If you have a dependency on another plugin, you can make sure it loads before yours by putting it in `"pre"`. If you are setting up things that might need to be used by a plugin later, you can ensure proper order by putting it in `"post"`.
Note that it would be far more sane to use `"pre"` in almost any case, but if you want to change config variables for another plugin, or maybe modify its environment, `"post"` could definitely be useful.
Also, note that dependencies should *also* be listed in your package.json, so they can be `npm install`'d automagically when your plugin gets installed.
## Package definition
Your plugin must also contain a [package definition file](http://npmjs.org/doc/json.html), called package.json, in the project root - this file contains various metadata relevant to your plugin, such as the name and version number, author, project hompage, contributors, a short description, etc. If you publish your plugin on npm, these metadata are used for package search etc., but it's necessary for Etherpad-lite plugins, even if you don't publish your plugin.
```json
{
"name": "ep_PLUGINNAME",
"version": "0.0.1",
"description": "DESCRIPTION",
"author": "USERNAME (REAL NAME) <MAIL@EXAMPLE.COM>",
"contributors": [],
"dependencies": {"MODULE": "0.3.20"},
"engines": { "node": ">= 0.6.0"}
}
```
## Templates
If your plugin adds or modifies the front end HTML (e.g. adding buttons or changing their functions), you should put the necessary HTML code for such operations in `templates/`, in files of type ".ejs", since Etherpad-Lite uses EJS for HTML templating. See the following link for more information about EJS: <https://github.com/visionmedia/ejs>.

View file

@ -141,22 +141,6 @@ exports.getAuthor = function (author, callback)
db.get("globalAuthor:" + author, callback);
}
/**
* Returns the Author Name of the author
* @param {String} author The id of the author
* @param {Function} callback callback(err, name)
*/
exports.getAuthorName = function (authorID, callback)
{
db.getSub("globalAuthor:" + author, ["name"], callback);
console.log(authorID);
db.getSub("globalAuthor:" + authorID, ["name"], function(err, authorName){
if(ERR(err, callback)) return;
callback(null, {authorName: authorName});
});
}
/**

View file

@ -16,6 +16,7 @@ var padMessageHandler = require("../handler/PadMessageHandler");
var readOnlyManager = require("./ReadOnlyManager");
var crypto = require("crypto");
var randomString = require("../utils/randomstring");
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
//serialization/deserialization attributes
var attributeBlackList = ["id"];
@ -86,6 +87,12 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
// set the author to pad
if(author)
authorManager.addPad(author, this.id);
if (this.head == 0) {
hooks.callAll("padCreate", {'pad':this});
} else {
hooks.callAll("padUpdate", {'pad':this});
}
};
//save all attributes to the database
@ -368,6 +375,7 @@ Pad.prototype.init = function init(text, callback) {
_this.appendRevision(firstChangeset, '');
}
hooks.callAll("padLoad", {'pad':_this});
callback(null);
});
};
@ -467,6 +475,7 @@ Pad.prototype.remove = function remove(callback) {
{
db.remove("pad:"+padID);
padManager.unloadPad(padID);
hooks.callAll("padRemove", {'padID':padID});
callback();
}
], function(err)

View file

@ -176,9 +176,11 @@ exports.handleMessage = function(client, message)
// Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages
// handleMessage will be called, even if the client is not authorized
hooks.aCallAll("handleMessage", { client: client, message: message }, function ( messages ) {
hooks.aCallAll("handleMessage", { client: client, message: message }, function ( err, messages ) {
if(ERR(err, callback)) return;
_.each(messages, function(newMessage){
if ( newmessage === null ) {
if ( newMessage === null ) {
dropMessage = true;
}
});
@ -415,22 +417,34 @@ function handleUserInfoUpdate(client, message)
authorManager.setAuthorName(author, message.data.userInfo.name);
var padId = sessioninfos[client.id].padId;
var infoMsg = {
type: "COLLABROOM",
data: {
// The Client doesn't know about USERINFO_UPDATE, use USER_NEWINFO
type: "USER_NEWINFO",
userInfo: {
userId: author,
name: message.data.userInfo.name,
colorId: message.data.userInfo.colorId,
userAgent: "Anonymous",
ip: "127.0.0.1",
}
}
};
//set a null name, when there is no name set. cause the client wants it null
if(message.data.userInfo.name == null)
if(infoMsg.data.userInfo.name == null)
{
message.data.userInfo.name = null;
infoMsg.data.userInfo.name = null;
}
//The Client don't know about a USERINFO_UPDATE, it can handle only new user_newinfo, so change the message type
message.data.type = "USER_NEWINFO";
//Send the other clients on the pad the update message
for(var i in pad2sessions[padId])
{
if(pad2sessions[padId][i] != client.id)
{
socketio.sockets.sockets[pad2sessions[padId][i]].json.send(message);
socketio.sockets.sockets[pad2sessions[padId][i]].json.send(infoMsg);
}
}
}

View file

@ -37,7 +37,7 @@ exports.ip = "0.0.0.0";
/**
* The Port ep-lite should listen to
*/
exports.port = 9001;
exports.port = process.env.PORT || 9001;
/*
* The Type of the database
*/

View file

@ -48,7 +48,7 @@ CachingMiddleware.prototype = new function () {
var old_res = {};
var supportsGzip =
req.header('Accept-Encoding', '').indexOf('gzip') != -1;
(req.get('Accept-Encoding') || '').indexOf('gzip') != -1;
var path = require('url').parse(req.url).path;
var cacheKey = (new Buffer(path)).toString('base64').replace(/[\/\+=]/g, '');

View file

@ -5,9 +5,10 @@
"keywords" : ["etherpad", "realtime", "collaborative", "editor"],
"author" : "Peter 'Pita' Martischka <petermartischka@googlemail.com> - Primary Technology Ltd",
"contributors" : [
{ "name": "John McLear",
"name": "Hans Pinckaers",
"name": "Robin Buse" }
{ "name": "John McLear" },
{ "name": "Hans Pinckaers" },
{ "name": "Robin Buse" },
{ "name": "Marcel Klehr" }
],
"dependencies" : {
"yajsml" : "1.1.6",
@ -15,7 +16,7 @@
"require-kernel" : "1.0.5",
"resolve" : "0.2.x",
"socket.io" : "0.9.x",
"ueberDB" : "0.1.7",
"ueberDB" : "0.1.8",
"async" : "0.1.22",
"express" : "3.x",
"connect" : "2.4.x",
@ -25,7 +26,7 @@
"log4js" : "0.5.x",
"jsdom-nocontextifiy" : "0.2.10",
"async-stacktrace" : "0.0.2",
"npm" : "1.1.24",
"npm" : "1.1.x",
"ejs" : "0.6.1",
"graceful-fs" : "1.1.5",
"slide" : "1.1.3",
@ -42,5 +43,5 @@
"engines" : { "node" : ">=0.6.0",
"npm" : ">=1.0"
},
"version" : "1.1.2"
"version" : "1.1.4"
}

View file

@ -122,6 +122,11 @@ function Ace2Editor()
return info.ace_getDebugProperty(prop);
};
editor.getInInternationalComposition = function()
{
return info.ace_getInInternationalComposition();
};
// prepareUserChangeset:
// Returns null if no new changes or ACE not ready. Otherwise, bundles up all user changes
// to the latest base text into a Changeset, which is returned (as a string if encodeAsString).

View file

@ -1173,7 +1173,7 @@ function Ace2Inner(){
//if (! top.BEFORE) top.BEFORE = [];
//top.BEFORE.push(magicdom.root.dom.innerHTML);
//if (! isEditable) return; // and don't reschedule
if (window.parent.parent.inInternationalComposition)
if (inInternationalComposition)
{
// don't do idle input incorporation during international input composition
idleWorkTimer.atLeast(500);
@ -3729,7 +3729,7 @@ function Ace2Inner(){
thisKeyDoesntTriggerNormalize = true;
}
if ((!specialHandled) && (!thisKeyDoesntTriggerNormalize) && (!window.parent.parent.inInternationalComposition))
if ((!specialHandled) && (!thisKeyDoesntTriggerNormalize) && (!inInternationalComposition))
{
if (type != "keyup" || !incorpIfQuick())
{
@ -4589,9 +4589,24 @@ function Ace2Inner(){
}
}
var inInternationalComposition = false;
function handleCompositionEvent(evt)
{
window.parent.parent.handleCompositionEvent(evt);
// international input events, fired in FF3, at least; allow e.g. Japanese input
if (evt.type == "compositionstart")
{
inInternationalComposition = true;
}
else if (evt.type == "compositionend")
{
inInternationalComposition = false;
}
}
editorInfo.ace_getInInternationalComposition = function ()
{
return inInternationalComposition;
}
function bindTheEventHandlers()

View file

@ -111,7 +111,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
function handleUserChanges()
{
if (window.parent.parent.inInternationalComposition) return;
if (editor.getInInternationalComposition()) return;
if ((!getSocket()) || channelState == "CONNECTING")
{
if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000))
@ -288,7 +288,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
var apool = msg.apool;
// When inInternationalComposition, msg pushed msgQueue.
if (msgQueue.length > 0 || window.parent.parent.inInternationalComposition) {
if (msgQueue.length > 0 || editor.getInInternationalComposition()) {
if (msgQueue.length > 0) oldRev = msgQueue[msgQueue.length - 1].newRev;
else oldRev = rev;
@ -358,6 +358,14 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
{
var userInfo = msg.userInfo;
var id = userInfo.userId;
// Avoid a race condition when setting colors. If our color was set by a
// query param, ignore our own "new user" message's color value.
if (id === initialUserInfo.userId && initialUserInfo.globalUserColor)
{
msg.userInfo.colorId = initialUserInfo.globalUserColor;
}
if (userSet[id])
{

View file

@ -24,6 +24,13 @@
var colorutils = {};
// Check that a given value is a css hex color value, e.g.
// "#ffffff" or "#fff"
colorutils.isCssHex = function(cssColor)
{
return /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(cssColor);
}
// "#ffffff" or "#fff" or "ffffff" or "fff" to [1.0, 1.0, 1.0]
colorutils.css2triple = function(cssColor)
{

View file

@ -311,7 +311,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
['insertorder', 'first']
].concat(
_.map(state.lineAttributes,function(value,key){
if (window.console) console.log([key, value])
if (typeof(window)!= 'undefined' && window.console) console.log([key, value])
return [key, value];
})
);

View file

@ -43,6 +43,7 @@ var padmodals = require('./pad_modals').padmodals;
var padsavedrevs = require('./pad_savedrevs');
var paduserlist = require('./pad_userlist').paduserlist;
var padutils = require('./pad_utils').padutils;
var colorutils = require('./colorutils').colorutils;
var createCookie = require('./pad_utils').createCookie;
var readCookie = require('./pad_utils').readCookie;
@ -50,22 +51,6 @@ var randomString = require('./pad_utils').randomString;
var hooks = require('./pluginfw/hooks');
window.inInternationalComposition = false;
var inInternationalComposition = window.inInternationalComposition;
window.handleCompositionEvent = function handleCompositionEvent(evt)
{
// international input events, fired in FF3, at least; allow e.g. Japanese input
if (evt.type == "compositionstart")
{
this.inInternationalComposition = true;
}
else if (evt.type == "compositionend")
{
this.inInternationalComposition = false;
}
}
function createCookie(name, value, days, path)
{
if (days)
@ -114,6 +99,7 @@ function getParams()
var showControls = params["showControls"];
var showChat = params["showChat"];
var userName = params["userName"];
var userColor = params["userColor"];
var showLineNumbers = params["showLineNumbers"];
var useMonospaceFont = params["useMonospaceFont"];
var IsnoColors = params["noColors"];
@ -162,6 +148,11 @@ function getParams()
// If the username is set as a parameter we should set a global value that we can call once we have initiated the pad.
settings.globalUserName = decodeURIComponent(userName);
}
if(userColor)
// If the userColor is set as a parameter, set a global value to use once we have initiated the pad.
{
settings.globalUserColor = decodeURIComponent(userColor);
}
if(rtl)
{
if(rtl == "true")
@ -363,6 +354,14 @@ function handshake()
pad.myUserInfo.name = settings.globalUserName;
$('#myusernameedit').attr({"value":settings.globalUserName}); // Updates the current users UI
}
if (settings.globalUserColor !== false && colorutils.isCssHex(settings.globalUserColor))
{
// Add a 'globalUserColor' property to myUserInfo, so collabClient knows we have a query parameter.
pad.myUserInfo.globalUserColor = settings.globalUserColor;
pad.notifyChangeColor(settings.globalUserColor); // Updates pad.myUserInfo.colorId
paduserlist.setMyUserInfo(pad.myUserInfo);
}
}
//This handles every Message after the clientVars
else
@ -1029,6 +1028,7 @@ var settings = {
, noColors: false
, useMonospaceFontGlobal: false
, globalUserName: false
, globalUserColor: false
, rtlIsTrue: false
};

View file

@ -144,13 +144,12 @@ function handleClientVars(message)
require('./pad_impexp').padimpexp.init();
//change export urls when the slider moves
var export_rev_regex = /(\/\d+)?\/export/
BroadcastSlider.onSlider(function(revno)
{
// export_links is a jQuery Array, so .each is allowed.
export_links.each(function()
{
this.setAttribute('href', this.href.replace(export_rev_regex, '/' + revno + '/export'));
this.setAttribute('href', this.href.replace( /(.+?)\/\w+\/(\d+\/)?export/ , '$1/' + padId + '/' + revno + '/export'));
});
});

View file

@ -2,6 +2,29 @@
<html>
<title>Etherpad Lite</title>
<script>
/*
|@licstart The following is the entire license notice for the
JavaScript code in this page.|
Copyright 2011 Peter Martischka, Primary Technology.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
|@licend The above is the entire license notice
for the JavaScript code in this page.|
*/
</script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
@ -9,9 +32,11 @@
<link rel="shortcut icon" href="favicon.ico">
<style>
html, body {
height: 100%;
}
body {
margin: 0;
height: 100%;
color: #333;
font: 14px helvetica, sans-serif;
background: #ddd;

View file

@ -3,8 +3,30 @@
%>
<!doctype html>
<html>
<title>Etherpad Lite</title>
<script>
/*
|@licstart The following is the entire license notice for the
JavaScript code in this page.|
Copyright 2011 Peter Martischka, Primary Technology.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
|@licend The above is the entire license notice
for the JavaScript code in this page.|
*/
</script>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
@ -233,7 +255,7 @@
</div>
<div class="reconnecting">
<h1>Reestablishing connection...</h1>
<p><img alt="" border="0" src="/static/img/connectingbar.gif" /></p>
<p><img alt="" border="0" src="../static/img/connectingbar.gif" /></p>
</div>
<div class="userdup">
<h1>Opened in another window.</h1>

View file

@ -1,11 +1,32 @@
<!doctype html>
<html lang="en">
<title>Etherpad Lite Timeslider</title>
<script>
/*
|@licstart The following is the entire license notice for the
JavaScript code in this page.|
Copyright 2011 Peter Martischka, Primary Technology.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
|@licend The above is the entire license notice
for the JavaScript code in this page.|
*/
</script>
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<title>Etherpad Lite Timeslider</title>
<link rel="shortcut icon" href="../../favicon.ico">
<link rel="stylesheet" href="../../static/css/pad.css">
<link rel="stylesheet" href="../../static/css/timeslider.css">
@ -83,7 +104,7 @@
</div>
<div class="reconnecting">
<h1>Reestablishing connection...</h1>
<p><img alt="" border="0" src="/static/img/connectingbar.gif" /></p>
<p><img alt="" border="0" src="../../static/img/connectingbar.gif" /></p>
</div>
<div class="userdup">
<h1>Opened in another window.</h1>

32
src/web.config Normal file
View file

@ -0,0 +1,32 @@
<configuration>
<system.webServer>
<handlers>
<add name="iisnode" path="node_modules/ep_etherpad-lite/node/server.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<!-- uncomment this section to enable debugging
<rule name="LogFile" patternSyntax="ECMAScript" stopProcessing="true">
<match url="iisnode"/>
<action type="Rewrite" url="node_modules/ep_etherpad-lite/node/iisnode" />
</rule>
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^server.js\/debug[\/]?" />
</rule>
-->
<rule name="StaticContent">
<action type="Rewrite" url="public{{REQUEST_URI}}"/>
</rule>
<rule name="DynamicContent">
<conditions>
<add input="{{REQUEST_FILENAME}}" matchType="IsFile" negate="True"/>
</conditions>
<action type="Rewrite" url="node_modules/ep_etherpad-lite/node/server.js" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>