================================
Image and Link Libraries in Kupu
================================

Abstract
--------

This document describes the Library feature of the Kupu WYSIWYG
editor. It defines the behaviour on both the client and the server and
serves as a specification for the XML protocol that is used for their
interaction.


Motivation
----------

Kupu is a visual editor for content management systems, with a target
audience of regular users without a high technical profile.  For this
audience, Word is the incumbent.

In addition to text, Kupu users need a simple, usable, and
high-performance system for getting images and hyperlinks into their
pages.  Sometimes they want to *browse* to find these assets.
Sometimes they want to *search*, particularly for large collections.

Regarding usability, open source CMS projects don't have many
browser-side developers with good usability backgrounds.  Since Kupu
is an oscom.org project, it is a goal to move the UI rendering from
the server (where talent would be dispersed among projects) to the
client (where one UI can work for many servers).  Therefore, the Kupu
Library only requests XML from the CMS, not HTML.

Note: Though Kupu will work with IE 5.5 sans service packs, Kupu
requires MS XML 3.0 or higher.  Any IE 5.5 that is up-to-date on
security updates, and any IE 6 or Mozilla 1.3+ can use the Kupu
Library.


Definitions
-----------

Library

  A static or dynamic collection of resources and collections defined
  at a top level. Libraries have no parents from a UI perspective,
  where as the collection they represent might have a parent
  collection.

Collection

  A static or dynamic collection of resources and collections,
  modelled after WebDAV collections.

Resource

  A named and URI-locatable object with associated metadata, such as
  title, description, preview, size, etc. A resource can also be a
  collection.


Process overview 
----------------

1. The user opens a drawer in Kupu. Kupu requests a list of available
   libraries. The server answers with a list of libraries possibly
   containing URIs at which to get the content listing of each library.

2. Upon user request, Kupu loads the contents of a library and present
   it to the user. Already opened libraries are not reloaded from the
   server

3. A user selects a resource at which point Kupu presents the
   associated metadata and may optionally load a preview image.

4. Double clicking on a collection or selecting a new library triggers
   step 2 again.

5. Clicking on a resource and clicking "Insert" (for an image) or
   "Link" (for links) updates the document and closes the drawer.

6. If the cursor is on a link or image, the drawer allows editing the
   image/link attributes.


List of libraries
-----------------

Kupu issues a simple HTTP GET request to a URL defined in the
'libraries' attribute of the iframe. The server returns XML conforming
to the following schema::

    start = libraries | library | collection

    ## 
    ##             The libraries element is the top level for the XML used in Kupu.
    ##             library elements which it contains are listed in the first column of the drawer.
    ##         
    libraries = element libraries { library* }

    ##  
    ##             A library is static or dynamic collection of resources and collections defined at a top level.
    ##             Libraries have no parents from a UI perspective, where as the collection they represent might have a parent collection.
    ##             A library may be returned as a top-level element from a search or canned query.
    ##         
    library =
      element library { commonToAllItems & (collectionItems | itemsSource) }
    commonToAllItems =
      attribute id {
        # Must be unique among all libraries, resources and collections.
        xsd:ID
      },
      attribute class { xsd:NMTOKENS }?,
      attribute selected { "selected" }?,
      (
       ## The url to use when linking to this object
       element uri { xsd:anyURI }
       & 
         ## Title to display in properties pane
         element title { text }
       & element label { text }?
       & 
         ## Icon to use in resource pane
         element icon { xsd:anyURI }?
       & 
         ## Type of media to insert in document 
         element media { text }?)

    ## A list of collections and resources with an optional upload button at the end.
    collectionItems =
      element items {
        collectionItem*,
        element uploadbutton {
          element uri { xsd:anyURI }
        }?
      }

    ## URI to retrieve the contents of the collection.
    itemsSource = element src { text }
    collectionItem = resource | collection

    ## A named and URI-locatable object with associated metadata, such as title, description, preview, size, etc.
    resource =
      element resource {
        commonToAllItems
        & extraResourceInfo
        & 
          ## src element only if the resource is also a collection
          (itemsSource?)
      }

    ## 
    ##             A static or dynamic collection of resources and collections, modelled after WebDAV collections.
    ##         
    collection =
      element collection {
        commonToAllItems & itemsSource & breadcrumbs? & collectionItems*
      }
    extraResourceInfo =
      element preview { xsd:anyURI }?
      & status*
      & element size { sizeType }?
      & element type { text }?
      & element description { text }?
      & element height { xsd:positiveInteger }?
      & element width { xsd:positiveInteger }?
      & 
        ## Set if this resource could contain anchors
        (element anchor { empty }?)
    sizeType = xsd:string { pattern = "[0-9]+([KkMmGgTt]?b)?" }
    status =

      ## Additional text to display in drawer
      element status {
        attribute class { xsd:NMTOKENS },
        text
      }

    ## 
    ## The optional breadcrumbs element may be used to provide 
    ## a breadcrumb bar at the top of the drawer.
    ##         
    breadcrumbs =
      element breadcrumbs {
        element crumb {
          attribute href { xsd:anyURI }
        }*,
        text
      }


Kupu parses this XML to a DOM managed in JavaScript and displays it
using an XSLT stylesheet. The DOM tree is stored throughout the whole
Kupu session.


Browser-side architecture
------------------------------------

When the drawer is opened, Kupu loads the XML data about the libraries
into a JS property that holds an XML DOM.  The Sarissa cross-browser
abstraction for XML (http://sarissa.sf.net) is used.

The initial XML should have *libraries* as the top element.

As the user clicks, more XML data are loaded if that collection hasn't
been visited yet. A click on a *collection* or *resource* which has a
*src* element will retrieve the *src* uri. This should return XML with
a *collection* as the top element. The *items* represent the elements
contained in the collection.

A search, or a click on a *library* in the left column retrieves the
*src* or the search uri. If the result has *library* as the top
element it is added to the available libraries. If the result uses
*collection* it does not add to the left column but does display the
collection contents in the middle column.

The loaded XML is then appended into the XML DOM.  The node that was
clicked on has an attribute set to mark it as selected.  Kupu then
runs an XSLT on the XML DOM to re-generate the drawer contents (as
HTML), and updates the HTML node with the output.  The use of XSLT and
XPath give increased performance, lower line count, and better
IE/Mozilla compatibility.

As an implementation note, XML is extremely compressible.  With
mod_deflate and other server-compression approaches, at least a
thousand entries can be encoded in 100 Kb.

User opens on a library
-----------------------

In case a collection or library has been active before, it is
deselected (by removing the 'selected' attribute on the DOM node). The
DOM node of the library the user has chosen is selected (by setting
the 'selected' attribute). Also, visually deselect the before selected
library (by unsetting a CSS class) and mark the newly selected library
(with a CSS class).

In case its <library> element provides the <src> subelement, an HTTP
GET to that URI is made, the returned XML retrieved, turned into a DOM
tree and the resulting <items> node of the result appended to the
library DOM node. That step is not made in case the <library> node
directly provides the <items> node.

Now, the XSLT is executed again, presenting the newly selected node
(by querying for the node with the 'selected' attribute) and its
contents.

For example::

 <library id="some_unique_id">
   <title>Current folder</title>
   <uri>http://server/current/folder</uri>
   <icon>http://server/folder.ico</icon>
   <items>
     <resource id="another_unique_id">
       <title>Foo img</title>
       <uri>http://server/current/folder/foo.jpg</uri>
       <icon>http://server/image.ico</icon>
     </resource>
     <collection id="a_collections_unique_id">
       <title>Some collection</title>
       <uri>http://server/current/folder/collection</uri>
       <icon>http://server/folder.ico</icon>
     </collection>
   </items>
 </library>


User opens a collection
-----------------------

Unselect the previously selected collection and try to execute one of
three cases in order:

  * The node of selected collection is queried for an attribute
    'loadedInNode'. If it exists, it refers to an id of a node which
    already contains that collection's items. Select that node and
    execute the XSLT to present changes.

  * The selected collection node does not have a 'loadedInNode'
    attribute. Therefore, the selected collection's URI is read and the
    document element's children are queried with an XPath to check
    whether the collection with that URI was already loaded before and
    attached to the document element. If so, the selected collection
    node receives an attribute 'loadedInNode' with the value of the
    corresponding collection node below the document element, which is
    selected (by setting the 'selected' attribute). The XSLT is executed
    to present the changes.

  * The selected collection node has no 'loadedInNode' attribute and
    there is no preloaded collection node of that URI. The selected
    node's <src> subelement is read. An HTTP GET request is made to that
    URI and the items XML data retrieved and turned into a DOM tree. The
    <collection> node of that resulting tree is given a new unique ID,
    appended to the document element of the library DOM tree and
    selected (by setting the 'selected' attribute). The selected
    collection node receives an attribute 'loadedInNode' with the value
    that newly generated id. The XSLT is executed to present the
    changes.

When a collection's contents has to be retrieved with an extra
request, it is returned according to the following schema (using
definitions from above)::

  <grammar xmlns="http://relaxng.org/ns/structure/1.0">

    <start>
      <ref name="collectionDocumentElement" />
    </start>

    <define name="collectionDocumentElement">
      <element name="collection">
        <ref name="commonToAllItems" />
        <ref name="collectionItems" />
      </element>
    </define>

  </grammar>

This grammar is modeled after the WebDAV response grammar, without
using the namespace parts of WebDAV. For example::

  <collection id="some_unique_id">
    <title>Some folder</title>
    <uri>http://server/some/folder</uri>
    <icon>http://server/folder.ico</icon>

    <items>
      <resource id="another_unique_id">
        <title>Foo img</title>
        <uri>http://server/some/folder/foo.jpg</uri>
        <icon>http://server/image.ico</icon>
      </resource>

      <collection id="a_subfolders_unique_id">
        <title>Some subfolder</title>
        <uri>http://server/some/folder/subfolder</uri>
        <icon>http://server/folder.ico</icon>
        <src>http://server/some/folder/resources.kupu</src>
      </collection>
    </items>

  </library>


If a collection's parent collection shall be accessible, then the
server has to return an entry for it explicitly.


Searching
---------

For searching, Kupu sends an HTTP POST request to the server (to a
configurable URI). The server does the search based on the POST form
values and return the results as if they were the contents of a
library. The document element of the returned XML, <library>, does not
have to have a unique id nor an icon subelement.

The POST request can contain an optional parameter for the size of the
result set. The server can enforce an upper limit itself, and if the
request parameter is higher, ignore it. By default, this value is 500.

Kupu treats the search result as a library, with the exception of
generating a unique id and icon for it. The search result library is
attached to the root node of the DOM tree and thus visible and later
accessible via the library list.


Implementation on the Plone side
--------------------------------

On the Plone side, a set of Page Templates is responsible for
generating the XML. They are aided by a special tool, KupuLibraryTool.

In order to use the XML generation for inclusion of media objects,
such as images and photos, as well as for searching documents that one
can link to from Kupu, the library tool keeps a mapping from resource
types to portal types. It provides a management interface in which the
site administrator can define, which portal types are to be treated as
collections, as linkable documents, as insertable media objects, etc.

The library tool also keeps a list of available libraries on the
site. The site administrator can add, modify, delete and reorder
libraries in the tool's management interface. The searching aspect of
Kupu Libraries is handled by the portal catalog.

Permissions used by the library tool:

- Kupu: Query libraries

  This permission is required for all users who want to query
  libraries from the library tool.

- Kupu: Manage libraries

  This permissions is required for all administrators who need to add,
  edit and delete existing libraries.

In order to be able to present special previews for resources without
having to load the resource itself in Kupu, a portal type can have a
defined preview action which is stored in the tool as a tal
expression. If a portal_type does not provide that action, preview is
disabled for it.

Example: Preview action for Image showing each picture's thumbnail
version:

  - Go to the kupu configlet/resource types tab. Scroll to the bottom
    of the page.

  - Under 'preview URLs' select the type 'Image' and under preview enter:

    string:${object_url}/image_thumb

  - Hit the 'Save' button.


Flash support
-------------

See PLONE2.txt

Futures
-------

  * Cached XSLT (need Sarissa support for this)

  * Deeper standards support (WebDAV/DASL)


