Tag Archives: plugins

Etherpad Lite Plugins bei npmjs.org veröffentlichen

30 Jun

In meinen bisherigen Beiträgen in diesem Blog habe ich u.A. gezeigt, wie ein Plugin für Etherpad Lite geschrieben wird und wie die dazugehörige package.json generiert werden kann. Um das fortzusetzen, möchte ich in nun kurz zeigen, wie ein Plugin bei npm.js veröffentlicht werden kann. Weiterlesen

Advertisements

Ein DDI-Plugin für Etherpad Lite

13 Mrz

In meinem letzten Beitrag habe ich das neue Plugin-Framework für Etherpad Lite vorgestellt. Heute zeige ich, wie man ein Plugin schreibt, das einen Text unter der URL http://localhost:9001/ddi anzeigt. Weiterlesen

Neues Plugin-Framework für Etherpad Lite

3 Mrz

In den letzten Wochen ist bei der Entwicklung von Etherpad Lite eine Menge passiert. Äußerlich ist nicht viel zu sehen, unter Haube wurde jedoch sehr viel Aufräumarbeit betrieben. Dies bringt schlankeren und stabileren Code mit sich.

Vergangene Woche fand außerdem ein sogenanntes Hackathon statt. Dabei ist unter anderem ein Plugin-Framework entstanden. Damit ist es nun möglich geworden, Etherpad Lite relativ einfach mit Plugins zu erweitern. Weiterlesen

Die Etherpad Plugin-Infrastruktur

2 Jul

Die folgenden Ausführungen basieren auf dem Dokument README.plugins und der Analyse des Etherpad Plugins heading1.

Plugin-Infrastruktur und Hooks

Bei der Plugin-Infrastruktur von Etherpad handelt es sich um ein sog. Hook-Infrastruktur, d.h. dass an geeignetter Stelle im Etherpad Quellcode sog. Hook-Calls ausgelöst werden, für welche sich ein oder mehrere Plugins registrieren können.

Ein Beispiel hierfür sind die Hook-Calls serverStartup und serverShutdown in der Datei etherpad/src/main.js in den Funktionen serverhandlers.startupHandler und serverhandlers.shutdownHandler.

/*
 * etherpad/src/main.js
 */
...
serverhandlers.startupHandler = function() {
  // Order matters.
  checkSystemRequirements();
  ...
  log.onStartup();
  statistics.onStartup();
  migration_runner.onStartup();
  pad_migrations.onStartup();
  model.onStartup();
  collab_server.onStartup();
  pad_control.onStartup();
  dbwriter.onStartup();
  blogcontrol.onStartup();
  importexport.onStartup();
  pro_pad_editors.onStartup();
  noprowatcher.onStartup();
  team_billing.onStartup();
  collabroom_server.onStartup();
  readLatestSessionsFromDisk();

  plugins.callHook('serverStartup');
};
…
serverhandlers.shutdownHandler = function() {
  plugins.callHook('serverShutdown');

  appjet.cache.shutdownHandlerIsRunning = true;
  log.callCatchingExceptions(writeSessionsToDisk);
  log.callCatchingExceptions(dbwriter.onShutdown);
  log.callCatchingExceptions(sqlcommon.onShutdown);
  log.callCatchingExceptions(pro_pad_editors.onShutdown);
};
...

Dabei kann grundsätzlich jeder in JavaScript zulässige Funktionsname als Hook-Name verwendet werden. Die Datei README.plugins führt eine (unvollständige) Liste von zur Verfügung stehenden Hooks auf. Ein Beispiel für einen wichtigen Hook, welcher u.a. nicht aufgeführt wird, ist aceCreateDomLine, doch dazu später mehr.

Serverseitige vs. clientseitige Plugin-Teile

Etherpad führt Code auf dem Server, als auch auf dem Client aus. In beiden Fällen kommt JavaScript zum Einsatz. Auf der Serverseite bildet dabei ein Gespann aus dem von den ehemaligen Etherpadentwicklern konzipierten AppJet und einem eigenen JavaScript Micro-Framework die Basis für serverseitiges JavaScript.

Ein Plugin besteht immer wenigstens aus der Datei main.js, im Falle des Plugins heading1 also bsp. etherpad/src/plugins/heading1/main.js. Diese wird auf der Serverseite geladen und ausgeführt. Diese Datei muss einen Objektkonstruktur enthalten, welcher der folgenden Benennungsvorschrift entsprechen muss

PluginNameInit

Im Falle des Plugins heading1 heißt der Objektkonstruktur als heading1Init und ist im folgenden als Bestandteil der Datei heading1/main.js dargestellt.

import("etherpad.log");
import("plugins.heading1.hooks");
import("plugins.heading1.static.js.main");

function heading1Init() {
  this.hooks = ['editBarItemsLeftPad', 'aceAttribsToClasses',
  'aceCreateDomLine'];
  this.description = 'heading1';
  this.client = new main.heading1Init();
  this.editBarItemsLeftPad = hooks.editBarItemsLeftPad;
  this.aceAttribsToClasses = main.aceAttribsToClasses;
  this.aceCreateDomLine = main.aceCreateDomLine;
  this.install = install;
  this.uninstall = uninstall;
}

function install() {
  log.info("Installing heading1");
}

function uninstall() {
  log.info("Uninstalling heading1");
}

Die Aufgaben des Objektkonstrukturs sind u.a.

  • Registrierung für bestehende Hook-Calls (this.hooks = …)
  • Definitionen der Hook-Funktionen, welche bei einem entsprechen Hook-Call aufgerufen werden (z.B. this.editBarItemsLeftPad = … oder this.aceAttribsToClasses = …)

An dieser Stelle kann man bereits die ersten Unterschiede zwischen serverseitigem und clientseitigem JavaScript Code erkennen. Vergleicht man einmal die Definitionen der beiden Hooks editBarItemsLeftPad und aceAttribsToClasses

  • this.editBarItemsLeftPad = hooks.editBarItemsLeftPad;
  • this.aceAttribsToClasses = main.aceAttribsToClasses;

so fällt einem auf, dass die eine Funktion unterhalb von hooks und die andere unterhalb von main definiert wird. Die entsprechenden Funktionsdefinitionen finden sich in den Dateien heading1/hooks.js und heading1/static/js/main.js.

Bei editBarItemsLeftPad handelt es sich um serverseitigen JavaScript Code, bei aceAttribsToClasses handelt es sich um Code, welcher sowohl client-, als auch serverseitig ausgeführt wird.

An dieser Stelle wird es zugegebener Maßen etwas unübersichtlich, dennoch ist es die gängige Praxis bei den bestehenden Etherpad-Plugins. Die Hauptplugindatei main.js inkludiert sowohl die Datei hooks.js mit dem serverseitigen Code, als auch die Datei static/js/main.js, welche den clientseitgen JavaScript Code beinhaltet. Dazu der entsprechende Abschnitt aus der Datei README.plugins, welcher diesen Sachverhalt relativ präzise auf den Punkt bringt.

The hook system is replicated on the client side - there is a
plugins.callHook() and plugins can register client side functions to
be called when that hook is called for.

This registration is done from a client side java script file called
static/js/main.js. There is one major catch with this file: It runs
both on the server and client! Beware!

static/js/main.js is modelled after the main main.js, and imported by
that one on the server. The descriptor object created by
pluginNameInit() in static/js/main.js is also included as a property
on the main plugin descriptor object.

Beispiel: Aufruf von heading1 auf der Serverseite

  1. src/ethpad/plugins/headin1/main.js wird aufgerufen
  2. src/ethpad/plugins/headin1/hooks.js wird inkludiert
  3. src/ethpad/plugins/headin1/static/js/main.js wird inkludiert
  4. Mit Hilfe des Objektkonstruktors heading1Init wird ein neues Objekt mit den folgenden Eigenschaften erstellt
    1. hooks = [‚editBarItemsLeftPad‘, ‚aceAttribsToClasses‘, ‚aceCreateDomLine‘];
    2. description = ‚heading1‘;
    3. client = new main.heading1Init();
    4. editBarItemsLeftPad = hooks.editBarItemsLeftPad;
    5. aceAttribsToClasses = main.aceAttribsToClasses;
    6. aceCreateDomLine = main.aceCreateDomLine;
    7. install = install;
    8. uninstall = uninstall;

Beispiel: Aufruf von heading1 auf der Clientseite

  1. src/etherpad/plugins/heading1/static/js/main.js wird aufgerufen
  2. Mit Hilfe des Objektkonstrukturs in DIESER Datei wird ein neues Objekt mit den folgenden Eigenschaften erstellt
    1. hooks = [‚aceAttribsToClasses‘, ‚aceCreateDomLine‘, ‚collectContentPre‘, ‚collectContentPost‘];
    2. aceAttribsToClasses = aceAttribsToClasses;
    3. aceCreateDomLine = aceCreateDomLine;
    4. collectContentPre = collectContentPre;
    5. collectContentPost = collectContentPost;

Vergleicht man nun einmal die Eigenschaft hooks der beiden Objekte, so fällt auf, dass es genau drei verschiedene Arten von Plugincode gibt

  1. Code welcher nur auf der Serverseite ausgeführt wird
    • z.B. editBarItemsLeftPad
  2. Code, welcher nur auf der Clientseite ausgeführt wird
    • z.B. collectContentPre und collectContentPost
  3. Code, welcher sowohl auf der Server- also auch auf der Clientseite ausgeführt wird
    • z.B. aceAttribsToClasses

Man erkennt ebenfalls gut, wie der clientseitge Code serverseitig über die Eigenschaft client eingebunden wird, nämlich via client = new main.heading1Init().

Es gilt nun, anhand der bestehenden Plugins die Verwendung der bestehenden Hook-Calls, wie etwa editBarItemsLeftPad, aceAttribsToClasses oder aceCreateDomLine zu analysieren.