From 5fac05a395a8a9739364d95373c0805ba36e6e45 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 3 Aug 2012 22:04:06 +0200 Subject: [PATCH 01/11] Add docs. --- doc/api/changeset_library.md | 141 +++++++++++++++++++++ doc/api/editorInfo.md | 47 +++++++ doc/api/embed_parameters.md | 49 ++++++++ doc/api/hooks_client-side.md | 160 +++++++++++++++++++++++ doc/api/hooks_server-side.md | 134 ++++++++++++++++++++ doc/api/http_api.md | 237 +++++++++++++++++++++++++++++++++++ 6 files changed, 768 insertions(+) create mode 100644 doc/api/changeset_library.md create mode 100644 doc/api/editorInfo.md create mode 100644 doc/api/embed_parameters.md create mode 100644 doc/api/hooks_client-side.md create mode 100644 doc/api/hooks_server-side.md create mode 100644 doc/api/http_api.md diff --git a/doc/api/changeset_library.md b/doc/api/changeset_library.md new file mode 100644 index 00000000..8d739b70 --- /dev/null +++ b/doc/api/changeset_library.md @@ -0,0 +1,141 @@ +# Changeset Library + +``` +"Z:z>1|2=m=b*0|1+1$\n" +``` + +This is a Changeset. Its just a string and its very difficult to read in this form. But the Changeset Library gives us some tools to read it. + +A changeset describes the diff between two revisions of the document. The Browser sends changesets to the server and the server sends them to the clients to update them. This Changesets gets also saved into the history of a pad. Which allows us to go back to every revision from the past. + +## Changeset.unpack(changeset) + + * `changeset` {String} + +This functions returns an object representaion of the changeset, similar to this: + +``` +{ oldLen: 35, newLen: 36, ops: '|2=m=b*0|1+1', charBank: '\n' } +``` + + * `oldLen` {Number} the original length of the document. + * `newLen` {Number} the length of the document after the changeset is applied. + * `ops` {String} the actual changes, introduced by this changeset. + * `charBank` {String} All characters that are added by this changeset. + +## Changeset.opIterator(ops) + + * `ops` {String} The operators, returned by `Changeset.unpack()` + +Returns an operator iterator. This iterator allows us to iterate over all operators that are in the changeset. + +You can iterate with an opIterator using its `next()` and `hasNext()` methods. Next returns the `next()` operator object and `hasNext()` indicates, whether there are any operators left. + +## The Operator object +There are 3 types of operators: `+`,`-` and `=`. These operators describe different changes to the document, beginning with the first character of the document. A `=` operator doesn't change the text, but it may add or remove text attributes. A `-` operator removes text. And a `+` Operator adds text and optionally adds some attributes to it. + + * `opcode` {String} the operator type + * `chars` {Number} the length of the text changed by this operator. + * `lines` {Number} the number of lines changed by this operator. + * `attribs` {attribs} attributes set on this text. + +### Example +``` +{ opcode: '+', + chars: 1, + lines: 1, + attribs: '*0' } +``` + +## APool +
+> var AttributePoolFactory = require("./utils/AttributePoolFactory");
+> var apool = AttributePoolFactory.createAttributePool();
+> console.log(apool)
+{ numToAttrib: {},
+  attribToNum: {},
+  nextNum: 0,
+  putAttrib: [Function],
+  getAttrib: [Function],
+  getAttribKey: [Function],
+  getAttribValue: [Function],
+  eachAttrib: [Function],
+  toJsonable: [Function],
+  fromJsonable: [Function] }
+
+This creates an empty apool. A apool saves which attributes were used during the history of a pad. There is one apool for each pad. It only saves the attributes that were really used, it doesn't save unused attributes. Lets fill this apool with some values +
+> apool.fromJsonable({"numToAttrib":{"0":["author","a.kVnWeomPADAT2pn9"],"1":["bold","true"],"2":["italic","true"]},"nextNum":3});
+> console.log(apool)
+{ numToAttrib: 
+   { '0': [ 'author', 'a.kVnWeomPADAT2pn9' ],
+     '1': [ 'bold', 'true' ],
+     '2': [ 'italic', 'true' ] },
+  attribToNum: 
+   { 'author,a.kVnWeomPADAT2pn9': 0,
+     'bold,true': 1,
+     'italic,true': 2 },
+  nextNum: 3,
+  putAttrib: [Function],
+  getAttrib: [Function],
+  getAttribKey: [Function],
+  getAttribValue: [Function],
+  eachAttrib: [Function],
+  toJsonable: [Function],
+  fromJsonable: [Function] }
+
+We used the fromJsonable function to fill the empty apool with values. the fromJsonable and toJsonable functions are used to serialize and deserialize an apool. You can see that it stores the relation between numbers and attributes. So for example the attribute 1 is the attribute bold and vise versa. A attribute is always a key value pair. For stuff like bold and italic its just 'italic':'true'. For authors its author:$AUTHORID. So a character can be bold and italic. But it can't belong to multiple authors +
+> apool.getAttrib(1)
+[ 'bold', 'true' ]
+
+Simple example of how to get the key value pair for the attribute 1 + +## AText +
+> var atext = {"text":"bold text\nitalic text\nnormal text\n\n","attribs":"*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2"};
+> console.log(atext)
+{ text: 'bold text\nitalic text\nnormal text\n\n',
+  attribs: '*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2' }
+
+This is an atext. An atext has two parts: text and attribs. The text is just the text of the pad as a string. We will look closer at the attribs at the next steps +
+> var opiterator = Changeset.opIterator(atext.attribs)
+> console.log(opiterator)
+{ next: [Function: next],
+  hasNext: [Function: hasNext],
+  lastIndex: [Function: lastIndex] }
+> opiterator.next()
+{ opcode: '+',
+  chars: 9,
+  lines: 0,
+  attribs: '*0*1' }
+> opiterator.next()
+{ opcode: '+',
+  chars: 1,
+  lines: 1,
+  attribs: '*0' }
+> opiterator.next()
+{ opcode: '+',
+  chars: 11,
+  lines: 0,
+  attribs: '*0*1*2' }
+> opiterator.next()
+{ opcode: '+',
+  chars: 1,
+  lines: 1,
+  attribs: '' }
+> opiterator.next()
+{ opcode: '+',
+  chars: 11,
+  lines: 0,
+  attribs: '*0' }
+> opiterator.next()
+{ opcode: '+',
+  chars: 2,
+  lines: 2,
+  attribs: '' }
+
+The attribs are again a bunch of operators like .ops in the changeset was. But these operators are only + operators. They describe which part of the text has which attributes + +For more information see /doc/easysync/easysync-notes.txt in the source. \ No newline at end of file diff --git a/doc/api/editorInfo.md b/doc/api/editorInfo.md new file mode 100644 index 00000000..38902bb5 --- /dev/null +++ b/doc/api/editorInfo.md @@ -0,0 +1,47 @@ +## editorInfo + +### editorInfo.ace_replaceRange(start, end, text) +This function replaces a range (from `start` to `end`) with `text`. + +### editorInfo.ace_getRep() +Returns the `rep` object. + +### editorInfo.ace_getAuthor() +### editorInfo.ace_inCallStack() +### editorInfo.ace_inCallStackIfNecessary(?) +### editorInfo.ace_focus(?) +### editorInfo.ace_importText(?) +### editorInfo.ace_importAText(?) +### editorInfo.ace_exportText(?) +### editorInfo.ace_editorChangedSize(?) +### editorInfo.ace_setOnKeyPress(?) +### editorInfo.ace_setOnKeyDown(?) +### editorInfo.ace_setNotifyDirty(?) +### editorInfo.ace_dispose(?) +### editorInfo.ace_getFormattedCode(?) +### editorInfo.ace_setEditable(bool) +### editorInfo.ace_execCommand(?) +### editorInfo.ace_callWithAce(fn, callStack, normalize) +### editorInfo.ace_setProperty(key, value) +### editorInfo.ace_setBaseText(txt) +### editorInfo.ace_setBaseAttributedText(atxt, apoolJsonObj) +### editorInfo.ace_applyChangesToBase(c, optAuthor, apoolJsonObj) +### editorInfo.ace_prepareUserChangeset() +### editorInfo.ace_applyPreparedChangesetToBase() +### editorInfo.ace_setUserChangeNotificationCallback(f) +### editorInfo.ace_setAuthorInfo(author, info) +### editorInfo.ace_setAuthorSelectionRange(author, start, end) +### editorInfo.ace_getUnhandledErrors() +### editorInfo.ace_getDebugProperty(prop) +### editorInfo.ace_fastIncorp(?) +### editorInfo.ace_isCaret(?) +### editorInfo.ace_getLineAndCharForPoint(?) +### editorInfo.ace_performDocumentApplyAttributesToCharRange(?) +### editorInfo.ace_setAttributeOnSelection(?) +### editorInfo.ace_toggleAttributeOnSelection(?) +### editorInfo.ace_performSelectionChange(?) +### editorInfo.ace_doIndentOutdent(?) +### editorInfo.ace_doUndoRedo(?) +### editorInfo.ace_doInsertUnorderedList(?) +### editorInfo.ace_doInsertOrderedList(?) +### editorInfo.ace_performDocumentApplyAttributesToRange() \ No newline at end of file diff --git a/doc/api/embed_parameters.md b/doc/api/embed_parameters.md new file mode 100644 index 00000000..6450c067 --- /dev/null +++ b/doc/api/embed_parameters.md @@ -0,0 +1,49 @@ +# Embed parameters +You can easily embed your etherpad-lite into any webpage by using iframes. You can configure the embedded pad using embed paramters. + +## Parameters + +### showLineNumbers + * Boolean + +Default: true + +### showControls + * Boolean + +Default: true + +### showChat + * Boolean + +Default: true + +### useMonospaceFont + * Boolean + +Default: false + +### userName + * String + +Default: "unnamed" + +Example: `userName=Etherpad%20User` + +### noColors + * Boolean + +Default: false + +### alwaysShowChat + * Boolean + +Default: false + + +## Example +``` + +``` + +Cut and paste this into any webpage (between the body tags) to embed a pad. The above parameters will hide the chat and the line numbers. \ No newline at end of file diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md new file mode 100644 index 00000000..0d73272c --- /dev/null +++ b/doc/api/hooks_client-side.md @@ -0,0 +1,160 @@ +# Client-side hooks +Most of these hooks are called during or in order to set up the formatting process. + +## aceDomLineProcessLineAttributes +Called from: src/static/js/domline.js + +Things in context: + +1. domline - The current DOM line being processed +2. cls - The class of the current block element (useful for styling) + +This hook is called for elements in the DOM that have the "lineMarkerAttribute" set. You can add elements into this category with the aceRegisterBlockElements hook above. + +The return value of this hook should have the following structure: + +`{ preHtml: String, postHtml: String, processedMarker: Boolean }` + +The preHtml and postHtml values will be added to the HTML display of the element, and if processedMarker is true, the engine won't try to process it any more. + +## aceCreateDomLine +Called from: src/static/js/domline.js + +Things in context: + +1. domline - the current DOM line being processed +2. cls - The class of the current element (useful for styling) + +This hook is called for any line being processed by the formatting engine, unless the aceDomLineProcessLineAttributes hook from above returned true, in which case this hook is skipped. + +The return value of this hook should have the following structure: + +`{ extraOpenTags: String, extraCloseTags: String, cls: String }` + +extraOpenTags and extraCloseTags will be added before and after the element in question, and cls will be the new class of the element going forward. + +## acePostWriteDomLineHTML +Called from: src/static/js/domline.js + +Things in context: + +1. node - the DOM node that just got written to the page + +This hook is for right after a node has been fully formatted and written to the page. + +## aceAttribsToClasses +Called from: src/static/js/linestylefilter.js + +Things in context: + +1. linestylefilter - the JavaScript object that's currently processing the ace attributes +2. key - the current attribute being processed +3. value - the value of the attribute being processed + +This hook is called during the attribute processing procedure, and should be used to translate key, value pairs into valid HTML classes that can be inserted into the DOM. + +The return value for this function should be a list of classes, which will then be parsed into a valid class string. + +## aceGetFilterStack +Called from: src/static/js/linestylefilter.js + +Things in context: + +1. linestylefilter - the JavaScript object that's currently processing the ace attributes +2. browser - an object indicating which browser is accessing the page + +This hook is called to apply custom regular expression filters to a set of styles. The one example available is the ep_linkify plugin, which adds internal links. They use it to find the telltale `[[ ]]` syntax that signifies internal links, and finding that syntax, they add in the internalHref attribute to be later used by the aceCreateDomLine hook (documented above). + +## aceEditorCSS +Called from: src/static/js/ace.js + +Things in context: None + +This hook is provided to allow custom CSS files to be loaded. The return value should be an array of paths relative to the plugins directory. + +## aceInitInnerdocbodyHead +Called from: src/static/js/ace.js + +Things in context: + +1. iframeHTML - the HTML of the editor iframe up to this point, in array format + +This hook is called during the creation of the editor HTML. The array should have lines of HTML added to it, giving the plugin author a chance to add in meta, script, link, and other tags that go into the `` element of the editor HTML document. + +## aceEditEvent +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 (this is a mystery to me) + +This hook is made available to edit the edit events that might occur when changes are made. Currently you can change the editor information, some of the meanings of the edit, and so on. You can also make internal changes (internal to your plugin) that use the information provided by the edit event. + +## aceRegisterBlockElements +Called from: src/static/js/ace2_inner.js + +Things in context: None + +The return value of this hook will add elements into the "lineMarkerAttribute" category, making the aceDomLineProcessLineAttributes hook (documented below) call for those elements. + +## aceInitialized +Called from: src/static/js/ace2_inner.js + +Things in context: + +1. editorInfo - information about the user who will be making changes through the interface, and a way to insert functions into the main ace object (see ep_headings) +2. rep - information about where the user's cursor is +3. documentAttributeManager - some kind of magic + +This hook is for inserting further information into the ace engine, for later use in formatting hooks. + +## postAceInit +Called from: src/static/js/pad.js + +Things in context: + +1. ace - the ace object that is applied to this editor. + +There doesn't appear to be any example available of this particular hook being used, but it gets fired after the editor is all set up. + +# Other client hooks + +These hooks are for other parts of the software on the client side, that is, when the editor is being used. + +## userJoinOrUpdate +Called from: src/static/js/pad_userlist.js + +Things in context: + +1. info - the user information + +This hook is called on the client side whenever a user joins or changes. This can be used to create notifications or an alternate user list. + +## collectContentPre +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. style - the style applied to the node (probably CSS) +5. cls - the HTML class string of the node + +This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. + +## collectContentPost +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. style - the style applied to the node (probably CSS) +5. cls - the HTML class string of the node + +This hook is called after the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. \ No newline at end of file diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md new file mode 100644 index 00000000..246b8c4d --- /dev/null +++ b/doc/api/hooks_server-side.md @@ -0,0 +1,134 @@ +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 + +# Server-side hooks +These hooks are called on server-side. + +## pluginUninstall +Called from: src/static/js/pluginfw/installer.js + +Things in context: + +1. plugin_name - self-explanatory + +If this hook returns an error, the callback to the uninstall function gets an error as well. This mostly seems useful for handling additional features added in based on the installation of other plugins, which is pretty cool! + +## pluginInstall +Called from: src/static/js/pluginfw/installer.js + +Things in context: + +1. plugin_name - self-explanatory + +If this hook returns an error, the callback to the install function gets an error, too. This seems useful for adding in features when a particular plugin is installed. + +## init_`` +Called from: src/static/js/pluginfw/plugins.js + +Things in context: None + +This function is called after a specific plugin is initialized. This would probably be more useful than the previous two functions if you only wanted to add in features to one specific plugin. + +## expressConfigure +Called from: src/node/server.js + +Things in context: + +1. app - the main application object + +This is a helpful hook for changing the behavior and configuration of the application. It's called right after the application gets configured. + +## expressCreateServer +Called from: src/node/server.js + +Things in context: + +1. app - the main application object (helpful for adding new paths and such) + +This hook gets called after the application object has been created, but before it starts listening. This is similar to the expressConfigure hook, but it's not guaranteed that the application object will have all relevant configuration variables. + +## eejsBlock_`` +Called from: src/node/eejs/index.js + +Things in context: + +1. content - the content of the block + +This hook gets called upon the rendering of an ejs template block. For any specific kind of block, you can change how that block gets rendered by modifying the content object passed in. + +Have a look at `src/templates/pad.html` and `src/templates/timeslider.html` to see which blocks are available. + +## socketio +Called from: src/node/hooks/express/socketio.js + +Things in context: + +1. app - the application object +2. io - the socketio object + +I have no idea what this is useful for, someone else will have to add this description. + +## authorize +Called from: src/node/hooks/express/webaccess.js + +Things in context: + +1. req - the request object +2. res - the response object +3. next - ? +4. resource - the path being accessed + +This is useful for modifying the way authentication is done, especially for specific paths. + +## authenticate +Called from: src/node/hooks/express/webaccess.js + +Things in context: + +1. req - the request object +2. res - the response object +3. next - ? +4. username - the username used (optional) +5. password - the password used (optional) + +This is useful for modifying the way authentication is done. + +## authFailure +Called from: src/node/hooks/express/webaccess.js + +Things in context: + +1. req - the request object +2. res - the response object +3. next - ? + +This is useful for modifying the way authentication is done. + +## handleMessage +Called from: src/node/handler/PadMessageHandler.js + +Things in context: + +1. message - the message being handled +2. client - the client object from socket.io + +This hook will be called once a message arrive. If a plugin calls `callback(null)` the message will be dropped. However it is not possible to modify the message. + +Plugins may also decide to implement custom behavior once a message arrives. + +**WARNING**: handleMessage will be called, even if the client is not authorized to send this message. It's up to the plugin to check permissions. + +Example: + +``` +function handleMessage ( hook, context, callback ) { + if ( context.message.type == 'USERINFO_UPDATE' ) { + // If the message type is USERINFO_UPDATE, drop the message + callback(null); + }else{ + callback(); + } +}; +``` \ No newline at end of file diff --git a/doc/api/http_api.md b/doc/api/http_api.md new file mode 100644 index 00000000..a4246b5c --- /dev/null +++ b/doc/api/http_api.md @@ -0,0 +1,237 @@ +# HTTP API + +## What can I do with this API? +The API gives another web application control of the pads. The basic functions are + +* create/delete pads +* grant/forbid access to pads +* get/set pad content + +The API is designed in a way, so you can reuse your existing user system with their permissions, and map it to etherpad lite. Means: Your web application still has to do authentication, but you can tell etherpad lite via the api, which visitors should get which permissions. This allows etherpad lite to fit into any web application and extend it with real-time functionality. You can embed the pads via an iframe into your website. + +Take a look at [HTTP API client libraries](https://github.com/Pita/etherpad-lite/wiki/HTTP-API-client-libraries) to see if a library in your favorite language. + +## Examples + +### Example 1 + +A portal (such as WordPress) wants to give a user access to a new pad. Let's assume the user have the internal id 7 and his name is michael. + +Portal maps the internal userid to an etherpad author. + +> Request: `http://pad.domain/api/1/createAuthorIfNotExistsFor?apikey=secret&name=Michael&authorMapper=7` +> +> Response: `{code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}}` + +Portal maps the internal userid to an etherpad group: + +> Request: `http://pad.domain/api/1/createGroupIfNotExistsFor?apikey=secret&groupMapper=7` +> +> Response: `{code: 0, message:"ok", data: {groupID: "g.s8oes9dhwrvt0zif"}}` + +Portal creates a pad in the userGroup + +> Request: `http://pad.domain/api/1/createGroupPad?apikey=secret&groupID=g.s8oes9dhwrvt0zif&padName=samplePad&text=This is the first sentence in the pad` +> +> Response: `{code: 0, message:"ok", data: null}` + +Portal starts the session for the user on the group: + +> Request: `http://pad.domain/api/1/createSession?apikey=secret&groupID=g.s8oes9dhwrvt0zif&authorID=a.s8oes9dhwrvt0zif&validUntil=1312201246` +> +> Response: `{"data":{"sessionID": "s.s8oes9dhwrvt0zif"}}` + +Portal places the cookie "sessionID" with the given value on the client and creates an iframe including the pad. + +### Example 2 + +A portal (such as WordPress) wants to transform the contents of a pad that multiple admins edited into a blog post. + +Portal retrieves the contents of the pad for entry into the db as a blog post: + +> Request: `http://pad.domain/api/1/getText?apikey=secret&padID=g.s8oes9dhwrvt0zif$123` +> +> Response: `{code: 0, message:"ok", data: {text:"Welcome Text"}}` + +Portal submits content into new blog post + +> Portal.AddNewBlog(content) +> + +## Request Format + +The API is accessible via HTTP. HTTP Requests are in the format /api/$APIVERSION/$FUNCTIONNAME. Parameters are transmitted via HTTP GET. $APIVERSION is 1 + +## Response Format +Responses are valid JSON in the following format: + +```js +{ + "code": number, + "message": string, + "data": obj +} +``` + +* **code** a return code + * **0** everything ok + * **1** wrong parameters + * **2** internal error + * **3** no such function + * **4** no or wrong API Key +* **message** a status message. Its ok if everything is fine, else it contains an error message +* **data** the payload + +## Overview + +![API Overview](http://i.imgur.com/d0nWp.png) + +## Data Types + +* **groupID** a string, the unique id of a group. Format is g.16RANDOMCHARS, for example g.s8oes9dhwrvt0zif +* **sessionID** a string, the unique id of a session. Format is s.16RANDOMCHARS, for example s.s8oes9dhwrvt0zif +* **authorID** a string, the unique id of an author. Format is a.16RANDOMCHARS, for example a.s8oes9dhwrvt0zif +* **readOnlyID** a string, the unique id of an readonly relation to a pad. Format is r.16RANDOMCHARS, for example r.s8oes9dhwrvt0zif +* **padID** a string, format is GROUPID$PADNAME, for example the pad test of group g.s8oes9dhwrvt0zif has padID g.s8oes9dhwrvt0zif$test + +## Authentication + +Authentication works via a token that is sent with each request as a post parameter. There is a single token per Etherpad-Lite deployment. This token will be random string, generated by Etherpad-Lite at the first start. It will be saved in APIKEY.txt in the root folder of Etherpad Lite. Only Etherpad Lite and the requesting application knows this key. Token management will not be exposed through this API. + +## Node Interoperability + +All functions will also be available through a node module accessable from other node.js applications. + +## JSONP + +The API provides _JSONP_ support to allow requests from a server in a different domain. +Simply add `&jsonp=?` to the API call. + +Example usage: http://api.jquery.com/jQuery.getJSON/ + +# API Functions + +## Groups +Pads can belong to a group. The padID of grouppads is starting with a groupID like g.asdfasdfasdfasdf$test + +* **createGroup()** creates a new group

*Example returns:* + * `{code: 0, message:"ok", data: {groupID: g.s8oes9dhwrvt0zif}}` + +* **createGroupIfNotExistsFor(groupMapper)** this functions helps you to map your application group ids to etherpad lite group ids

*Example returns:* + * `{code: 0, message:"ok", data: {groupID: g.s8oes9dhwrvt0zif}}` + +* **deleteGroup(groupID)** deletes a group

*Example returns:* + * `{code: 0, message:"ok", data: null}` + * `{code: 1, message:"groupID does not exist", data: null}` + +* **listPads(groupID)** returns all pads of this group

*Example returns:* + * `{code: 0, message:"ok", data: {padIDs : ["g.s8oes9dhwrvt0zif$test", "g.s8oes9dhwrvt0zif$test2"]}` + * `{code: 1, message:"groupID does not exist", data: null}` + +* **createGroupPad(groupID, padName [, text])** creates a new pad in this group

*Example returns:* + * `{code: 0, message:"ok", data: null}` + * `{code: 1, message:"pad does already exist", data: null}` + * `{code: 1, message:"groupID does not exist", data: null}` + +## Author +Theses authors are bind to the attributes the users choose (color and name). + +* **createAuthor([name])** creates a new author

*Example returns:* + * `{code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}}` + +* **createAuthorIfNotExistsFor(authorMapper [, name])** this functions helps you to map your application author ids to etherpad lite author ids

*Example returns:* + * `{code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}}` + +* **listPadsOfAuthor(authorID)** returns an array of all pads this author contributed to

*Example returns:* + * `{code: 0, message:"ok", data: {padIDs: ["g.s8oes9dhwrvt0zif$test", "g.s8oejklhwrvt0zif$foo"]}}` + * `{code: 1, message:"authorID does not exist", data: null}` + +-> can't be deleted cause this would involve scanning all the pads where this author was + +## Session +Sessions can be created between a group and an author. This allows an author to access more than one group. The sessionID will be set as a cookie to the client and is valid until a certain date. Only users with a valid session for this group, can access group pads. You can create a session after you authenticated the user at your web application, to give them access to the pads. You should save the sessionID of this session and delete it after the user logged out + +* **createSession(groupID, authorID, validUntil)** creates a new session. validUntil is an unix timestamp in seconds

*Example returns:* + * `{code: 0, message:"ok", data: {sessionID: "s.s8oes9dhwrvt0zif"}}` + * `{code: 1, message:"groupID doesn't exist", data: null}` + * `{code: 1, message:"authorID doesn't exist", data: null}` + * `{code: 1, message:"validUntil is in the past", data: null}` + +* **deleteSession(sessionID)** deletes a session

*Example returns:* + * `{code: 1, message:"ok", data: null}` + * `{code: 1, message:"sessionID does not exist", data: null}` + +* **getSessionInfo(sessionID)** returns informations about a session

*Example returns:* + * `{code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif", groupID: g.s8oes9dhwrvt0zif, validUntil: 1312201246}}` + * `{code: 1, message:"sessionID does not exist", data: null}` + +* **listSessionsOfGroup(groupID)** returns all sessions of a group

*Example returns:* + * `{"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}}` + * `{code: 1, message:"groupID does not exist", data: null}` + +* **listSessionsOfAuthor(authorID)** returns all sessions of an author

*Example returns:* + * `{"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}}` + * `{code: 1, message:"authorID does not exist", data: null}` + +## Pad Content + +Pad content can be updated and retrieved through the API + +* **getText(padID, [rev])** returns the text of a pad

*Example returns:* + * `{code: 0, message:"ok", data: {text:"Welcome Text"}}` + * `{code: 1, message:"padID does not exist", data: null}` + +* **setText(padID, text)** sets the text of a pad

*Example returns:* + * `{code: 0, message:"ok", data: null}` + * `{code: 1, message:"padID does not exist", data: null}` + * `{code: 1, message:"text too long", data: null}` + +* **getHTML(padID, [rev])** returns the text of a pad formatted as HTML

*Example returns:* + * `{code: 0, message:"ok", data: {html:"Welcome Text
More Text"}}` + * `{code: 1, message:"padID does not exist", data: null}` + +## Pad +Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security manager controls access of them and its forbidden for normal pads to include a $ in the name. + +* **createPad(padID [, text])** creates a new (non-group) pad. Note that if you need to create a group Pad, you should call **createGroupPad**.

*Example returns:* + * `{code: 0, message:"ok", data: null}` + * `{code: 1, message:"pad does already exist", data: null}` + +* **getRevisionsCount(padID)** returns the number of revisions of this pad

*Example returns:* + * `{code: 0, message:"ok", data: {revisions: 56}}` + * `{code: 1, message:"padID does not exist", data: null}` + +* **padUsersCount(padID)** returns the number of user that are currently editing this pad

*Example returns:* + * `{code: 0, message:"ok", data: {padUsersCount: 5}}` + +* **deletePad(padID)** deletes a pad

*Example returns:* + * `{code: 0, message:"ok", data: null}` + * `{code: 1, message:"padID does not exist", data: null}` + +* **getReadOnlyID(padID)** returns the read only link of a pad

*Example returns:* + * `{code: 0, message:"ok", data: {readOnlyID: "r.s8oes9dhwrvt0zif"}}` + * `{code: 1, message:"padID does not exist", data: null}` + +* **setPublicStatus(padID, publicStatus)** sets a boolean for the public status of a pad

*Example returns:* + * `{code: 0, message:"ok", data: null}` + * `{code: 1, message:"padID does not exist", data: null}` + +* **getPublicStatus(padID)** return true of false

*Example returns:* + * `{code: 0, message:"ok", data: {publicStatus: true}}` + * `{code: 1, message:"padID does not exist", data: null}` + +* **setPassword(padID, password)** returns ok or a error message

*Example returns:* + * `{code: 0, message:"ok", data: null}` + * `{code: 1, message:"padID does not exist", data: null}` + +* **isPasswordProtected(padID)** returns true or false

*Example returns:* + * `{code: 0, message:"ok", data: {passwordProtection: true}}` + * `{code: 1, message:"padID does not exist", data: null}` + +* **listAuthorsOfPad(padID)** returns an array of authors who contributed to this pad

*Example returns:* + * `{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}` + * `{code: 1, message:"padID does not exist", data: null}` + +* **getLastEdited(padID)** returns the timestamp of the last revision of the pad

*Example returns:* + * `{code: 0, message:"ok", data: {lastEdited: 1340815946602}}` + * `{code: 1, message:"padID does not exist", data: null}` \ No newline at end of file From 70fb76511863448bea0f254f3cdd578317c96d23 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 7 Aug 2012 20:19:37 +0200 Subject: [PATCH 02/11] Restructure headings. --- doc/all.md | 3 +++ doc/api/api.md | 7 +++++++ doc/api/changeset_library.md | 14 ++++++------- doc/api/embed_parameters.md | 20 +++++++++--------- doc/api/hooks_client-side.md | 39 ++++++++++++++++++------------------ doc/api/hooks_server-side.md | 28 +++++++++++++------------- doc/api/http_api.md | 38 ++++++++++++++++++----------------- doc/database.md | 2 ++ doc/documentation.md | 15 ++++++++++++++ 9 files changed, 97 insertions(+), 69 deletions(-) create mode 100644 doc/all.md create mode 100644 doc/api/api.md create mode 100644 doc/documentation.md diff --git a/doc/all.md b/doc/all.md new file mode 100644 index 00000000..40001c31 --- /dev/null +++ b/doc/all.md @@ -0,0 +1,3 @@ +@include documentation.md +@include api/api.md +@include database.md diff --git a/doc/api/api.md b/doc/api/api.md new file mode 100644 index 00000000..55b527c7 --- /dev/null +++ b/doc/api/api.md @@ -0,0 +1,7 @@ +# API +@include embed_parameters.md +@include http_api.md +@include hooks_client-side.md +@include hooks_server-side.md +@include editorInfo.md +@include changeset_library.md \ No newline at end of file diff --git a/doc/api/changeset_library.md b/doc/api/changeset_library.md index 8d739b70..6c0c52c2 100644 --- a/doc/api/changeset_library.md +++ b/doc/api/changeset_library.md @@ -1,4 +1,4 @@ -# Changeset Library +## Changeset Library ``` "Z:z>1|2=m=b*0|1+1$\n" @@ -8,7 +8,7 @@ This is a Changeset. Its just a string and its very difficult to read in this fo A changeset describes the diff between two revisions of the document. The Browser sends changesets to the server and the server sends them to the clients to update them. This Changesets gets also saved into the history of a pad. Which allows us to go back to every revision from the past. -## Changeset.unpack(changeset) +### Changeset.unpack(changeset) * `changeset` {String} @@ -23,7 +23,7 @@ This functions returns an object representaion of the changeset, similar to this * `ops` {String} the actual changes, introduced by this changeset. * `charBank` {String} All characters that are added by this changeset. -## Changeset.opIterator(ops) +### Changeset.opIterator(ops) * `ops` {String} The operators, returned by `Changeset.unpack()` @@ -31,7 +31,7 @@ Returns an operator iterator. This iterator allows us to iterate over all operat You can iterate with an opIterator using its `next()` and `hasNext()` methods. Next returns the `next()` operator object and `hasNext()` indicates, whether there are any operators left. -## The Operator object +### The Operator object There are 3 types of operators: `+`,`-` and `=`. These operators describe different changes to the document, beginning with the first character of the document. A `=` operator doesn't change the text, but it may add or remove text attributes. A `-` operator removes text. And a `+` Operator adds text and optionally adds some attributes to it. * `opcode` {String} the operator type @@ -39,7 +39,7 @@ There are 3 types of operators: `+`,`-` and `=`. These operators describe differ * `lines` {Number} the number of lines changed by this operator. * `attribs` {attribs} attributes set on this text. -### Example +#### Example ``` { opcode: '+', chars: 1, @@ -47,7 +47,7 @@ There are 3 types of operators: `+`,`-` and `=`. These operators describe differ attribs: '*0' } ``` -## APool +### APool
 > var AttributePoolFactory = require("./utils/AttributePoolFactory");
 > var apool = AttributePoolFactory.createAttributePool();
@@ -91,7 +91,7 @@ We used the fromJsonable function to fill the empty apool with values. the fromJ
 
Simple example of how to get the key value pair for the attribute 1 -## AText +### AText
 > var atext = {"text":"bold text\nitalic text\nnormal text\n\n","attribs":"*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2"};
 > console.log(atext)
diff --git a/doc/api/embed_parameters.md b/doc/api/embed_parameters.md
index 6450c067..e8f9eaf7 100644
--- a/doc/api/embed_parameters.md
+++ b/doc/api/embed_parameters.md
@@ -1,7 +1,13 @@
-# Embed parameters
+## Embed parameters
 You can easily embed your etherpad-lite into any webpage by using iframes. You can configure the embedded pad using embed paramters.
 
-## Parameters
+Example:
+
+Cut and paste the following code into any webpage to embed a pad. The parameters below will hide the chat and the line numbers.
+
+```
+
+```
 
 ### showLineNumbers
  * Boolean
@@ -38,12 +44,4 @@ Default: false
 ### alwaysShowChat
  * Boolean
 
-Default: false
-
-
-## Example
-```
-
-```
-
-Cut and paste this into any webpage (between the body tags) to embed a pad. The above parameters will hide the chat and the line numbers.
\ No newline at end of file
+Default: false
\ No newline at end of file
diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md
index 0d73272c..6652f757 100644
--- a/doc/api/hooks_client-side.md
+++ b/doc/api/hooks_client-side.md
@@ -1,7 +1,12 @@
-# Client-side hooks
+## Client-side hooks
 Most of these hooks are called during or in order to set up the formatting process.
 
-## aceDomLineProcessLineAttributes
+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
+
+### aceDomLineProcessLineAttributes
 Called from: src/static/js/domline.js
 
 Things in context:
@@ -17,7 +22,7 @@ The return value of this hook should have the following structure:
 
 The preHtml and postHtml values will be added to the HTML display of the element, and if processedMarker is true, the engine won't try to process it any more.
 
-## aceCreateDomLine
+### aceCreateDomLine
 Called from: src/static/js/domline.js
 
 Things in context:
@@ -33,7 +38,7 @@ The return value of this hook should have the following structure:
 
 extraOpenTags and extraCloseTags will be added before and after the element in question, and cls will be the new class of the element going forward.
 
-## acePostWriteDomLineHTML
+### acePostWriteDomLineHTML
 Called from: src/static/js/domline.js
 
 Things in context:
@@ -42,7 +47,7 @@ Things in context:
 
 This hook is for right after a node has been fully formatted and written to the page.
 
-## aceAttribsToClasses
+### aceAttribsToClasses
 Called from: src/static/js/linestylefilter.js
 
 Things in context:
@@ -55,7 +60,7 @@ This hook is called during the attribute processing procedure, and should be use
 
 The return value for this function should be a list of classes, which will then be parsed into a valid class string.
 
-## aceGetFilterStack
+### aceGetFilterStack
 Called from: src/static/js/linestylefilter.js
 
 Things in context:
@@ -65,14 +70,14 @@ Things in context:
 
 This hook is called to apply custom regular expression filters to a set of styles. The one example available is the ep_linkify plugin, which adds internal links. They use it to find the telltale `[[ ]]` syntax that signifies internal links, and finding that syntax, they add in the internalHref attribute to be later used by the aceCreateDomLine hook (documented above).
 
-## aceEditorCSS
+### aceEditorCSS
 Called from: src/static/js/ace.js
 
 Things in context: None
 
 This hook is provided to allow custom CSS files to be loaded. The return value should be an array of paths relative to the plugins directory.
 
-## aceInitInnerdocbodyHead
+### aceInitInnerdocbodyHead
 Called from: src/static/js/ace.js
 
 Things in context:
@@ -81,7 +86,7 @@ Things in context:
 
 This hook is called during the creation of the editor HTML. The array should have lines of HTML added to it, giving the plugin author a chance to add in meta, script, link, and other tags that go into the `` element of the editor HTML document.
 
-## aceEditEvent
+### aceEditEvent
 Called from: src/static/js/ace2_inner.js
 
 Things in context:
@@ -93,14 +98,14 @@ Things in context:
 
 This hook is made available to edit the edit events that might occur when changes are made. Currently you can change the editor information, some of the meanings of the edit, and so on. You can also make internal changes (internal to your plugin) that use the information provided by the edit event.
 
-## aceRegisterBlockElements
+### aceRegisterBlockElements
 Called from: src/static/js/ace2_inner.js
 
 Things in context: None
 
 The return value of this hook will add elements into the "lineMarkerAttribute" category, making the aceDomLineProcessLineAttributes hook (documented below) call for those elements.
 
-## aceInitialized
+### aceInitialized
 Called from: src/static/js/ace2_inner.js
 
 Things in context:
@@ -111,7 +116,7 @@ Things in context:
 
 This hook is for inserting further information into the ace engine, for later use in formatting hooks.
 
-## postAceInit
+### postAceInit
 Called from: src/static/js/pad.js
 
 Things in context:
@@ -120,11 +125,7 @@ Things in context:
 
 There doesn't appear to be any example available of this particular hook being used, but it gets fired after the editor is all set up.
 
-# Other client hooks
-
-These hooks are for other parts of the software on the client side, that is, when the editor is being used.
-
-## userJoinOrUpdate
+### userJoinOrUpdate
 Called from: src/static/js/pad_userlist.js
 
 Things in context:
@@ -133,7 +134,7 @@ Things in context:
 
 This hook is called on the client side whenever a user joins or changes. This can be used to create notifications or an alternate user list.
 
-## collectContentPre
+### collectContentPre
 Called from: src/static/js/contentcollector.js
 
 Things in context:
@@ -146,7 +147,7 @@ Things in context:
 
 This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original.
 
-## collectContentPost
+### collectContentPost
 Called from: src/static/js/contentcollector.js
 
 Things in context:
diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md
index 246b8c4d..8fd5c3ba 100644
--- a/doc/api/hooks_server-side.md
+++ b/doc/api/hooks_server-side.md
@@ -1,12 +1,12 @@
+## 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
 
-# Server-side hooks
-These hooks are called on server-side.
-
-## pluginUninstall
+### pluginUninstall
 Called from: src/static/js/pluginfw/installer.js
 
 Things in context:
@@ -15,7 +15,7 @@ Things in context:
 
 If this hook returns an error, the callback to the uninstall function gets an error as well. This mostly seems useful for handling additional features added in based on the installation of other plugins, which is pretty cool!
 
-## pluginInstall
+### pluginInstall
 Called from: src/static/js/pluginfw/installer.js
 
 Things in context:
@@ -24,14 +24,14 @@ Things in context:
 
 If this hook returns an error, the callback to the install function gets an error, too. This seems useful for adding in features when a particular plugin is installed.
 
-## init_``
+### init_``
 Called from: src/static/js/pluginfw/plugins.js
 
 Things in context: None
 
 This function is called after a specific plugin is initialized. This would probably be more useful than the previous two functions if you only wanted to add in features to one specific plugin.
 
-## expressConfigure
+### expressConfigure
 Called from: src/node/server.js
 
 Things in context:
@@ -40,7 +40,7 @@ Things in context:
 
 This is a helpful hook for changing the behavior and configuration of the application. It's called right after the application gets configured.
 
-## expressCreateServer
+### expressCreateServer
 Called from: src/node/server.js
 
 Things in context:
@@ -49,7 +49,7 @@ Things in context:
 
 This hook gets called after the application object has been created, but before it starts listening. This is similar to the expressConfigure hook, but it's not guaranteed that the application object will have all relevant configuration variables.
 
-## eejsBlock_``
+### eejsBlock_``
 Called from: src/node/eejs/index.js
 
 Things in context:
@@ -60,7 +60,7 @@ 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.
 
-## socketio
+### socketio
 Called from: src/node/hooks/express/socketio.js
 
 Things in context:
@@ -70,7 +70,7 @@ Things in context:
 
 I have no idea what this is useful for, someone else will have to add this description.
 
-## authorize
+### authorize
 Called from: src/node/hooks/express/webaccess.js
 
 Things in context:
@@ -82,7 +82,7 @@ Things in context:
 
 This is useful for modifying the way authentication is done, especially for specific paths.
 
-## authenticate
+### authenticate
 Called from: src/node/hooks/express/webaccess.js
 
 Things in context:
@@ -95,7 +95,7 @@ Things in context:
 
 This is useful for modifying the way authentication is done.
 
-## authFailure
+### authFailure
 Called from: src/node/hooks/express/webaccess.js
 
 Things in context:
@@ -106,7 +106,7 @@ Things in context:
 
 This is useful for modifying the way authentication is done.
 
-## handleMessage
+### handleMessage
 Called from: src/node/handler/PadMessageHandler.js
 
 Things in context:
diff --git a/doc/api/http_api.md b/doc/api/http_api.md
index a4246b5c..33932ca3 100644
--- a/doc/api/http_api.md
+++ b/doc/api/http_api.md
@@ -1,6 +1,6 @@
-# HTTP API
+## HTTP API
 
-## What can I do with this API?
+### What can I do with this API?
 The API gives another web application control of the pads. The basic functions are
 
 * create/delete pads 
@@ -11,9 +11,9 @@ The API is designed in a way, so you can reuse your existing user system with th
 
 Take a look at [HTTP API client libraries](https://github.com/Pita/etherpad-lite/wiki/HTTP-API-client-libraries) to see if a library in your favorite language.
 
-## Examples
+### Examples
 
-### Example 1
+#### Example 1
 
 A portal (such as WordPress) wants to give a user access to a new pad. Let's assume the user have the internal id 7 and his name is michael. 
 
@@ -43,7 +43,7 @@ Portal starts the session for the user on the group:
 
 Portal places the cookie "sessionID" with the given value on the client and creates an iframe including the pad.
 
-### Example 2
+#### Example 2
 
 A portal (such as WordPress) wants to transform the contents of a pad that multiple admins edited into a blog post.
 
@@ -58,11 +58,13 @@ Portal submits content into new blog post
 > Portal.AddNewBlog(content)
 >
 
-## Request Format
+### Usage
+
+#### Request Format
 
 The API is accessible via HTTP. HTTP Requests are in the format /api/$APIVERSION/$FUNCTIONNAME. Parameters are transmitted via HTTP GET. $APIVERSION is 1
 
-## Response Format
+#### Response Format
 Responses are valid JSON in the following format:
 
 ```js
@@ -82,11 +84,11 @@ Responses are valid JSON in the following format:
 * **message** a status message. Its ok if everything is fine, else it contains an error message
 * **data** the payload
 
-## Overview
+#### Overview
 
 ![API Overview](http://i.imgur.com/d0nWp.png)
 
-## Data Types
+#### Data Types
 
 * **groupID**  a string, the unique id of a group. Format is g.16RANDOMCHARS, for example g.s8oes9dhwrvt0zif
 * **sessionID** a string, the unique id of a session. Format is s.16RANDOMCHARS, for example s.s8oes9dhwrvt0zif
@@ -94,24 +96,24 @@ Responses are valid JSON in the following format:
 * **readOnlyID** a string, the unique id of an readonly relation to a pad. Format is r.16RANDOMCHARS, for example r.s8oes9dhwrvt0zif
 * **padID** a string, format is GROUPID$PADNAME, for example the pad test of group g.s8oes9dhwrvt0zif has padID g.s8oes9dhwrvt0zif$test
 
-## Authentication
+#### Authentication
 
 Authentication works via a token that is sent with each request as a post parameter.  There is a single token per Etherpad-Lite deployment.  This token will be random string, generated by Etherpad-Lite at the first start. It will be saved in APIKEY.txt in the root folder of Etherpad Lite. Only Etherpad Lite and the requesting application knows this key. Token management will not be exposed through this API. 
 
-## Node Interoperability
+#### Node Interoperability
 
 All functions will also be available through a node module accessable from other node.js applications.
 
-## JSONP
+#### JSONP
 
 The API provides _JSONP_ support to allow requests from a server in a different domain.
 Simply add `&jsonp=?` to the API call.
 
 Example usage: http://api.jquery.com/jQuery.getJSON/
 
-# API Functions
+### API Methods
 
-## Groups
+#### Groups
 Pads can belong to a group. The padID of grouppads is starting with a groupID like g.asdfasdfasdfasdf$test
 
 * **createGroup()** creates a new group 

*Example returns:* @@ -133,7 +135,7 @@ 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}` -## Author +#### Author Theses authors are bind to the attributes the users choose (color and name). * **createAuthor([name])** creates a new author

*Example returns:* @@ -148,7 +150,7 @@ Theses authors are bind to the attributes the users choose (color and name). -> can't be deleted cause this would involve scanning all the pads where this author was -## Session +#### Session Sessions can be created between a group and an author. This allows an author to access more than one group. The sessionID will be set as a cookie to the client and is valid until a certain date. Only users with a valid session for this group, can access group pads. You can create a session after you authenticated the user at your web application, to give them access to the pads. You should save the sessionID of this session and delete it after the user logged out * **createSession(groupID, authorID, validUntil)** creates a new session. validUntil is an unix timestamp in seconds

*Example returns:* @@ -173,7 +175,7 @@ Sessions can be created between a group and an author. This allows an author to * `{"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}}` * `{code: 1, message:"authorID does not exist", data: null}` -## Pad Content +#### Pad Content Pad content can be updated and retrieved through the API @@ -190,7 +192,7 @@ Pad content can be updated and retrieved through the API * `{code: 0, message:"ok", data: {html:"Welcome Text
More Text"}}` * `{code: 1, message:"padID does not exist", data: null}` -## Pad +#### Pad Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security manager controls access of them and its forbidden for normal pads to include a $ in the name. * **createPad(padID [, text])** creates a new (non-group) pad. Note that if you need to create a group Pad, you should call **createGroupPad**.

*Example returns:* diff --git a/doc/database.md b/doc/database.md index 37269174..2e06e206 100644 --- a/doc/database.md +++ b/doc/database.md @@ -1,5 +1,7 @@ # Database structure +## Keys and their values + ### pad:$PADID Saves all informations about pads diff --git a/doc/documentation.md b/doc/documentation.md new file mode 100644 index 00000000..0542c436 --- /dev/null +++ b/doc/documentation.md @@ -0,0 +1,15 @@ +# About this Documentation + + + +The goal of this documentation is to comprehensively explain Etherpad-Lite, +both from a reference as well as a conceptual point of view. + +Where appropriate, property types, method arguments, and the arguments +provided to event handlers are detailed in a list underneath the topic +heading. + +Every `.html` file is generated based on the corresponding +`.markdown` file in the `doc/api/` folder in the source tree. The +documentation is generated using the `tools/doc/generate.js` program. +The HTML template is located at `doc/template.html`. \ No newline at end of file From 7e2820a674e0129cb7f570727c1e204af604a5de Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 7 Aug 2012 20:21:11 +0200 Subject: [PATCH 03/11] Remove file endings from includes. --- doc/all.md | 6 +++--- doc/api/api.md | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/all.md b/doc/all.md index 40001c31..c0cbf369 100644 --- a/doc/all.md +++ b/doc/all.md @@ -1,3 +1,3 @@ -@include documentation.md -@include api/api.md -@include database.md +@include documentation +@include api/api +@include database diff --git a/doc/api/api.md b/doc/api/api.md index 55b527c7..830e5f4c 100644 --- a/doc/api/api.md +++ b/doc/api/api.md @@ -1,7 +1,7 @@ # API -@include embed_parameters.md -@include http_api.md -@include hooks_client-side.md -@include hooks_server-side.md -@include editorInfo.md -@include changeset_library.md \ No newline at end of file +@include embed_parameters +@include http_api +@include hooks_client-side +@include hooks_server-side +@include editorInfo +@include changeset_library \ No newline at end of file From db4ae500a109543212d762ef1a8964c44d6472ed Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 7 Aug 2012 20:29:44 +0200 Subject: [PATCH 04/11] Fix markup. --- doc/api/changeset_library.md | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/doc/api/changeset_library.md b/doc/api/changeset_library.md index 6c0c52c2..498c2901 100644 --- a/doc/api/changeset_library.md +++ b/doc/api/changeset_library.md @@ -48,7 +48,8 @@ There are 3 types of operators: `+`,`-` and `=`. These operators describe differ ``` ### APool -
+
+```
 > var AttributePoolFactory = require("./utils/AttributePoolFactory");
 > var apool = AttributePoolFactory.createAttributePool();
 > console.log(apool)
@@ -62,9 +63,11 @@ There are 3 types of operators: `+`,`-` and `=`. These operators describe differ
   eachAttrib: [Function],
   toJsonable: [Function],
   fromJsonable: [Function] }
-
+``` + This creates an empty apool. A apool saves which attributes were used during the history of a pad. There is one apool for each pad. It only saves the attributes that were really used, it doesn't save unused attributes. Lets fill this apool with some values -
+
+```
 > apool.fromJsonable({"numToAttrib":{"0":["author","a.kVnWeomPADAT2pn9"],"1":["bold","true"],"2":["italic","true"]},"nextNum":3});
 > console.log(apool)
 { numToAttrib: 
@@ -83,23 +86,29 @@ This creates an empty apool. A apool saves which attributes were used during the
   eachAttrib: [Function],
   toJsonable: [Function],
   fromJsonable: [Function] }
-
+``` + We used the fromJsonable function to fill the empty apool with values. the fromJsonable and toJsonable functions are used to serialize and deserialize an apool. You can see that it stores the relation between numbers and attributes. So for example the attribute 1 is the attribute bold and vise versa. A attribute is always a key value pair. For stuff like bold and italic its just 'italic':'true'. For authors its author:$AUTHORID. So a character can be bold and italic. But it can't belong to multiple authors -
+
+```
 > apool.getAttrib(1)
 [ 'bold', 'true' ]
-
+``` + Simple example of how to get the key value pair for the attribute 1 ### AText -
+
+```
 > var atext = {"text":"bold text\nitalic text\nnormal text\n\n","attribs":"*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2"};
 > console.log(atext)
 { text: 'bold text\nitalic text\nnormal text\n\n',
   attribs: '*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2' }
-
+``` + This is an atext. An atext has two parts: text and attribs. The text is just the text of the pad as a string. We will look closer at the attribs at the next steps -
+
+```
 > var opiterator = Changeset.opIterator(atext.attribs)
 > console.log(opiterator)
 { next: [Function: next],
@@ -135,7 +144,8 @@ This is an atext. An atext has two parts: text and attribs. The text is just the
   chars: 2,
   lines: 2,
   attribs: '' }
-
+``` + The attribs are again a bunch of operators like .ops in the changeset was. But these operators are only + operators. They describe which part of the text has which attributes For more information see /doc/easysync/easysync-notes.txt in the source. \ No newline at end of file From e491476a0f6c3875ad34cca20010a2a414f0a165 Mon Sep 17 00:00:00 2001 From: Mark Holmquist Date: Wed, 8 Aug 2012 10:24:57 -0700 Subject: [PATCH 05/11] Added in docs for the HTTP call I just added --- doc/api/http_api.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/api/http_api.md b/doc/api/http_api.md index 33932ca3..da6e926b 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -236,4 +236,8 @@ Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security * **getLastEdited(padID)** returns the timestamp of the last revision of the pad

*Example returns:* * `{code: 0, message:"ok", data: {lastEdited: 1340815946602}}` - * `{code: 1, message:"padID does not exist", data: null}` \ No newline at end of file + * `{code: 1, message:"padID does not exist", data: null}` + +* **sendClientsMessage(padID, msg)** sends a custom message of type `msg` to the pad

*Example returns:* + * `{code: 0, message:"ok", data: {}}` + * `{code: 1, message:"padID does not exist", data: null}` From 453c68da19058965dc18c7eff421ae5575b9834d Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 8 Aug 2012 20:56:56 +0200 Subject: [PATCH 06/11] Add doc generator. --- doc/template.html | 23 ++ tools/doc/LICENSE | 18 ++ tools/doc/README.md | 76 ++++++ tools/doc/generate.js | 120 +++++++++ tools/doc/html.js | 174 +++++++++++++ tools/doc/json.js | 557 +++++++++++++++++++++++++++++++++++++++++ tools/doc/package.json | 15 ++ 7 files changed, 983 insertions(+) create mode 100644 doc/template.html create mode 100644 tools/doc/LICENSE create mode 100644 tools/doc/README.md create mode 100644 tools/doc/generate.js create mode 100644 tools/doc/html.js create mode 100644 tools/doc/json.js create mode 100644 tools/doc/package.json diff --git a/doc/template.html b/doc/template.html new file mode 100644 index 00000000..2eb93987 --- /dev/null +++ b/doc/template.html @@ -0,0 +1,23 @@ + + + + + __SECTION__ Etherpad-Lite Manual & Documentation + + + + + +
+

Table of Contents

+ __TOC__ +
+ +
+ __CONTENT__ +
+ + + diff --git a/tools/doc/LICENSE b/tools/doc/LICENSE new file mode 100644 index 00000000..e3d4e695 --- /dev/null +++ b/tools/doc/LICENSE @@ -0,0 +1,18 @@ +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +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: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. diff --git a/tools/doc/README.md b/tools/doc/README.md new file mode 100644 index 00000000..743bfaae --- /dev/null +++ b/tools/doc/README.md @@ -0,0 +1,76 @@ +Here's how the node docs work. + +Each type of heading has a description block. + + + ## module + + Stability: 3 - Stable + + description and examples. + + ### module.property + + * Type + + description of the property. + + ### module.someFunction(x, y, [z=100]) + + * `x` {String} the description of the string + * `y` {Boolean} Should I stay or should I go? + * `z` {Number} How many zebras to bring. + + A description of the function. + + ### Event: 'blerg' + + * Argument: SomeClass object. + + Modules don't usually raise events on themselves. `cluster` is the + only exception. + + ## Class: SomeClass + + description of the class. + + ### Class Method: SomeClass.classMethod(anArg) + + * `anArg` {Object} Just an argument + * `field` {String} anArg can have this field. + * `field2` {Boolean} Another field. Default: `false`. + * Return: {Boolean} `true` if it worked. + + Description of the method for humans. + + ### someClass.nextSibling() + + * Return: {SomeClass object | null} The next someClass in line. + + ### someClass.someProperty + + * String + + The indication of what someProperty is. + + ### Event: 'grelb' + + * `isBlerg` {Boolean} + + This event is emitted on instances of SomeClass, not on the module itself. + + +* Modules have (description, Properties, Functions, Classes, Examples) +* Properties have (type, description) +* Functions have (list of arguments, description) +* Classes have (description, Properties, Methods, Events) +* Events have (list of arguments, description) +* Methods have (list of arguments, description) +* Properties have (type, description) + +# CLI usage + +Run the following from the etherpad-lite root directory: +```sh +$ node tools/doc/generate doc/all.md --format=html --template=doc/template.html > out.htm +``` \ No newline at end of file diff --git a/tools/doc/generate.js b/tools/doc/generate.js new file mode 100644 index 00000000..8d52e101 --- /dev/null +++ b/tools/doc/generate.js @@ -0,0 +1,120 @@ +#!/usr/bin/env node +// Copyright Joyent, Inc. and other Node contributors. +// +// 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: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// 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. + +var marked = require('marked'); +var fs = require('fs'); +var path = require('path'); + +// parse the args. +// Don't use nopt or whatever for this. It's simple enough. + +var args = process.argv.slice(2); +var format = 'json'; +var template = null; +var inputFile = null; + +args.forEach(function (arg) { + if (!arg.match(/^\-\-/)) { + inputFile = arg; + } else if (arg.match(/^\-\-format=/)) { + format = arg.replace(/^\-\-format=/, ''); + } else if (arg.match(/^\-\-template=/)) { + template = arg.replace(/^\-\-template=/, ''); + } +}) + + +if (!inputFile) { + throw new Error('No input file specified'); +} + + +console.error('Input file = %s', inputFile); +fs.readFile(inputFile, 'utf8', function(er, input) { + if (er) throw er; + // process the input for @include lines + processIncludes(inputFile, input, next); +}); + + +var includeExpr = /^@include\s+([A-Za-z0-9-_\/]+)(?:\.)?([a-zA-Z]*)$/gmi; +var includeData = {}; +function processIncludes(inputFile, input, cb) { + var includes = input.match(includeExpr); + if (includes === null) return cb(null, input); + var errState = null; + console.error(includes); + var incCount = includes.length; + if (incCount === 0) cb(null, input); + + includes.forEach(function(include) { + var fname = include.replace(/^@include\s+/, ''); + if (!fname.match(/\.md$/)) fname += '.md'; + + if (includeData.hasOwnProperty(fname)) { + input = input.split(include).join(includeData[fname]); + incCount--; + if (incCount === 0) { + return cb(null, input); + } + } + + var fullFname = path.resolve(path.dirname(inputFile), fname); + fs.readFile(fullFname, 'utf8', function(er, inc) { + if (errState) return; + if (er) return cb(errState = er); + processIncludes(fullFname, inc, function(er, inc) { + if (errState) return; + if (er) return cb(errState = er); + incCount--; + includeData[fname] = inc; + input = input.split(include).join(includeData[fname]); + if (incCount === 0) { + return cb(null, input); + } + }); + }); + }); +} + + +function next(er, input) { + if (er) throw er; + switch (format) { + case 'json': + require('./json.js')(input, inputFile, function(er, obj) { + console.log(JSON.stringify(obj, null, 2)); + if (er) throw er; + }); + break; + + case 'html': + require('./html.js')(input, inputFile, template, function(er, html) { + if (er) throw er; + console.log(html); + }); + break; + + default: + throw new Error('Invalid format: ' + format); + } +} diff --git a/tools/doc/html.js b/tools/doc/html.js new file mode 100644 index 00000000..c52fff70 --- /dev/null +++ b/tools/doc/html.js @@ -0,0 +1,174 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// 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. + +var fs = require('fs'); +var marked = require('marked'); +var path = require('path'); + +module.exports = toHTML; + +function toHTML(input, filename, template, cb) { + var lexed = marked.lexer(input); + fs.readFile(template, 'utf8', function(er, template) { + if (er) return cb(er); + render(lexed, filename, template, cb); + }); +} + +function render(lexed, filename, template, cb) { + // get the section + var section = getSection(lexed); + + filename = path.basename(filename, '.md'); + + lexed = parseLists(lexed); + + // generate the table of contents. + // this mutates the lexed contents in-place. + buildToc(lexed, filename, function(er, toc) { + if (er) return cb(er); + + template = template.replace(/__FILENAME__/g, filename); + template = template.replace(/__SECTION__/g, section); + template = template.replace(/__VERSION__/g, process.version); + template = template.replace(/__TOC__/g, toc); + + // content has to be the last thing we do with + // the lexed tokens, because it's destructive. + content = marked.parser(lexed); + template = template.replace(/__CONTENT__/g, content); + + cb(null, template); + }); +} + + +// just update the list item text in-place. +// lists that come right after a heading are what we're after. +function parseLists(input) { + var state = null; + var depth = 0; + var output = []; + output.links = input.links; + input.forEach(function(tok) { + if (state === null) { + if (tok.type === 'heading') { + state = 'AFTERHEADING'; + } + output.push(tok); + return; + } + if (state === 'AFTERHEADING') { + if (tok.type === 'list_start') { + state = 'LIST'; + if (depth === 0) { + output.push({ type:'html', text: '
' }); + } + depth++; + output.push(tok); + return; + } + state = null; + output.push(tok); + return; + } + if (state === 'LIST') { + if (tok.type === 'list_start') { + depth++; + output.push(tok); + return; + } + if (tok.type === 'list_end') { + depth--; + if (depth === 0) { + state = null; + output.push({ type:'html', text: '
' }); + } + output.push(tok); + return; + } + if (tok.text) { + tok.text = parseListItem(tok.text); + } + } + output.push(tok); + }); + + return output; +} + + +function parseListItem(text) { + text = text.replace(/\{([^\}]+)\}/, '$1'); + //XXX maybe put more stuff here? + return text; +} + + +// section is just the first heading +function getSection(lexed) { + var section = ''; + for (var i = 0, l = lexed.length; i < l; i++) { + var tok = lexed[i]; + if (tok.type === 'heading') return tok.text; + } + return ''; +} + + +function buildToc(lexed, filename, cb) { + var indent = 0; + var toc = []; + var depth = 0; + lexed.forEach(function(tok) { + if (tok.type !== 'heading') return; + if (tok.depth - depth > 1) { + return cb(new Error('Inappropriate heading level\n' + + JSON.stringify(tok))); + } + + depth = tok.depth; + var id = getId(filename + '_' + tok.text.trim()); + toc.push(new Array((depth - 1) * 2 + 1).join(' ') + + '* ' + + tok.text + ''); + tok.text += '#'; + }); + + toc = marked.parse(toc.join('\n')); + cb(null, toc); +} + +var idCounters = {}; +function getId(text) { + text = text.toLowerCase(); + text = text.replace(/[^a-z0-9]+/g, '_'); + text = text.replace(/^_+|_+$/, ''); + text = text.replace(/^([^a-z])/, '_$1'); + if (idCounters.hasOwnProperty(text)) { + text += '_' + (++idCounters[text]); + } else { + idCounters[text] = 0; + } + return text; +} + diff --git a/tools/doc/json.js b/tools/doc/json.js new file mode 100644 index 00000000..2459bc26 --- /dev/null +++ b/tools/doc/json.js @@ -0,0 +1,557 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// 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. + +module.exports = doJSON; + +// Take the lexed input, and return a JSON-encoded object +// A module looks like this: https://gist.github.com/1777387 + +var marked = require('marked'); + +function doJSON(input, filename, cb) { + var root = {source: filename}; + var stack = [root]; + var depth = 0; + var current = root; + var state = null; + var lexed = marked.lexer(input); + lexed.forEach(function (tok) { + var type = tok.type; + var text = tok.text; + + // + // This is for cases where the markdown semantic structure is lacking. + if (type === 'paragraph' || type === 'html') { + var metaExpr = /\n*/g; + text = text.replace(metaExpr, function(_0, k, v) { + current[k.trim()] = v.trim(); + return ''; + }); + text = text.trim(); + if (!text) return; + } + + if (type === 'heading' && + !text.trim().match(/^example/i)) { + if (tok.depth - depth > 1) { + return cb(new Error('Inappropriate heading level\n'+ + JSON.stringify(tok))); + } + + // Sometimes we have two headings with a single + // blob of description. Treat as a clone. + if (current && + state === 'AFTERHEADING' && + depth === tok.depth) { + var clone = current; + current = newSection(tok); + current.clone = clone; + // don't keep it around on the stack. + stack.pop(); + } else { + // if the level is greater than the current depth, + // then it's a child, so we should just leave the stack + // as it is. + // However, if it's a sibling or higher, then it implies + // the closure of the other sections that came before. + // root is always considered the level=0 section, + // and the lowest heading is 1, so this should always + // result in having a valid parent node. + var d = tok.depth; + while (d <= depth) { + finishSection(stack.pop(), stack[stack.length - 1]); + d++; + } + current = newSection(tok); + } + + depth = tok.depth; + stack.push(current); + state = 'AFTERHEADING'; + return; + } // heading + + // Immediately after a heading, we can expect the following + // + // { type: 'code', text: 'Stability: ...' }, + // + // a list: starting with list_start, ending with list_end, + // maybe containing other nested lists in each item. + // + // If one of these isnt' found, then anything that comes between + // here and the next heading should be parsed as the desc. + var stability + if (state === 'AFTERHEADING') { + if (type === 'code' && + (stability = text.match(/^Stability: ([0-5])(?:\s*-\s*)?(.*)$/))) { + current.stability = parseInt(stability[1], 10); + current.stabilityText = stability[2].trim(); + return; + } else if (type === 'list_start' && !tok.ordered) { + state = 'AFTERHEADING_LIST'; + current.list = current.list || []; + current.list.push(tok); + current.list.level = 1; + } else { + current.desc = current.desc || []; + if (!Array.isArray(current.desc)) { + current.shortDesc = current.desc; + current.desc = []; + } + current.desc.push(tok); + state = 'DESC'; + } + return; + } + + if (state === 'AFTERHEADING_LIST') { + current.list.push(tok); + if (type === 'list_start') { + current.list.level++; + } else if (type === 'list_end') { + current.list.level--; + } + if (current.list.level === 0) { + state = 'AFTERHEADING'; + processList(current); + } + return; + } + + current.desc = current.desc || []; + current.desc.push(tok); + + }); + + // finish any sections left open + while (root !== (current = stack.pop())) { + finishSection(current, stack[stack.length - 1]); + } + + return cb(null, root) +} + + +// go from something like this: +// [ { type: 'list_item_start' }, +// { type: 'text', +// text: '`settings` Object, Optional' }, +// { type: 'list_start', ordered: false }, +// { type: 'list_item_start' }, +// { type: 'text', +// text: 'exec: String, file path to worker file. Default: `__filename`' }, +// { type: 'list_item_end' }, +// { type: 'list_item_start' }, +// { type: 'text', +// text: 'args: Array, string arguments passed to worker.' }, +// { type: 'text', +// text: 'Default: `process.argv.slice(2)`' }, +// { type: 'list_item_end' }, +// { type: 'list_item_start' }, +// { type: 'text', +// text: 'silent: Boolean, whether or not to send output to parent\'s stdio.' }, +// { type: 'text', text: 'Default: `false`' }, +// { type: 'space' }, +// { type: 'list_item_end' }, +// { type: 'list_end' }, +// { type: 'list_item_end' }, +// { type: 'list_end' } ] +// to something like: +// [ { name: 'settings', +// type: 'object', +// optional: true, +// settings: +// [ { name: 'exec', +// type: 'string', +// desc: 'file path to worker file', +// default: '__filename' }, +// { name: 'args', +// type: 'array', +// default: 'process.argv.slice(2)', +// desc: 'string arguments passed to worker.' }, +// { name: 'silent', +// type: 'boolean', +// desc: 'whether or not to send output to parent\'s stdio.', +// default: 'false' } ] } ] + +function processList(section) { + var list = section.list; + var values = []; + var current; + var stack = []; + + // for now, *just* build the heirarchical list + list.forEach(function(tok) { + var type = tok.type; + if (type === 'space') return; + if (type === 'list_item_start') { + if (!current) { + var n = {}; + values.push(n); + current = n; + } else { + current.options = current.options || []; + stack.push(current); + var n = {}; + current.options.push(n); + current = n; + } + return; + } else if (type === 'list_item_end') { + if (!current) { + throw new Error('invalid list - end without current item\n' + + JSON.stringify(tok) + '\n' + + JSON.stringify(list)); + } + current = stack.pop(); + } else if (type === 'text') { + if (!current) { + throw new Error('invalid list - text without current item\n' + + JSON.stringify(tok) + '\n' + + JSON.stringify(list)); + } + current.textRaw = current.textRaw || ''; + current.textRaw += tok.text + ' '; + } + }); + + // shove the name in there for properties, since they are always + // just going to be the value etc. + if (section.type === 'property' && values[0]) { + values[0].textRaw = '`' + section.name + '` ' + values[0].textRaw; + } + + // now pull the actual values out of the text bits. + values.forEach(parseListItem); + + // Now figure out what this list actually means. + // depending on the section type, the list could be different things. + + switch (section.type) { + case 'ctor': + case 'classMethod': + case 'method': + // each item is an argument, unless the name is 'return', + // in which case it's the return value. + section.signatures = section.signatures || []; + var sig = {} + section.signatures.push(sig); + sig.params = values.filter(function(v) { + if (v.name === 'return') { + sig.return = v; + return false; + } + return true; + }); + parseSignature(section.textRaw, sig); + break; + + case 'property': + // there should be only one item, which is the value. + // copy the data up to the section. + var value = values[0] || {}; + delete value.name; + section.typeof = value.type; + delete value.type; + Object.keys(value).forEach(function(k) { + section[k] = value[k]; + }); + break; + + case 'event': + // event: each item is an argument. + section.params = values; + break; + } + + // section.listParsed = values; + delete section.list; +} + + +// textRaw = "someobject.someMethod(a, [b=100], [c])" +function parseSignature(text, sig) { + var params = text.match(paramExpr); + if (!params) return; + params = params[1]; + // the ] is irrelevant. [ indicates optionalness. + params = params.replace(/\]/g, ''); + params = params.split(/,/) + params.forEach(function(p, i, _) { + p = p.trim(); + if (!p) return; + var param = sig.params[i]; + var optional = false; + var def; + // [foo] -> optional + if (p.charAt(0) === '[') { + optional = true; + p = p.substr(1); + } + var eq = p.indexOf('='); + if (eq !== -1) { + def = p.substr(eq + 1); + p = p.substr(0, eq); + } + if (!param) { + param = sig.params[i] = { name: p }; + } + // at this point, the name should match. + if (p !== param.name) { + console.error('Warning: invalid param "%s"', p); + console.error(' > ' + JSON.stringify(param)); + console.error(' > ' + text); + } + if (optional) param.optional = true; + if (def !== undefined) param.default = def; + }); +} + + +function parseListItem(item) { + if (item.options) item.options.forEach(parseListItem); + if (!item.textRaw) return; + + // the goal here is to find the name, type, default, and optional. + // anything left over is 'desc' + var text = item.textRaw.trim(); + // text = text.replace(/^(Argument|Param)s?\s*:?\s*/i, ''); + + text = text.replace(/^, /, '').trim(); + var retExpr = /^returns?\s*:?\s*/i; + var ret = text.match(retExpr); + if (ret) { + item.name = 'return'; + text = text.replace(retExpr, ''); + } else { + var nameExpr = /^['`"]?([^'`": \{]+)['`"]?\s*:?\s*/; + var name = text.match(nameExpr); + if (name) { + item.name = name[1]; + text = text.replace(nameExpr, ''); + } + } + + text = text.trim(); + var defaultExpr = /\(default\s*[:=]?\s*['"`]?([^, '"`]*)['"`]?\)/i; + var def = text.match(defaultExpr); + if (def) { + item.default = def[1]; + text = text.replace(defaultExpr, ''); + } + + text = text.trim(); + var typeExpr = /^\{([^\}]+)\}/; + var type = text.match(typeExpr); + if (type) { + item.type = type[1]; + text = text.replace(typeExpr, ''); + } + + text = text.trim(); + var optExpr = /^Optional\.|(?:, )?Optional$/; + var optional = text.match(optExpr); + if (optional) { + item.optional = true; + text = text.replace(optExpr, ''); + } + + text = text.replace(/^\s*-\s*/, ''); + text = text.trim(); + if (text) item.desc = text; +} + + +function finishSection(section, parent) { + if (!section || !parent) { + throw new Error('Invalid finishSection call\n'+ + JSON.stringify(section) + '\n' + + JSON.stringify(parent)); + } + + if (!section.type) { + section.type = 'module'; + if (parent && (parent.type === 'misc')) { + section.type = 'misc'; + } + section.displayName = section.name; + section.name = section.name.toLowerCase() + .trim().replace(/\s+/g, '_'); + } + + if (section.desc && Array.isArray(section.desc)) { + section.desc.links = section.desc.links || []; + section.desc = marked.parser(section.desc); + } + + if (!section.list) section.list = []; + processList(section); + + // classes sometimes have various 'ctor' children + // which are actually just descriptions of a constructor + // class signature. + // Merge them into the parent. + if (section.type === 'class' && section.ctors) { + section.signatures = section.signatures || []; + var sigs = section.signatures; + section.ctors.forEach(function(ctor) { + ctor.signatures = ctor.signatures || [{}]; + ctor.signatures.forEach(function(sig) { + sig.desc = ctor.desc; + }); + sigs.push.apply(sigs, ctor.signatures); + }); + delete section.ctors; + } + + // properties are a bit special. + // their "type" is the type of object, not "property" + if (section.properties) { + section.properties.forEach(function (p) { + if (p.typeof) p.type = p.typeof; + else delete p.type; + delete p.typeof; + }); + } + + // handle clones + if (section.clone) { + var clone = section.clone; + delete section.clone; + delete clone.clone; + deepCopy(section, clone); + finishSection(clone, parent); + } + + var plur; + if (section.type.slice(-1) === 's') { + plur = section.type + 'es'; + } else if (section.type.slice(-1) === 'y') { + plur = section.type.replace(/y$/, 'ies'); + } else { + plur = section.type + 's'; + } + + // if the parent's type is 'misc', then it's just a random + // collection of stuff, like the "globals" section. + // Make the children top-level items. + if (section.type === 'misc') { + Object.keys(section).forEach(function(k) { + switch (k) { + case 'textRaw': + case 'name': + case 'type': + case 'desc': + case 'miscs': + return; + default: + if (parent.type === 'misc') { + return; + } + if (Array.isArray(k) && parent[k]) { + parent[k] = parent[k].concat(section[k]); + } else if (!parent[k]) { + parent[k] = section[k]; + } else { + // parent already has, and it's not an array. + return; + } + } + }); + } + + parent[plur] = parent[plur] || []; + parent[plur].push(section); +} + + +// Not a general purpose deep copy. +// But sufficient for these basic things. +function deepCopy(src, dest) { + Object.keys(src).filter(function(k) { + return !dest.hasOwnProperty(k); + }).forEach(function(k) { + dest[k] = deepCopy_(src[k]); + }); +} + +function deepCopy_(src) { + if (!src) return src; + if (Array.isArray(src)) { + var c = new Array(src.length); + src.forEach(function(v, i) { + c[i] = deepCopy_(v); + }); + return c; + } + if (typeof src === 'object') { + var c = {}; + Object.keys(src).forEach(function(k) { + c[k] = deepCopy_(src[k]); + }); + return c; + } + return src; +} + + +// these parse out the contents of an H# tag +var eventExpr = /^Event(?::|\s)+['"]?([^"']+).*$/i; +var classExpr = /^Class:\s*([^ ]+).*?$/i; +var propExpr = /^(?:property:?\s*)?[^\.]+\.([^ \.\(\)]+)\s*?$/i; +var braceExpr = /^(?:property:?\s*)?[^\.\[]+(\[[^\]]+\])\s*?$/i; +var classMethExpr = + /^class\s*method\s*:?[^\.]+\.([^ \.\(\)]+)\([^\)]*\)\s*?$/i; +var methExpr = + /^(?:method:?\s*)?(?:[^\.]+\.)?([^ \.\(\)]+)\([^\)]*\)\s*?$/i; +var newExpr = /^new ([A-Z][a-z]+)\([^\)]*\)\s*?$/; +var paramExpr = /\((.*)\);?$/; + +function newSection(tok) { + var section = {}; + // infer the type from the text. + var text = section.textRaw = tok.text; + if (text.match(eventExpr)) { + section.type = 'event'; + section.name = text.replace(eventExpr, '$1'); + } else if (text.match(classExpr)) { + section.type = 'class'; + section.name = text.replace(classExpr, '$1'); + } else if (text.match(braceExpr)) { + section.type = 'property'; + section.name = text.replace(braceExpr, '$1'); + } else if (text.match(propExpr)) { + section.type = 'property'; + section.name = text.replace(propExpr, '$1'); + } else if (text.match(classMethExpr)) { + section.type = 'classMethod'; + section.name = text.replace(classMethExpr, '$1'); + } else if (text.match(methExpr)) { + section.type = 'method'; + section.name = text.replace(methExpr, '$1'); + } else if (text.match(newExpr)) { + section.type = 'ctor'; + section.name = text.replace(newExpr, '$1'); + } else { + section.name = text; + } + return section; +} diff --git a/tools/doc/package.json b/tools/doc/package.json new file mode 100644 index 00000000..d87c9345 --- /dev/null +++ b/tools/doc/package.json @@ -0,0 +1,15 @@ +{ + "author": "Isaac Z. Schlueter (http://blog.izs.me/)", + "name": "node-doc-generator", + "description": "Internal tool for generating Node.js API docs", + "version": "0.0.0", + "engines": { + "node": ">=0.6.10" + }, + "dependencies": { + "marked": "~0.1.9" + }, + "devDependencies": {}, + "optionalDependencies": {}, + "bin": "./generate.js" +} From 14da09dddd684f6601738c3fce99621917a9a604 Mon Sep 17 00:00:00 2001 From: Mark Holmquist Date: Wed, 8 Aug 2012 10:42:16 -0700 Subject: [PATCH 07/11] Add in docs for the new hook I just added --- doc/api/hooks_client-side.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index 6652f757..b991d62d 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -158,4 +158,15 @@ Things in context: 4. style - the style applied to the node (probably CSS) 5. cls - the HTML class string of the node -This hook is called after the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. \ No newline at end of file +This hook is called after the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. + +### handleClientMessage_`name` +Called from: `src/static/js/collab_client.js` + +Things in context: + +1. payload - the data that got sent with the message (use it for custom message content) + +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. From 75c07ad569e08a8f90766d52f64f3f91da12f5c8 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 11 Aug 2012 13:18:17 +0200 Subject: [PATCH 08/11] Check in tools/doc/node_modules --- tools/doc/node_modules/.bin/marked | 1 + tools/doc/node_modules/marked/.npmignore | 2 + tools/doc/node_modules/marked/LICENSE | 19 + tools/doc/node_modules/marked/Makefile | 9 + tools/doc/node_modules/marked/README.md | 135 ++++ tools/doc/node_modules/marked/bin/marked | 115 ++++ tools/doc/node_modules/marked/index.js | 1 + tools/doc/node_modules/marked/lib/marked.js | 662 ++++++++++++++++++++ tools/doc/node_modules/marked/man/marked.1 | 39 ++ tools/doc/node_modules/marked/package.json | 15 + 10 files changed, 998 insertions(+) create mode 100644 tools/doc/node_modules/.bin/marked create mode 100644 tools/doc/node_modules/marked/.npmignore create mode 100644 tools/doc/node_modules/marked/LICENSE create mode 100644 tools/doc/node_modules/marked/Makefile create mode 100644 tools/doc/node_modules/marked/README.md create mode 100644 tools/doc/node_modules/marked/bin/marked create mode 100644 tools/doc/node_modules/marked/index.js create mode 100644 tools/doc/node_modules/marked/lib/marked.js create mode 100644 tools/doc/node_modules/marked/man/marked.1 create mode 100644 tools/doc/node_modules/marked/package.json diff --git a/tools/doc/node_modules/.bin/marked b/tools/doc/node_modules/.bin/marked new file mode 100644 index 00000000..a8d872e9 --- /dev/null +++ b/tools/doc/node_modules/.bin/marked @@ -0,0 +1 @@ +../marked/bin/marked \ No newline at end of file diff --git a/tools/doc/node_modules/marked/.npmignore b/tools/doc/node_modules/marked/.npmignore new file mode 100644 index 00000000..3fb773c0 --- /dev/null +++ b/tools/doc/node_modules/marked/.npmignore @@ -0,0 +1,2 @@ +.git* +test/ diff --git a/tools/doc/node_modules/marked/LICENSE b/tools/doc/node_modules/marked/LICENSE new file mode 100644 index 00000000..40597477 --- /dev/null +++ b/tools/doc/node_modules/marked/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2012, Christopher Jeffrey (https://github.com/chjj/) + +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: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. diff --git a/tools/doc/node_modules/marked/Makefile b/tools/doc/node_modules/marked/Makefile new file mode 100644 index 00000000..76904000 --- /dev/null +++ b/tools/doc/node_modules/marked/Makefile @@ -0,0 +1,9 @@ +all: + @cp lib/marked.js marked.js + @uglifyjs -o marked.min.js marked.js + +clean: + @rm marked.js + @rm marked.min.js + +.PHONY: clean all diff --git a/tools/doc/node_modules/marked/README.md b/tools/doc/node_modules/marked/README.md new file mode 100644 index 00000000..1a0747c0 --- /dev/null +++ b/tools/doc/node_modules/marked/README.md @@ -0,0 +1,135 @@ +# marked + +A full-featured markdown parser and compiler. +Built for speed. + +## Benchmarks + +node v0.4.x + +``` bash +$ node test --bench +marked completed in 12071ms. +showdown (reuse converter) completed in 27387ms. +showdown (new converter) completed in 75617ms. +markdown-js completed in 70069ms. +``` + +node v0.6.x + +``` bash +$ node test --bench +marked completed in 6485ms. +marked (with gfm) completed in 7466ms. +discount completed in 7169ms. +showdown (reuse converter) completed in 15937ms. +showdown (new converter) completed in 18279ms. +markdown-js completed in 23572ms. +``` + +__Marked is now faster than Discount, which is written in C.__ + +For those feeling skeptical: These benchmarks run the entire markdown test suite +1000 times. The test suite tests every feature. It doesn't cater to specific +aspects. + +Benchmarks for other engines to come (?). + +## Install + +``` bash +$ npm install marked +``` + +## Another javascript markdown parser + +The point of marked was to create a markdown compiler where it was possible to +frequently parse huge chunks of markdown without having to worry about +caching the compiled output somehow...or blocking for an unnecesarily long time. + +marked is very concise and still implements all markdown features. It is also +now fully compatible with the client-side. + +marked more or less passes the official markdown test suite in its +entirety. This is important because a surprising number of markdown compilers +cannot pass more than a few tests. It was very difficult to get marked as +compliant as it is. It could have cut corners in several areas for the sake +of performance, but did not in order to be exactly what you expect in terms +of a markdown rendering. In fact, this is why marked could be considered at a +disadvantage in the benchmarks above. + +Along with implementing every markdown feature, marked also implements +[GFM features](http://github.github.com/github-flavored-markdown/). + +## Usage + +``` js +var marked = require('marked'); +console.log(marked('i am using __markdown__.')); +``` + +You also have direct access to the lexer and parser if you so desire. + +``` js +var tokens = marked.lexer(str); +console.log(marked.parser(tokens)); +``` + +``` bash +$ node +> require('marked').lexer('> i am using marked.') +[ { type: 'blockquote_start' }, + { type: 'text', text: ' i am using marked.' }, + { type: 'blockquote_end' }, + links: {} ] +``` + +## CLI + +``` bash +$ marked -o hello.html +hello world +^D +$ cat hello.html +

hello world

+``` + +## Syntax Highlighting + +Marked has an interface that allows for a syntax highlighter to highlight code +blocks before they're output. + +Example implementation: + +``` js +var highlight = require('my-syntax-highlighter') + , marked_ = require('marked'); + +var marked = function(text) { + var tokens = marked_.lexer(text) + , l = tokens.length + , i = 0 + , token; + + for (; i < l; i++) { + token = tokens[i]; + if (token.type === 'code') { + token.text = highlight(token.text, token.lang); + // marked should not escape this + token.escaped = true; + } + } + + text = marked_.parser(tokens); + + return text; +}; + +module.exports = marked; +``` + +## License + +Copyright (c) 2011-2012, Christopher Jeffrey. (MIT License) + +See LICENSE for more info. diff --git a/tools/doc/node_modules/marked/bin/marked b/tools/doc/node_modules/marked/bin/marked new file mode 100644 index 00000000..7d00504e --- /dev/null +++ b/tools/doc/node_modules/marked/bin/marked @@ -0,0 +1,115 @@ +#!/usr/bin/env node + +/** + * Marked CLI + * Copyright (c) 2011-2012, Christopher Jeffrey (MIT License) + */ + +var fs = require('fs') + , util = require('util') + , marked = require('../'); + +/** + * Man Page + */ + +var help = function() { + var spawn = require('child_process').spawn; + + var options = { + cwd: process.cwd(), + env: process.env, + setsid: false, + customFds: [0, 1, 2] + }; + + spawn('man', + [__dirname + '/../man/marked.1'], + options); +}; + +/** + * Main + */ + +var main = function(argv) { + var files = [] + , data = '' + , input + , output + , arg + , tokens; + + var getarg = function() { + var arg = argv.shift(); + arg = arg.split('='); + if (arg.length > 1) { + argv.unshift(arg.slice(1).join('=')); + } + return arg[0]; + }; + + while (argv.length) { + arg = getarg(); + switch (arg) { + case '-o': + case '--output': + output = argv.shift(); + break; + case '-i': + case '--input': + input = argv.shift(); + break; + case '-t': + case '--tokens': + tokens = true; + break; + case '-h': + case '--help': + return help(); + default: + files.push(arg); + break; + } + } + + if (!input) { + if (files.length <= 2) { + var stdin = process.stdin; + + stdin.setEncoding('utf8'); + stdin.resume(); + + stdin.on('data', function(text) { + data += text; + }); + + stdin.on('end', write); + + return; + } + input = files.pop(); + } + + data = fs.readFileSync(input, 'utf8'); + write(); + + function write() { + data = tokens + ? JSON.stringify(marked.lexer(data), null, 2) + : marked(data); + + if (!output) { + process.stdout.write(data + '\n'); + } else { + fs.writeFileSync(output, data); + } + } +}; + +if (!module.parent) { + process.title = 'marked'; + main(process.argv.slice()); +} else { + module.exports = main; +} diff --git a/tools/doc/node_modules/marked/index.js b/tools/doc/node_modules/marked/index.js new file mode 100644 index 00000000..a12f9056 --- /dev/null +++ b/tools/doc/node_modules/marked/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/marked'); diff --git a/tools/doc/node_modules/marked/lib/marked.js b/tools/doc/node_modules/marked/lib/marked.js new file mode 100644 index 00000000..c6f3d0ac --- /dev/null +++ b/tools/doc/node_modules/marked/lib/marked.js @@ -0,0 +1,662 @@ +/** + * marked - A markdown parser (https://github.com/chjj/marked) + * Copyright (c) 2011-2012, Christopher Jeffrey. (MIT Licensed) + */ + +;(function() { + +/** + * Block-Level Grammar + */ + +var block = { + newline: /^\n+/, + code: /^ {4,}[^\n]*(?:\n {4,}[^\n]*|\n)*(?:\n+|$)/, + gfm_code: /^ *``` *(\w+)? *\n([^\0]+?)\s*``` *(?:\n+|$)/, + hr: /^( *[\-*_]){3,} *(?:\n+|$)/, + heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, + lheading: /^([^\n]+)\n *(=|-){3,} *\n*/, + blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/, + list: /^( *)([*+-]|\d+\.) [^\0]+?(?:\n{2,}(?! )|\s*$)(?!\1bullet)\n*/, + html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, + def: /^ *\[([^\]]+)\]: *([^\s]+)(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + paragraph: /^([^\n]+\n?(?!body))+\n*/, + text: /^[^\n]+/ +}; + +block.list = (function() { + var list = block.list.source; + + list = list + .replace('bullet', /(?:[*+-](?!(?: *[-*]){2,})|\d+\.)/.source); + + return new RegExp(list); +})(); + +block.html = (function() { + var html = block.html.source; + + html = html + .replace('comment', //.source) + .replace('closed', /<(tag)[^\0]+?<\/\1>/.source) + .replace('closing', /])*?>/.source) + .replace(/tag/g, tag()); + + return new RegExp(html); +})(); + +block.paragraph = (function() { + var paragraph = block.paragraph.source + , body = []; + + (function push(rule) { + rule = block[rule] ? block[rule].source : rule; + body.push(rule.replace(/(^|[^\[])\^/g, '$1')); + return push; + }) + ('gfm_code') + ('hr') + ('heading') + ('lheading') + ('blockquote') + ('<' + tag()) + ('def'); + + return new + RegExp(paragraph.replace('body', body.join('|'))); +})(); + +/** + * Block Lexer + */ + +block.lexer = function(src) { + var tokens = []; + + tokens.links = {}; + + src = src + .replace(/\r\n|\r/g, '\n') + .replace(/\t/g, ' '); + + return block.token(src, tokens, true); +}; + +block.token = function(src, tokens, top) { + var src = src.replace(/^ +$/gm, '') + , next + , loose + , cap + , item + , space + , i + , l; + + while (src) { + // newline + if (cap = block.newline.exec(src)) { + src = src.substring(cap[0].length); + if (cap[0].length > 1) { + tokens.push({ + type: 'space' + }); + } + } + + // code + if (cap = block.code.exec(src)) { + src = src.substring(cap[0].length); + cap = cap[0].replace(/^ {4}/gm, ''); + tokens.push({ + type: 'code', + text: cap.replace(/\n+$/, '') + }); + continue; + } + + // gfm_code + if (cap = block.gfm_code.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'code', + lang: cap[1], + text: cap[2] + }); + continue; + } + + // heading + if (cap = block.heading.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'heading', + depth: cap[1].length, + text: cap[2] + }); + continue; + } + + // lheading + if (cap = block.lheading.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'heading', + depth: cap[2] === '=' ? 1 : 2, + text: cap[1] + }); + continue; + } + + // hr + if (cap = block.hr.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'hr' + }); + continue; + } + + // blockquote + if (cap = block.blockquote.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'blockquote_start' + }); + + cap = cap[0].replace(/^ *> ?/gm, ''); + + // Pass `top` to keep the current + // "toplevel" state. This is exactly + // how markdown.pl works. + block.token(cap, tokens, top); + + tokens.push({ + type: 'blockquote_end' + }); + continue; + } + + // list + if (cap = block.list.exec(src)) { + src = src.substring(cap[0].length); + + tokens.push({ + type: 'list_start', + ordered: isFinite(cap[2]) + }); + + // Get each top-level item. + cap = cap[0].match( + /^( *)([*+-]|\d+\.)[^\n]*(?:\n(?!\1(?:[*+-]|\d+\.))[^\n]*)*/gm + ); + + next = false; + l = cap.length; + i = 0; + + for (; i < l; i++) { + item = cap[i]; + + // Remove the list item's bullet + // so it is seen as the next token. + space = item.length; + item = item.replace(/^ *([*+-]|\d+\.) */, ''); + + // Outdent whatever the + // list item contains. Hacky. + if (~item.indexOf('\n ')) { + space -= item.length; + item = item.replace(new RegExp('^ {1,' + space + '}', 'gm'), ''); + } + + // Determine whether item is loose or not. + // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ + // for discount behavior. + loose = next || /\n\n(?!\s*$)/.test(item); + if (i !== l - 1) { + next = item[item.length-1] === '\n'; + if (!loose) loose = next; + } + + tokens.push({ + type: loose + ? 'loose_item_start' + : 'list_item_start' + }); + + // Recurse. + block.token(item, tokens); + + tokens.push({ + type: 'list_item_end' + }); + } + + tokens.push({ + type: 'list_end' + }); + + continue; + } + + // html + if (cap = block.html.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'html', + text: cap[0] + }); + continue; + } + + // def + if (top && (cap = block.def.exec(src))) { + src = src.substring(cap[0].length); + tokens.links[cap[1].toLowerCase()] = { + href: cap[2], + title: cap[3] + }; + continue; + } + + // top-level paragraph + if (top && (cap = block.paragraph.exec(src))) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'paragraph', + text: cap[0] + }); + continue; + } + + // text + if (cap = block.text.exec(src)) { + // Top-level should never reach here. + src = src.substring(cap[0].length); + tokens.push({ + type: 'text', + text: cap[0] + }); + continue; + } + } + + return tokens; +}; + +/** + * Inline Processing + */ + +var inline = { + escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, + autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, + gfm_autolink: /^(\w+:\/\/[^\s]+[^.,:;"')\]\s])/, + tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, + link: /^!?\[((?:\[[^\]]*\]|[^\[\]]|\[|\](?=[^[\]]*\]))*)\]\(([^\)]*)\)/, + reflink: /^!?\[((?:\[[^\]]*\]|[^\[\]]|\[|\](?=[^[\]]*\]))*)\]\s*\[([^\]]*)\]/, + nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, + strong: /^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/, + em: /^\b_([^\0]+?)_\b|^\*((?:\*\*|[^\0])+?)\*(?!\*)/, + code: /^(`+)([^\0]*?[^`])\1(?!`)/, + br: /^ {2,}\n(?!\s*$)/, + text: /^[^\0]+?(?=[\\' + + text + + ''; + continue; + } + + // gfm_autolink + if (cap = inline.gfm_autolink.exec(src)) { + src = src.substring(cap[0].length); + text = escape(cap[1]); + href = text; + out += '' + + text + + ''; + continue; + } + + // tag + if (cap = inline.tag.exec(src)) { + src = src.substring(cap[0].length); + out += cap[0]; + continue; + } + + // link + if (cap = inline.link.exec(src)) { + src = src.substring(cap[0].length); + text = /^\s*?(?:\s+"([^\n]+)")?\s*$/.exec(cap[2]); + if (!text) { + out += cap[0][0]; + src = cap[0].substring(1) + src; + continue; + } + out += outputLink(cap, { + href: text[1], + title: text[2] + }); + continue; + } + + // reflink, nolink + if ((cap = inline.reflink.exec(src)) + || (cap = inline.nolink.exec(src))) { + src = src.substring(cap[0].length); + link = (cap[2] || cap[1]).replace(/\s+/g, ' '); + link = links[link.toLowerCase()]; + if (!link || !link.href) { + out += cap[0][0]; + src = cap[0].substring(1) + src; + continue; + } + out += outputLink(cap, link); + continue; + } + + // strong + if (cap = inline.strong.exec(src)) { + src = src.substring(cap[0].length); + out += '' + + inline.lexer(cap[2] || cap[1]) + + ''; + continue; + } + + // em + if (cap = inline.em.exec(src)) { + src = src.substring(cap[0].length); + out += '' + + inline.lexer(cap[2] || cap[1]) + + ''; + continue; + } + + // code + if (cap = inline.code.exec(src)) { + src = src.substring(cap[0].length); + out += '' + + escape(cap[2], true) + + ''; + continue; + } + + // br + if (cap = inline.br.exec(src)) { + src = src.substring(cap[0].length); + out += '
'; + continue; + } + + // text + if (cap = inline.text.exec(src)) { + src = src.substring(cap[0].length); + out += escape(cap[0]); + continue; + } + } + + return out; +}; + +var outputLink = function(cap, link) { + if (cap[0][0] !== '!') { + return '' + + inline.lexer(cap[1]) + + ''; + } else { + return ''
+      + escape(cap[1])
+      + ''; + } +}; + +/** + * Parsing + */ + +var tokens + , token; + +var next = function() { + return token = tokens.pop(); +}; + +var tok = function() { + switch (token.type) { + case 'space': { + return ''; + } + case 'hr': { + return '
\n'; + } + case 'heading': { + return '' + + inline.lexer(token.text) + + '\n'; + } + case 'code': { + return '
'
+        + (token.escaped
+        ? token.text
+        : escape(token.text, true))
+        + '
\n'; + } + case 'blockquote_start': { + var body = ''; + + while (next().type !== 'blockquote_end') { + body += tok(); + } + + return '
\n' + + body + + '
\n'; + } + case 'list_start': { + var type = token.ordered ? 'ol' : 'ul' + , body = ''; + + while (next().type !== 'list_end') { + body += tok(); + } + + return '<' + + type + + '>\n' + + body + + '\n'; + } + case 'list_item_start': { + var body = ''; + + while (next().type !== 'list_item_end') { + body += token.type === 'text' + ? parseText() + : tok(); + } + + return '
  • ' + + body + + '
  • \n'; + } + case 'loose_item_start': { + var body = ''; + + while (next().type !== 'list_item_end') { + body += tok(); + } + + return '
  • ' + + body + + '
  • \n'; + } + case 'html': { + return inline.lexer(token.text); + } + case 'paragraph': { + return '

    ' + + inline.lexer(token.text) + + '

    \n'; + } + case 'text': { + return '

    ' + + parseText() + + '

    \n'; + } + } +}; + +var parseText = function() { + var body = token.text + , top; + + while ((top = tokens[tokens.length-1]) + && top.type === 'text') { + body += '\n' + next().text; + } + + return inline.lexer(body); +}; + +var parse = function(src) { + tokens = src.reverse(); + + var out = ''; + while (next()) { + out += tok(); + } + + tokens = null; + token = null; + + return out; +}; + +/** + * Helpers + */ + +var escape = function(html, encode) { + return html + .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +}; + +var mangle = function(text) { + var out = '' + , l = text.length + , i = 0 + , ch; + + for (; i < l; i++) { + ch = text.charCodeAt(i); + if (Math.random() > 0.5) { + ch = 'x' + ch.toString(16); + } + out += '&#' + ch + ';'; + } + + return out; +}; + +function tag() { + var tag = '(?!(?:' + + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' + + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' + + '|span|br|wbr|ins|del|img)\\b)\\w+'; + + return tag; +} + +/** + * Expose + */ + +var marked = function(src) { + return parse(block.lexer(src)); +}; + +marked.parser = parse; +marked.lexer = block.lexer; + +marked.parse = marked; + +if (typeof module !== 'undefined') { + module.exports = marked; +} else { + this.marked = marked; +} + +}).call(this); diff --git a/tools/doc/node_modules/marked/man/marked.1 b/tools/doc/node_modules/marked/man/marked.1 new file mode 100644 index 00000000..21453339 --- /dev/null +++ b/tools/doc/node_modules/marked/man/marked.1 @@ -0,0 +1,39 @@ +.ds q \N'34' +.TH marked 1 +.SH NAME +marked \- a javascript markdown parser +.SH SYNOPSIS +.nf +.B marked [\-o output] [\-i input] [\-th] +.fi +.SH DESCRIPTION +.B marked +is a full-featured javascript markdown parser, built for speed. It also includes +multiple GFM features. +.SH OPTIONS +.TP +.BI \-o,\ \-\-output\ [output] +Specify file output. If none is specified, write to stdout. +.TP +.BI \-i,\ \-\-input\ [input] +Specify file input, otherwise use last argument as input file. If no input file +is specified, read from stdin. +.TP +.BI \-t,\ \-\-tokens +Output a token stream instead of html. +.TP +.BI \-h,\ \-\-help +Display help information. +.SH EXAMPLES +.TP +cat in.md | marked > out.html +.TP +echo "hello *world*" | marked +.TP +marked -o out.html in.md +.TP +marked --output="hello world.html" -i in.md +.SH BUGS +Please report any bugs to https://github.com/chjj/marked. +.SH LICENSE +Copyright (c) 2011-2012, Christopher Jeffrey (MIT License) diff --git a/tools/doc/node_modules/marked/package.json b/tools/doc/node_modules/marked/package.json new file mode 100644 index 00000000..f47a9e12 --- /dev/null +++ b/tools/doc/node_modules/marked/package.json @@ -0,0 +1,15 @@ +{ + "name": "marked", + "description": "A markdown parser built for speed", + "author": "Christopher Jeffrey", + "version": "0.1.9", + "main": "./lib/marked.js", + "bin": "./bin/marked", + "man": "./man/marked.1", + "preferGlobal": false, + "repository": "git://github.com/chjj/marked.git", + "homepage": "https://github.com/chjj/marked", + "bugs": "http://github.com/chjj/marked/issues", + "keywords": [ "markdown", "markup", "html" ], + "tags": [ "markdown", "markup", "html" ] +} From 98cdb96ac31b7ffc0903ebf7651cef9193bc0230 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 12 Aug 2012 21:29:21 +0200 Subject: [PATCH 09/11] Document 'documentReady' client hook. --- doc/api/hooks_client-side.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index b991d62d..03703e6b 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -6,6 +6,15 @@ 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 + +Things in context: + +nothing + +This hook proxies the functionality of jQuery's `$(document).ready` event. + ### aceDomLineProcessLineAttributes Called from: src/static/js/domline.js From 17375b2eed613aecd176e2ac00226113cb1dec73 Mon Sep 17 00:00:00 2001 From: Mark Holmquist Date: Mon, 13 Aug 2012 19:03:06 -0700 Subject: [PATCH 10/11] Fix doc format, add in makefile for docs OK, first up, SOMEBODY *cough*analphabet*cough* screwed up the docs by making them all use the wrong heading level. Not cool, guy. I had to change them so they would compile right. But anyway, now the docs will build into sexy-looking HTML and will shortly be hosted on marktraceur.info. Fixed the makefile to work properly. Run: * `make clean` for removing old doc-build(s) * `make docs` for running new doc-build(s) --- Makefile | 13 ++++++ doc/api/changeset_library.md | 16 +++---- doc/api/editorInfo.md | 84 ++++++++++++++++++------------------ doc/api/embed_parameters.md | 18 ++++---- doc/api/hooks_client-side.md | 34 +++++++-------- doc/api/hooks_server-side.md | 26 +++++------ doc/api/http_api.md | 38 ++++++++-------- 7 files changed, 121 insertions(+), 108 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..01f30701 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +doc_dirs = doc $(wildcard doc/*/) +outdoc_dirs = out $(addprefix out/,$(doc_dirs)) +doc_sources = $(wildcard doc/*/*.md) $(wildcard doc/*.md) +outdoc_files = $(addprefix out/,$(doc_sources:.md=.html)) + +docs: $(outdoc_files) + +out/doc/%.html: doc/%.md + mkdir -p $(@D) + node tools/doc/generate.js --format=html --template=doc/template.html $< > $@ + +clean: + rm -rf out/ diff --git a/doc/api/changeset_library.md b/doc/api/changeset_library.md index 498c2901..2dce55aa 100644 --- a/doc/api/changeset_library.md +++ b/doc/api/changeset_library.md @@ -1,4 +1,4 @@ -## Changeset Library +# Changeset Library ``` "Z:z>1|2=m=b*0|1+1$\n" @@ -8,7 +8,7 @@ This is a Changeset. Its just a string and its very difficult to read in this fo A changeset describes the diff between two revisions of the document. The Browser sends changesets to the server and the server sends them to the clients to update them. This Changesets gets also saved into the history of a pad. Which allows us to go back to every revision from the past. -### Changeset.unpack(changeset) +## Changeset.unpack(changeset) * `changeset` {String} @@ -23,7 +23,7 @@ This functions returns an object representaion of the changeset, similar to this * `ops` {String} the actual changes, introduced by this changeset. * `charBank` {String} All characters that are added by this changeset. -### Changeset.opIterator(ops) +## Changeset.opIterator(ops) * `ops` {String} The operators, returned by `Changeset.unpack()` @@ -31,7 +31,7 @@ Returns an operator iterator. This iterator allows us to iterate over all operat You can iterate with an opIterator using its `next()` and `hasNext()` methods. Next returns the `next()` operator object and `hasNext()` indicates, whether there are any operators left. -### The Operator object +## The Operator object There are 3 types of operators: `+`,`-` and `=`. These operators describe different changes to the document, beginning with the first character of the document. A `=` operator doesn't change the text, but it may add or remove text attributes. A `-` operator removes text. And a `+` Operator adds text and optionally adds some attributes to it. * `opcode` {String} the operator type @@ -39,7 +39,7 @@ There are 3 types of operators: `+`,`-` and `=`. These operators describe differ * `lines` {Number} the number of lines changed by this operator. * `attribs` {attribs} attributes set on this text. -#### Example +### Example ``` { opcode: '+', chars: 1, @@ -47,7 +47,7 @@ There are 3 types of operators: `+`,`-` and `=`. These operators describe differ attribs: '*0' } ``` -### APool +## APool ``` > var AttributePoolFactory = require("./utils/AttributePoolFactory"); @@ -97,7 +97,7 @@ We used the fromJsonable function to fill the empty apool with values. the fromJ Simple example of how to get the key value pair for the attribute 1 -### AText +## AText ``` > var atext = {"text":"bold text\nitalic text\nnormal text\n\n","attribs":"*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2"}; @@ -148,4 +148,4 @@ This is an atext. An atext has two parts: text and attribs. The text is just the The attribs are again a bunch of operators like .ops in the changeset was. But these operators are only + operators. They describe which part of the text has which attributes -For more information see /doc/easysync/easysync-notes.txt in the source. \ No newline at end of file +For more information see /doc/easysync/easysync-notes.txt in the source. diff --git a/doc/api/editorInfo.md b/doc/api/editorInfo.md index 38902bb5..e4322e9e 100644 --- a/doc/api/editorInfo.md +++ b/doc/api/editorInfo.md @@ -1,47 +1,47 @@ -## editorInfo +# editorInfo -### editorInfo.ace_replaceRange(start, end, text) +## editorInfo.ace_replaceRange(start, end, text) This function replaces a range (from `start` to `end`) with `text`. -### editorInfo.ace_getRep() +## editorInfo.ace_getRep() Returns the `rep` object. -### editorInfo.ace_getAuthor() -### editorInfo.ace_inCallStack() -### editorInfo.ace_inCallStackIfNecessary(?) -### editorInfo.ace_focus(?) -### editorInfo.ace_importText(?) -### editorInfo.ace_importAText(?) -### editorInfo.ace_exportText(?) -### editorInfo.ace_editorChangedSize(?) -### editorInfo.ace_setOnKeyPress(?) -### editorInfo.ace_setOnKeyDown(?) -### editorInfo.ace_setNotifyDirty(?) -### editorInfo.ace_dispose(?) -### editorInfo.ace_getFormattedCode(?) -### editorInfo.ace_setEditable(bool) -### editorInfo.ace_execCommand(?) -### editorInfo.ace_callWithAce(fn, callStack, normalize) -### editorInfo.ace_setProperty(key, value) -### editorInfo.ace_setBaseText(txt) -### editorInfo.ace_setBaseAttributedText(atxt, apoolJsonObj) -### editorInfo.ace_applyChangesToBase(c, optAuthor, apoolJsonObj) -### editorInfo.ace_prepareUserChangeset() -### editorInfo.ace_applyPreparedChangesetToBase() -### editorInfo.ace_setUserChangeNotificationCallback(f) -### editorInfo.ace_setAuthorInfo(author, info) -### editorInfo.ace_setAuthorSelectionRange(author, start, end) -### editorInfo.ace_getUnhandledErrors() -### editorInfo.ace_getDebugProperty(prop) -### editorInfo.ace_fastIncorp(?) -### editorInfo.ace_isCaret(?) -### editorInfo.ace_getLineAndCharForPoint(?) -### editorInfo.ace_performDocumentApplyAttributesToCharRange(?) -### editorInfo.ace_setAttributeOnSelection(?) -### editorInfo.ace_toggleAttributeOnSelection(?) -### editorInfo.ace_performSelectionChange(?) -### editorInfo.ace_doIndentOutdent(?) -### editorInfo.ace_doUndoRedo(?) -### editorInfo.ace_doInsertUnorderedList(?) -### editorInfo.ace_doInsertOrderedList(?) -### editorInfo.ace_performDocumentApplyAttributesToRange() \ No newline at end of file +## editorInfo.ace_getAuthor() +## editorInfo.ace_inCallStack() +## editorInfo.ace_inCallStackIfNecessary(?) +## editorInfo.ace_focus(?) +## editorInfo.ace_importText(?) +## editorInfo.ace_importAText(?) +## editorInfo.ace_exportText(?) +## editorInfo.ace_editorChangedSize(?) +## editorInfo.ace_setOnKeyPress(?) +## editorInfo.ace_setOnKeyDown(?) +## editorInfo.ace_setNotifyDirty(?) +## editorInfo.ace_dispose(?) +## editorInfo.ace_getFormattedCode(?) +## editorInfo.ace_setEditable(bool) +## editorInfo.ace_execCommand(?) +## editorInfo.ace_callWithAce(fn, callStack, normalize) +## editorInfo.ace_setProperty(key, value) +## editorInfo.ace_setBaseText(txt) +## editorInfo.ace_setBaseAttributedText(atxt, apoolJsonObj) +## editorInfo.ace_applyChangesToBase(c, optAuthor, apoolJsonObj) +## editorInfo.ace_prepareUserChangeset() +## editorInfo.ace_applyPreparedChangesetToBase() +## editorInfo.ace_setUserChangeNotificationCallback(f) +## editorInfo.ace_setAuthorInfo(author, info) +## editorInfo.ace_setAuthorSelectionRange(author, start, end) +## editorInfo.ace_getUnhandledErrors() +## editorInfo.ace_getDebugProperty(prop) +## editorInfo.ace_fastIncorp(?) +## editorInfo.ace_isCaret(?) +## editorInfo.ace_getLineAndCharForPoint(?) +## editorInfo.ace_performDocumentApplyAttributesToCharRange(?) +## editorInfo.ace_setAttributeOnSelection(?) +## editorInfo.ace_toggleAttributeOnSelection(?) +## editorInfo.ace_performSelectionChange(?) +## editorInfo.ace_doIndentOutdent(?) +## editorInfo.ace_doUndoRedo(?) +## editorInfo.ace_doInsertUnorderedList(?) +## editorInfo.ace_doInsertOrderedList(?) +## editorInfo.ace_performDocumentApplyAttributesToRange() diff --git a/doc/api/embed_parameters.md b/doc/api/embed_parameters.md index e8f9eaf7..58730566 100644 --- a/doc/api/embed_parameters.md +++ b/doc/api/embed_parameters.md @@ -1,4 +1,4 @@ -## Embed parameters +# Embed parameters You can easily embed your etherpad-lite into any webpage by using iframes. You can configure the embedded pad using embed paramters. Example: @@ -9,39 +9,39 @@ Cut and paste the following code into any webpage to embed a pad. The parameters ``` -### showLineNumbers +## showLineNumbers * Boolean Default: true -### showControls +## showControls * Boolean Default: true -### showChat +## showChat * Boolean Default: true -### useMonospaceFont +## useMonospaceFont * Boolean Default: false -### userName +## userName * String Default: "unnamed" Example: `userName=Etherpad%20User` -### noColors +## noColors * Boolean Default: false -### alwaysShowChat +## alwaysShowChat * Boolean -Default: false \ No newline at end of file +Default: false diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index 03703e6b..50ec3f98 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -1,4 +1,4 @@ -## Client-side hooks +# 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: @@ -6,7 +6,7 @@ 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 +## documentReady Called from: src/templates/pad.html Things in context: @@ -15,7 +15,7 @@ nothing This hook proxies the functionality of jQuery's `$(document).ready` event. -### aceDomLineProcessLineAttributes +## aceDomLineProcessLineAttributes Called from: src/static/js/domline.js Things in context: @@ -31,7 +31,7 @@ The return value of this hook should have the following structure: The preHtml and postHtml values will be added to the HTML display of the element, and if processedMarker is true, the engine won't try to process it any more. -### aceCreateDomLine +## aceCreateDomLine Called from: src/static/js/domline.js Things in context: @@ -47,7 +47,7 @@ The return value of this hook should have the following structure: extraOpenTags and extraCloseTags will be added before and after the element in question, and cls will be the new class of the element going forward. -### acePostWriteDomLineHTML +## acePostWriteDomLineHTML Called from: src/static/js/domline.js Things in context: @@ -56,7 +56,7 @@ Things in context: This hook is for right after a node has been fully formatted and written to the page. -### aceAttribsToClasses +## aceAttribsToClasses Called from: src/static/js/linestylefilter.js Things in context: @@ -69,7 +69,7 @@ This hook is called during the attribute processing procedure, and should be use The return value for this function should be a list of classes, which will then be parsed into a valid class string. -### aceGetFilterStack +## aceGetFilterStack Called from: src/static/js/linestylefilter.js Things in context: @@ -79,14 +79,14 @@ Things in context: This hook is called to apply custom regular expression filters to a set of styles. The one example available is the ep_linkify plugin, which adds internal links. They use it to find the telltale `[[ ]]` syntax that signifies internal links, and finding that syntax, they add in the internalHref attribute to be later used by the aceCreateDomLine hook (documented above). -### aceEditorCSS +## aceEditorCSS Called from: src/static/js/ace.js Things in context: None This hook is provided to allow custom CSS files to be loaded. The return value should be an array of paths relative to the plugins directory. -### aceInitInnerdocbodyHead +## aceInitInnerdocbodyHead Called from: src/static/js/ace.js Things in context: @@ -95,7 +95,7 @@ Things in context: This hook is called during the creation of the editor HTML. The array should have lines of HTML added to it, giving the plugin author a chance to add in meta, script, link, and other tags that go into the `` element of the editor HTML document. -### aceEditEvent +## aceEditEvent Called from: src/static/js/ace2_inner.js Things in context: @@ -107,14 +107,14 @@ Things in context: This hook is made available to edit the edit events that might occur when changes are made. Currently you can change the editor information, some of the meanings of the edit, and so on. You can also make internal changes (internal to your plugin) that use the information provided by the edit event. -### aceRegisterBlockElements +## aceRegisterBlockElements Called from: src/static/js/ace2_inner.js Things in context: None The return value of this hook will add elements into the "lineMarkerAttribute" category, making the aceDomLineProcessLineAttributes hook (documented below) call for those elements. -### aceInitialized +## aceInitialized Called from: src/static/js/ace2_inner.js Things in context: @@ -125,7 +125,7 @@ Things in context: This hook is for inserting further information into the ace engine, for later use in formatting hooks. -### postAceInit +## postAceInit Called from: src/static/js/pad.js Things in context: @@ -134,7 +134,7 @@ Things in context: There doesn't appear to be any example available of this particular hook being used, but it gets fired after the editor is all set up. -### userJoinOrUpdate +## userJoinOrUpdate Called from: src/static/js/pad_userlist.js Things in context: @@ -143,7 +143,7 @@ Things in context: This hook is called on the client side whenever a user joins or changes. This can be used to create notifications or an alternate user list. -### collectContentPre +## collectContentPre Called from: src/static/js/contentcollector.js Things in context: @@ -156,7 +156,7 @@ Things in context: This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. -### collectContentPost +## collectContentPost Called from: src/static/js/contentcollector.js Things in context: @@ -169,7 +169,7 @@ Things in context: This hook is called after the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. -### handleClientMessage_`name` +## handleClientMessage_`name` Called from: `src/static/js/collab_client.js` Things in context: diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 8fd5c3ba..8177696a 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -1,4 +1,4 @@ -## Server-side hooks +# Server-side hooks These hooks are called on server-side. All hooks registered to these events are called with two arguments: @@ -6,7 +6,7 @@ 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 -### pluginUninstall +## pluginUninstall Called from: src/static/js/pluginfw/installer.js Things in context: @@ -15,7 +15,7 @@ Things in context: If this hook returns an error, the callback to the uninstall function gets an error as well. This mostly seems useful for handling additional features added in based on the installation of other plugins, which is pretty cool! -### pluginInstall +## pluginInstall Called from: src/static/js/pluginfw/installer.js Things in context: @@ -24,14 +24,14 @@ Things in context: If this hook returns an error, the callback to the install function gets an error, too. This seems useful for adding in features when a particular plugin is installed. -### init_`` +## init_`` Called from: src/static/js/pluginfw/plugins.js Things in context: None This function is called after a specific plugin is initialized. This would probably be more useful than the previous two functions if you only wanted to add in features to one specific plugin. -### expressConfigure +## expressConfigure Called from: src/node/server.js Things in context: @@ -40,7 +40,7 @@ Things in context: This is a helpful hook for changing the behavior and configuration of the application. It's called right after the application gets configured. -### expressCreateServer +## expressCreateServer Called from: src/node/server.js Things in context: @@ -49,7 +49,7 @@ Things in context: This hook gets called after the application object has been created, but before it starts listening. This is similar to the expressConfigure hook, but it's not guaranteed that the application object will have all relevant configuration variables. -### eejsBlock_`` +## eejsBlock_`` Called from: src/node/eejs/index.js Things in context: @@ -60,7 +60,7 @@ 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. -### socketio +## socketio Called from: src/node/hooks/express/socketio.js Things in context: @@ -70,7 +70,7 @@ Things in context: I have no idea what this is useful for, someone else will have to add this description. -### authorize +## authorize Called from: src/node/hooks/express/webaccess.js Things in context: @@ -82,7 +82,7 @@ Things in context: This is useful for modifying the way authentication is done, especially for specific paths. -### authenticate +## authenticate Called from: src/node/hooks/express/webaccess.js Things in context: @@ -95,7 +95,7 @@ Things in context: This is useful for modifying the way authentication is done. -### authFailure +## authFailure Called from: src/node/hooks/express/webaccess.js Things in context: @@ -106,7 +106,7 @@ Things in context: This is useful for modifying the way authentication is done. -### handleMessage +## handleMessage Called from: src/node/handler/PadMessageHandler.js Things in context: @@ -131,4 +131,4 @@ function handleMessage ( hook, context, callback ) { callback(); } }; -``` \ No newline at end of file +``` diff --git a/doc/api/http_api.md b/doc/api/http_api.md index da6e926b..d51f42c4 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -1,6 +1,6 @@ -## HTTP API +# HTTP API -### What can I do with this API? +## What can I do with this API? The API gives another web application control of the pads. The basic functions are * create/delete pads @@ -11,9 +11,9 @@ The API is designed in a way, so you can reuse your existing user system with th Take a look at [HTTP API client libraries](https://github.com/Pita/etherpad-lite/wiki/HTTP-API-client-libraries) to see if a library in your favorite language. -### Examples +## Examples -#### Example 1 +### Example 1 A portal (such as WordPress) wants to give a user access to a new pad. Let's assume the user have the internal id 7 and his name is michael. @@ -43,7 +43,7 @@ Portal starts the session for the user on the group: Portal places the cookie "sessionID" with the given value on the client and creates an iframe including the pad. -#### Example 2 +### Example 2 A portal (such as WordPress) wants to transform the contents of a pad that multiple admins edited into a blog post. @@ -58,13 +58,13 @@ Portal submits content into new blog post > Portal.AddNewBlog(content) > -### Usage +## Usage -#### Request Format +### Request Format The API is accessible via HTTP. HTTP Requests are in the format /api/$APIVERSION/$FUNCTIONNAME. Parameters are transmitted via HTTP GET. $APIVERSION is 1 -#### Response Format +### Response Format Responses are valid JSON in the following format: ```js @@ -84,11 +84,11 @@ Responses are valid JSON in the following format: * **message** a status message. Its ok if everything is fine, else it contains an error message * **data** the payload -#### Overview +### Overview ![API Overview](http://i.imgur.com/d0nWp.png) -#### Data Types +## Data Types * **groupID** a string, the unique id of a group. Format is g.16RANDOMCHARS, for example g.s8oes9dhwrvt0zif * **sessionID** a string, the unique id of a session. Format is s.16RANDOMCHARS, for example s.s8oes9dhwrvt0zif @@ -96,24 +96,24 @@ Responses are valid JSON in the following format: * **readOnlyID** a string, the unique id of an readonly relation to a pad. Format is r.16RANDOMCHARS, for example r.s8oes9dhwrvt0zif * **padID** a string, format is GROUPID$PADNAME, for example the pad test of group g.s8oes9dhwrvt0zif has padID g.s8oes9dhwrvt0zif$test -#### Authentication +### Authentication Authentication works via a token that is sent with each request as a post parameter. There is a single token per Etherpad-Lite deployment. This token will be random string, generated by Etherpad-Lite at the first start. It will be saved in APIKEY.txt in the root folder of Etherpad Lite. Only Etherpad Lite and the requesting application knows this key. Token management will not be exposed through this API. -#### Node Interoperability +### Node Interoperability All functions will also be available through a node module accessable from other node.js applications. -#### JSONP +### JSONP The API provides _JSONP_ support to allow requests from a server in a different domain. Simply add `&jsonp=?` to the API call. Example usage: http://api.jquery.com/jQuery.getJSON/ -### API Methods +## API Methods -#### Groups +### Groups Pads can belong to a group. The padID of grouppads is starting with a groupID like g.asdfasdfasdfasdf$test * **createGroup()** creates a new group

    *Example returns:* @@ -135,7 +135,7 @@ 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}` -#### Author +### Author Theses authors are bind to the attributes the users choose (color and name). * **createAuthor([name])** creates a new author

    *Example returns:* @@ -150,7 +150,7 @@ Theses authors are bind to the attributes the users choose (color and name). -> can't be deleted cause this would involve scanning all the pads where this author was -#### Session +### Session Sessions can be created between a group and an author. This allows an author to access more than one group. The sessionID will be set as a cookie to the client and is valid until a certain date. Only users with a valid session for this group, can access group pads. You can create a session after you authenticated the user at your web application, to give them access to the pads. You should save the sessionID of this session and delete it after the user logged out * **createSession(groupID, authorID, validUntil)** creates a new session. validUntil is an unix timestamp in seconds

    *Example returns:* @@ -175,7 +175,7 @@ Sessions can be created between a group and an author. This allows an author to * `{"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}}` * `{code: 1, message:"authorID does not exist", data: null}` -#### Pad Content +### Pad Content Pad content can be updated and retrieved through the API @@ -192,7 +192,7 @@ Pad content can be updated and retrieved through the API * `{code: 0, message:"ok", data: {html:"Welcome Text
    More Text"}}` * `{code: 1, message:"padID does not exist", data: null}` -#### Pad +### Pad Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security manager controls access of them and its forbidden for normal pads to include a $ in the name. * **createPad(padID [, text])** creates a new (non-group) pad. Note that if you need to create a group Pad, you should call **createGroupPad**.

    *Example returns:* From 1bb8844f423dbf2f0770a6a8dd82d0fdbbc53e8a Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 15 Aug 2012 10:10:55 +0200 Subject: [PATCH 11/11] Document loadSettings hook. --- doc/api/hooks_server-side.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 8177696a..518e1213 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -6,6 +6,15 @@ 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 + +Things in context: + +1. settings - the settings object + +Use this hook to receive the global settings in your plugin. + ## pluginUninstall Called from: src/static/js/pluginfw/installer.js