[Checkins] SVN: CMF/trunk/CMFDefault/ - replaced document and news item edit forms by formlib based forms

Yvo Schubbe y.2007- at wcm-solutions.de
Sun Feb 4 10:31:52 EST 2007


Log message for revision 72362:
  - replaced document and news item edit forms by formlib based forms

Changed:
  U   CMF/trunk/CMFDefault/browser/TODO.txt
  U   CMF/trunk/CMFDefault/browser/configure.zcml
  U   CMF/trunk/CMFDefault/browser/document.py
  A   CMF/trunk/CMFDefault/browser/document.txt
  U   CMF/trunk/CMFDefault/browser/newsitem.py
  D   CMF/trunk/CMFDefault/browser/templates/document_edit.pt
  D   CMF/trunk/CMFDefault/browser/templates/newsitem_edit.pt
  U   CMF/trunk/CMFDefault/browser/tests.py
  U   CMF/trunk/CMFDefault/formlib/editform.pt
  U   CMF/trunk/CMFDefault/formlib/vocabulary.py
  U   CMF/trunk/CMFDefault/formlib/widgets.py
  U   CMF/trunk/CMFDefault/locales/cmf_default.pot
  U   CMF/trunk/CMFDefault/skins/zpt_generic/zpt_stylesheet.css

-=-
Modified: CMF/trunk/CMFDefault/browser/TODO.txt
===================================================================
--- CMF/trunk/CMFDefault/browser/TODO.txt	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/browser/TODO.txt	2007-02-04 15:31:51 UTC (rev 72362)
@@ -55,18 +55,18 @@
   [x] IMutableDocument @@edit.html:
 
       document_edit_form.py -> DocumentEditView
-      document_edit_template.pt -> templates/document_edit.pt
-      validateHTML.py -> DocumentEditView.validateHTML
-      validateTextFile.py -> DocumentEditView.validateTextFile
-      document_edit_control.py -> DocumentEditView.edit_control
+      document_edit_template.pt -> formlib based
+      validateHTML.py -> formlib based
+      validateTextFile.py -> formlib based
+      document_edit_control.py -> formlib based
 
   [x] IMutableNewsItem @@edit.html:
 
       newsitem_edit_form.py -> NewsItemEditView
-      newsitem_edit_template.pt -> templates/newsitem_edit.pt
-      validateHTML.py -> DocumentEditView.validateHTML
-      validateTextFile.py -> DocumentEditView.validateTextFile
-      newsitem_edit_control.py -> NewsItemEditView.edit_control
+      newsitem_edit_template.pt -> formlib based
+      validateHTML.py -> formlib based
+      validateTextFile.py -> formlib based
+      newsitem_edit_control.py -> formlib based
 
   [x] ILink @@view.html:
 

Modified: CMF/trunk/CMFDefault/browser/configure.zcml
===================================================================
--- CMF/trunk/CMFDefault/browser/configure.zcml	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/browser/configure.zcml	2007-02-04 15:31:51 UTC (rev 72362)
@@ -40,6 +40,13 @@
       permission="cmf.ModifyPortalContent"
       />
 
+  <adapter factory=".document.DocumentSchemaAdapter"/>
+
+  <utility
+      component=".document.TextFormatVocabularyFactory"
+      name="cmf.AvailableTextFormats"
+      />
+
   <browser:page
       for="..interfaces.IDocument"
       layer="..interfaces.ICMFDefaultSkin"
@@ -54,16 +61,16 @@
       layer="..interfaces.ICMFDefaultSkin"
       name="edit.html"
       class=".document.DocumentEditView"
-      template="templates/document_edit.pt"
       permission="cmf.ModifyPortalContent"
       />
 
+  <adapter factory=".newsitem.NewsItemSchemaAdapter"/>
+
   <browser:page
       for="..interfaces.IMutableNewsItem"
       layer="..interfaces.ICMFDefaultSkin"
       name="edit.html"
       class=".newsitem.NewsItemEditView"
-      template="templates/newsitem_edit.pt"
       permission="cmf.ModifyPortalContent"
       />
 

Modified: CMF/trunk/CMFDefault/browser/document.py
===================================================================
--- CMF/trunk/CMFDefault/browser/document.py	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/browser/document.py	2007-02-04 15:31:51 UTC (rev 72362)
@@ -15,97 +15,129 @@
 $Id$
 """
 
-from Products.CMFDefault.exceptions import EditingConflict
-from Products.CMFDefault.exceptions import IllegalHTML
-from Products.CMFDefault.exceptions import ResourceLockedError
+from zope.component import adapts
+from zope.formlib import form
+from zope.interface import implements
+from zope.interface import Interface
+from zope.schema import ASCIILine
+from zope.schema import Bytes
+from zope.schema import Choice
+from zope.schema import Text
+from zope.schema import TextLine
+
+from Products.CMFDefault.formlib.form import ContentEditFormBase
+from Products.CMFDefault.formlib.schema import ProxyFieldProperty
+from Products.CMFDefault.formlib.schema import SchemaAdapterBase
+from Products.CMFDefault.formlib.vocabulary import StaticVocabulary
+from Products.CMFDefault.formlib.widgets import ChoiceRadioWidget
+from Products.CMFDefault.formlib.widgets import TextInputWidget
+from Products.CMFDefault.interfaces import IMutableDocument
 from Products.CMFDefault.utils import Message as _
-from Products.CMFDefault.utils import scrubHTML
 
 from utils import decode
-from utils import FormViewBase
 from utils import memoize
 from utils import ViewBase
 
+available_text_formats = (
+        (u'structured-text', 'structured-text', _(u'structured-text')),
+        (u'plain', 'plain', _(u'plain text')),
+        (u'html', 'html', _(u'html')))
 
-class DocumentView(ViewBase):
+TextFormatVocabularyFactory = StaticVocabulary(available_text_formats)
 
-    """View for IDocument.
+
+class IDocumentSchema(Interface):
+
+    """Schema for document views.
     """
 
-    # interface
+    safety_belt = ASCIILine()
 
-    @memoize
-    @decode
-    def text(self):
-        return self.context.CookedBody()
+    title = TextLine(
+        title=_(u'Title'),
+        readonly=True)
 
+    description = Text(
+        title=_(u'Description'),
+        readonly=True)
 
-class DocumentEditView(FormViewBase):
+    text_format = Choice(
+        title=_(u'Format'),
+        vocabulary='cmf.AvailableTextFormats')
 
-    """Edit view for IMutableDocument.
+    upload = Bytes(
+        title=_(u'Upload'),
+        required=False)
+
+    text = Text(
+        title=_(u'Body'),
+        required=False,
+        missing_value=u'')
+
+
+class DocumentSchemaAdapter(SchemaAdapterBase):
+
+    """Adapter for IMutableDocument.
     """
 
-    _BUTTONS = ({'id': 'change',
-                 'title': _(u'Change'),
-                 'transform': ('validateTextFile', 'validateHTML',
-                               'edit_control'),
-                 'redirect': ('portal_types', 'object/edit')},
-                {'id': 'change_and_view',
-                 'title': _(u'Change and View'),
-                 'transform': ('validateTextFile', 'validateHTML',
-                               'edit_control'),
-                 'redirect': ('portal_types', 'object/view')})
+    adapts(IMutableDocument)
+    implements(IDocumentSchema)
 
-    #helpers
+    safety_belt = ProxyFieldProperty(IDocumentSchema['safety_belt'],
+                                     'SafetyBelt')
+    title = ProxyFieldProperty(IDocumentSchema['title'], 'Title')
+    description = ProxyFieldProperty(IDocumentSchema['description'],
+                                     'Description')
+    text_format = ProxyFieldProperty(IDocumentSchema['text_format'])
+    upload = None
+    text = ProxyFieldProperty(IDocumentSchema['text'],
+                              'EditableBody', '_edit')
 
-    @memoize
-    def _getHiddenVars(self):
-        belt = self.request.form.get('SafetyBelt', self.context.SafetyBelt())
-        return {'SafetyBelt': belt}
 
+class DocumentView(ViewBase):
+
+    """View for IDocument.
+    """
+
     # interface
 
     @memoize
-    def text_format(self):
-        return self.request.form.get('text_format', self.context.text_format)
-
-    @memoize
     @decode
     def text(self):
-        return self.request.form.get('text', self.context.EditableBody())
+        return self.context.CookedBody()
 
-    # validators
 
-    def validateHTML(self, text, description='', **kw):
-        try:
-            self.request.form['description'] = scrubHTML(description)
-            self.request.form['text'] = scrubHTML(text)
-            return True
-        except IllegalHTML, errmsg:
-            return False, errmsg
+class DocumentEditView(ContentEditFormBase):
 
-    def validateTextFile(self, file='', **kw):
-        try:
-            upload = file.read()
-        except AttributeError:
-            return True
-        else:
-            if upload:
-                self.request.form['text'] = upload
-                return True
-            else:
-                return True
+    """Edit view for IMutableDocument.
+    """
 
-    # controllers
+    form_fields = form.FormFields(IDocumentSchema)
+    form_fields['text_format'].custom_widget = ChoiceRadioWidget
+    form_fields['text'].custom_widget = TextInputWidget
 
-    def edit_control(self, text_format, text, SafetyBelt='', **kw):
-        context = self.context
-        if text_format != context.text_format or \
-                text != context.EditableBody():
-            try:
-                context.edit(text_format, text, safety_belt=SafetyBelt)
-                return True, _(u'Document changed.')
-            except (ResourceLockedError, EditingConflict), errmsg:
-                return False, errmsg
-        else:
-            return False, _(u'Nothing to change.')
+    def setUpWidgets(self, ignore_request=False):
+        super(DocumentEditView,
+              self).setUpWidgets(ignore_request=ignore_request)
+        self.widgets['safety_belt'].hide = True
+        self.widgets['description'].height = 3
+        self.widgets['text_format'].orientation = 'horizontal'
+        self.widgets['upload'].displayWidth = 60
+        self.widgets['text'].height = 20
+
+    def _handle_success(self, action, data):
+        body = data.get('upload')
+        if body:
+            data['text'] = body.decode(self._getDefaultCharset())
+        super(DocumentEditView, self)._handle_success(action, data)
+
+    def handle_validate(self, action, data):
+        errors = super(DocumentEditView, self).handle_validate(action, data)
+        if errors:
+            return errors
+        safety_belt = self.request.form['form.safety_belt']
+        if not self.context._safety_belt_update(safety_belt):
+            return (_(u'Intervening changes from elsewhere detected. Please '
+                      u'refetch the document and reapply your changes.'),)
+        self.request.form['form.safety_belt'] = self.context.SafetyBelt()
+        return None

Added: CMF/trunk/CMFDefault/browser/document.txt
===================================================================
--- CMF/trunk/CMFDefault/browser/document.txt	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/browser/document.txt	2007-02-04 15:31:51 UTC (rev 72362)
@@ -0,0 +1,68 @@
+Document Views
+--------------
+
+Set up Document and user.
+
+    >>> from Products.CMFDefault.Document import Document
+    >>> id = app.site._setObject('myDocument', Document('myDocument'))
+
+    >>> uf = app.site.acl_users
+    >>> uf._doAddUser('mgr', 'mgrpw', ['Manager'], [])
+
+Create the browser object we'll be using.
+
+    >>> from Products.Five.testbrowser import Browser
+    >>> browser = Browser()
+    >>> browser.handleErrors = False
+    >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+
+Use the edit form with invalid input.
+
+    >>> browser.open('http://localhost/site/myDocument/@@edit.html')
+    >>> browser.getControl('[[cmf_default][Body]]').value = '<script>'
+    >>> browser.getControl('[[cmf_default][Change]]').click()
+    >>> '[[zope][There were errors]]' in browser.contents
+    True
+    >>> '[[cmf_default][Dynamic tag \'script\' not ' in browser.contents
+    True
+
+Use the edit form with valid input.
+
+    >>> browser.getControl('[[cmf_default][Body]]').value = 'spam'
+    >>> browser.getControl('[[cmf_default][Change]]').click()
+    >>> '[[zope][There were errors]]' in browser.contents
+    False
+
+Look at the result.
+
+    >>> browser.open('http://localhost/site/myDocument/@@view.html')
+    >>> 'spam' in browser.contents
+    True
+
+Changes can't be saved if the safety belt is invalid.
+
+    >>> browser.open('http://localhost/site/myDocument/@@edit.html')
+
+    >>> app.site.myDocument._safety_belt = '1'
+
+    >>> browser.getControl('[[cmf_default][Body]]').value = 'spam and eggs'
+    >>> browser.getControl('[[cmf_default][Change]]').click()
+    >>> '[[zope][There were errors]]' in browser.contents
+    True
+    >>> '[[cmf_default][Intervening changes from ' in browser.contents
+    True
+
+Changes can't be saved if the document is locked.
+
+    >>> browser.open('http://localhost/site/myDocument/@@edit.html')
+
+    >>> from webdav.LockItem import LockItem
+    >>> lock = LockItem(uf.getUser('mgr').__of__(uf))
+    >>> app.site.myDocument.wl_setLock(lock.getLockToken(), lock)
+
+    >>> browser.getControl('[[cmf_default][Body]]').value = 'spam and eggs'
+    >>> browser.getControl('[[cmf_default][Change]]').click()
+    >>> '[[zope][There were errors]]' in browser.contents
+    True
+    >>> '[[cmf_default][This resource is locked ' in browser.contents
+    True


Property changes on: CMF/trunk/CMFDefault/browser/document.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: CMF/trunk/CMFDefault/browser/newsitem.py
===================================================================
--- CMF/trunk/CMFDefault/browser/newsitem.py	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/browser/newsitem.py	2007-02-04 15:31:51 UTC (rev 72362)
@@ -15,39 +15,76 @@
 $Id$
 """
 
-from Products.CMFDefault.exceptions import ResourceLockedError
+from zope.component import adapts
+from zope.formlib import form
+from zope.interface import implements
+from zope.interface import Interface
+from zope.schema import Choice
+from zope.schema import Text
+from zope.schema import TextLine
+
+from Products.CMFDefault.formlib.form import ContentEditFormBase
+from Products.CMFDefault.formlib.schema import ProxyFieldProperty
+from Products.CMFDefault.formlib.schema import SchemaAdapterBase
+from Products.CMFDefault.formlib.widgets import ChoiceRadioWidget
+from Products.CMFDefault.formlib.widgets import TextInputWidget
+from Products.CMFDefault.interfaces import IMutableNewsItem
 from Products.CMFDefault.utils import Message as _
 
-from document import DocumentEditView
-from utils import decode
-from utils import memoize
 
+class INewsItemSchema(Interface):
 
-class NewsItemEditView(DocumentEditView):
+    """Schema for news item views.
+    """
 
-    """Edit view for INewsItem.
+    title = TextLine(
+        title=_(u'Title'),
+        readonly=True)
+
+    text_format = Choice(
+        title=_(u'Format'),
+        vocabulary='cmf.AvailableTextFormats')
+
+    description = Text(
+        title=_(u'Lead-in'),
+        required=False,
+        missing_value=u'')
+
+    text = Text(
+        title=_(u'Body'),
+        required=False,
+        missing_value=u'')
+
+
+class NewsItemSchemaAdapter(SchemaAdapterBase):
+
+    """Adapter for IMutableNewsItem.
     """
 
-    # interface
+    adapts(IMutableNewsItem)
+    implements(INewsItemSchema)
 
-    @memoize
-    @decode
-    def description(self):
-        return self.request.form.get('description',
-                                     self.context.Description())
+    title = ProxyFieldProperty(INewsItemSchema['title'], 'Title')
+    text_format = ProxyFieldProperty(INewsItemSchema['text_format'])
+    description = ProxyFieldProperty(INewsItemSchema['description'],
+                                     'Description', 'setDescription')
+    text = ProxyFieldProperty(INewsItemSchema['text'],
+                              'EditableBody', '_edit')
 
-    # controllers
 
-    def edit_control(self, text_format, text, description='', **kw):
-        context = self.context
-        if description != context.Description() or \
-                text_format != context.text_format or \
-                text != context.EditableBody():
-            try:
-                context.edit(text=text, description=description,
-                             text_format=text_format)
-                return True, _(u'News Item changed.')
-            except ResourceLockedError, errmsg:
-                return False, errmsg
-        else:
-            return False, _(u'Nothing to change.')
+class NewsItemEditView(ContentEditFormBase):
+
+    """Edit view for INewsItem.
+    """
+
+    form_fields = form.FormFields(INewsItemSchema)
+    form_fields['text_format'].custom_widget = ChoiceRadioWidget
+    form_fields['description'].custom_widget = TextInputWidget
+    form_fields['text'].custom_widget = TextInputWidget
+
+    def setUpWidgets(self, ignore_request=False):
+        super(NewsItemEditView,
+              self).setUpWidgets(ignore_request=ignore_request)
+        self.widgets['text_format'].orientation = 'horizontal'
+        self.widgets['description'].height = 8
+        self.widgets['text'].height = 16

Deleted: CMF/trunk/CMFDefault/browser/templates/document_edit.pt
===================================================================
--- CMF/trunk/CMFDefault/browser/templates/document_edit.pt	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/browser/templates/document_edit.pt	2007-02-04 15:31:51 UTC (rev 72362)
@@ -1,58 +0,0 @@
-<html metal:use-macro="context/@@standard_macros/page">
-<body>
-
-<metal:slot metal:fill-slot="body" i18n:domain="cmf_default">
-<h1 i18n:translate="">Edit: <tal:span
-    tal:content="view/title" i18n:name="obj_title">Title</tal:span></h1>
-
-<form action="document_edit_form" method="post" enctype="multipart/form-data"
-   tal:attributes="action view/form_action"
-><metal:macro metal:use-macro="context/@@form_widget/hidden_vars" />
-<table class="FormLayout">
- <tr>
-  <th i18n:translate="">Title</th>
-  <td tal:content="view/title">Title</td>
- </tr>
- <tr>
-  <th i18n:translate="">Description</th>
-  <td tal:content="view/description">Description</td>
- </tr>
- <tr>
-  <th i18n:translate="">Format</th>
-  <td>
-   <input type="radio" name="text_format" value="structured-text" id="cb_stx"
-      tal:attributes="checked python: view.text_format()=='structured-text'" />
-   <label for="cb_stx" i18n:translate="">structured-text</label>
-   <input type="radio" name="text_format" value="plain" id="cb_plain"
-      tal:attributes="checked python: view.text_format()=='plain'" />
-   <label for="cb_plain" i18n:translate="">plain text</label>
-   <input type="radio" name="text_format" value="html" id="cb_html"
-      tal:attributes="checked python: view.text_format()=='html'" />
-   <label for="cb_html" i18n:translate="">html</label>
-  </td>
- </tr>
- <tr>
-  <th i18n:translate="">Upload</th>
-  <td>
-   <input type="file" name="file" size="40" />
-  </td>
- </tr>
- <tr>
-  <th i18n:translate="">Edit</th>
-  <td>
-   <textarea name="text:text" rows="20" cols="80" wrap="soft"
-      tal:content="view/text"></textarea>
-  </td>
- </tr>
- <tr>
-  <td>&nbsp;</td>
-  <td>
-   <metal:macro metal:use-macro="context/@@form_widget/buttons" />
-  </td>
- </tr>
-</table>
-</form>
-</metal:slot>
-
-</body>
-</html>

Deleted: CMF/trunk/CMFDefault/browser/templates/newsitem_edit.pt
===================================================================
--- CMF/trunk/CMFDefault/browser/templates/newsitem_edit.pt	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/browser/templates/newsitem_edit.pt	2007-02-04 15:31:51 UTC (rev 72362)
@@ -1,54 +0,0 @@
-<html metal:use-macro="context/@@standard_macros/page">
-<body>
-
-<metal:slot metal:fill-slot="body" i18n:domain="cmf_default">
-<h1 i18n:translate="">Edit: <tal:span
-    tal:content="view/title" i18n:name="obj_title">Title</tal:span></h1>
-
-<form action="newsitem_edit_form" method="post"
-   tal:attributes="action view/form_action">
-<table class="FormLayout">
- <tr>
-  <th i18n:translate="">Title</th>
-  <td tal:content="view/title">Title</td>
- </tr>
- <tr>
-  <th i18n:translate="">Format</th>
-  <td>
-   <input type="radio" name="text_format" value="structured-text" id="cb_stx"
-      tal:attributes="checked python: view.text_format()=='structured-text'" />
-   <label for="cb_stx" i18n:translate="">structured-text</label>
-   <input type="radio" name="text_format" value="plain" id="cb_plain"
-      tal:attributes="checked python: view.text_format()=='plain'" />
-   <label for="cb_plain" i18n:translate="">plain text</label>
-   <input type="radio" name="text_format" value="html" id="cb_html"
-      tal:attributes="checked python: view.text_format()=='html'" />
-   <label for="cb_html" i18n:translate="">html</label>
-  </td>
- </tr>
- <tr>
-  <th i18n:translate="">Lead-in</th>
-  <td>
-   <textarea name="description:text" rows="8" cols="80" wrap="soft"
-      tal:content="view/description"></textarea>
-  </td>
- </tr>
- <tr>
-  <th i18n:translate="">Body</th>
-  <td>
-   <textarea name="text:text" rows="16" cols="80" wrap="soft"
-      tal:content="view/text"></textarea>
-  </td>
- </tr>
- <tr>
-  <td>&nbsp;</td>
-  <td>
-   <metal:macro metal:use-macro="context/@@form_widget/buttons" />
-  </td>
- </tr>
-</table>
-</form>
-</metal:slot>
-
-</body>
-</html>

Modified: CMF/trunk/CMFDefault/browser/tests.py
===================================================================
--- CMF/trunk/CMFDefault/browser/tests.py	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/browser/tests.py	2007-02-04 15:31:51 UTC (rev 72362)
@@ -29,6 +29,9 @@
     s = ZopeTestCase.FunctionalDocFileSuite('metadata.txt')
     s.layer = FunctionalLayer
     suite.addTest(s)
+    s = ZopeTestCase.FunctionalDocFileSuite('document.txt')
+    s.layer = FunctionalLayer
+    suite.addTest(s)
     return suite
 
 if __name__ == '__main__':

Modified: CMF/trunk/CMFDefault/formlib/editform.pt
===================================================================
--- CMF/trunk/CMFDefault/formlib/editform.pt	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/formlib/editform.pt	2007-02-04 15:31:51 UTC (rev 72362)
@@ -13,7 +13,7 @@
    tal:attributes="action request/ACTUAL_URL">
 <div class="widgets">
  <tal:loop tal:repeat="widget view/widgets"
-><div class="widget"
+><div class="widget" tal:condition="not: widget/hide|nothing"
     tal:define="split widget/split|nothing; hint widget/hint|nothing"
     tal:attributes="class python: split and 'widget split' or 'widget'">
   <label tal:attributes="for widget/name; title python: hint or None"
@@ -23,7 +23,9 @@
    <div class="data"><tal:span tal:replace="structure widget" /></div
   ><tal:case tal:condition="widget/error"
       tal:replace="structure widget/error" /></div>
- </div></tal:loop>
+ </div
+><tal:span tal:condition="widget/hide|nothing"
+    tal:replace="structure widget/hidden" /></tal:loop>
 </div>
 <div class="clear"></div>
 

Modified: CMF/trunk/CMFDefault/formlib/vocabulary.py
===================================================================
--- CMF/trunk/CMFDefault/formlib/vocabulary.py	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/formlib/vocabulary.py	2007-02-04 15:31:51 UTC (rev 72362)
@@ -15,6 +15,8 @@
 $Id$
 """
 
+from zope.interface import implements
+from zope.schema.interfaces import IVocabularyFactory
 from zope.schema.vocabulary import SimpleVocabulary
 
 
@@ -28,3 +30,17 @@
         return cls(terms, *interfaces)
 
     fromTitleItems = classmethod(fromTitleItems)
+
+
+class StaticVocabulary(object):
+
+    """Vocabulary factory for static items.
+    """
+
+    implements(IVocabularyFactory)
+
+    def __init__(self, items):
+        self._vocabulary = SimpleVocabulary.fromTitleItems(items)
+
+    def __call__(self, context):
+        return self._vocabulary

Modified: CMF/trunk/CMFDefault/formlib/widgets.py
===================================================================
--- CMF/trunk/CMFDefault/formlib/widgets.py	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/formlib/widgets.py	2007-02-04 15:31:51 UTC (rev 72362)
@@ -18,10 +18,12 @@
 from zope.app.form import InputWidget
 from zope.app.form.browser import BrowserWidget
 from zope.app.form.browser import MultiSelectSetWidget
+from zope.app.form.browser import RadioWidget
 from zope.app.form.browser import TextWidget
 from zope.app.form.browser import TextAreaWidget
 from zope.app.form.interfaces import ConversionError
 from zope.app.form.interfaces import IInputWidget
+from zope.app.form.interfaces import WidgetInputError
 from zope.component import adapts
 from zope.interface import implementsOnly
 from zope.publisher.interfaces.browser import IBrowserRequest
@@ -32,11 +34,19 @@
 from zope.schema.interfaces import ITextLine
 
 from Products.CMFCore.utils import getToolByName
-from Products.CMFDefault.formlib.schema import IEmailLine
-from Products.CMFDefault.formlib.vocabulary import SimpleVocabulary
+from Products.CMFDefault.exceptions import IllegalHTML
+from Products.CMFDefault.utils import scrubHTML
 from Products.CMFDefault.utils import Message as _
+from schema import IEmailLine
+from vocabulary import SimpleVocabulary
 
 
+# generic widgets
+
+def ChoiceRadioWidget(field, request):
+    return RadioWidget(field, field.vocabulary, request)
+
+
 class EmailInputWidget(TextWidget):
 
     implementsOnly(IInputWidget)
@@ -52,6 +62,20 @@
             raise ConversionError(_(u'Invalid email address.'), err)
 
 
+class TextInputWidget(TextAreaWidget):
+
+    def getInputValue(self):
+        value = super(TextInputWidget, self).getInputValue()
+        if value:
+            try:
+                value = scrubHTML(value)
+            except IllegalHTML, err:
+                self._error = WidgetInputError(self.context.__name__,
+                                               self.label, err.args[0])
+                raise self._error
+        return value
+
+
 class TupleTextAreaWidget(TextAreaWidget):
 
     implementsOnly(IInputWidget)
@@ -81,6 +105,8 @@
     return TupleTextAreaWidget(field, field.value_type, request)
 
 
+# special widgets
+
 class SubjectInputWidget(InputWidget, BrowserWidget):
 
     implementsOnly(IInputWidget)

Modified: CMF/trunk/CMFDefault/locales/cmf_default.pot
===================================================================
--- CMF/trunk/CMFDefault/locales/cmf_default.pot	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/locales/cmf_default.pot	2007-02-04 15:31:51 UTC (rev 72362)
@@ -19,32 +19,37 @@
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
+#: CMFCore/PortalContent.py:79
+#: CMFDefault/formlib/form.py:127
+msgid "This resource is locked via webDAV."
+msgstr ""
+
 #: CMFCore/RegistrationTool.py:144
 #: CMFDefault/RegistrationTool.py:94
 msgid "The login name you selected is already in use or is not valid. Please choose another."
 msgstr ""
 
-#: CMFCore/WorkflowTool.py:225
-#: CMFCore/WorkflowTool.py:251
+#: CMFCore/WorkflowTool.py:228
+#: CMFCore/WorkflowTool.py:254
 msgid "No workflows found."
 msgstr ""
 
-#: CMFCore/WorkflowTool.py:232
+#: CMFCore/WorkflowTool.py:235
 #. Default: ""
 msgid "No workflow provides the '${action_id}' action."
 msgstr ""
 
-#: CMFCore/WorkflowTool.py:239
-#: CMFCore/WorkflowTool.py:271
+#: CMFCore/WorkflowTool.py:242
+#: CMFCore/WorkflowTool.py:274
 msgid "Requested workflow definition not found."
 msgstr ""
 
-#: CMFCore/WorkflowTool.py:261
+#: CMFCore/WorkflowTool.py:264
 #. Default: ""
 msgid "No workflow provides '${name}' information."
 msgstr ""
 
-#: CMFCore/WorkflowTool.py:276
+#: CMFCore/WorkflowTool.py:279
 #. Default: ""
 msgid "Could not get info: ${name}"
 msgstr ""
@@ -89,70 +94,36 @@
 msgid "You must enter an email address."
 msgstr ""
 
-#: CMFDefault/browser/document.py:107
-#: CMFDefault/skins/zpt_content/document_edit_control.py:10
-msgid "Document changed."
+#: CMFDefault/browser/document.py:140
+msgid "Intervening changes from elsewhere detected. Please refetch the document and reapply your changes."
 msgstr ""
 
-#: CMFDefault/browser/document.py:111
-#: CMFDefault/browser/folder.py:359
-#: CMFDefault/browser/folder.py:374
-#: CMFDefault/browser/folder.py:389
-#: CMFDefault/browser/folder.py:404
-#: CMFDefault/browser/newsitem.py:53
-#: CMFDefault/formlib/form.py:144
-#: CMFDefault/skins/zpt_content/document_edit_control.py:14
-#: CMFDefault/skins/zpt_content/folder_edit_control.py:9
-#: CMFDefault/skins/zpt_content/link_edit_control.py:13
-#: CMFDefault/skins/zpt_content/newsitem_edit_control.py:15
-#: CMFDefault/skins/zpt_control/folder_bottom_control.py:13
-#: CMFDefault/skins/zpt_control/folder_down_control.py:13
-#: CMFDefault/skins/zpt_control/folder_rename_control.py:17
-#: CMFDefault/skins/zpt_control/folder_top_control.py:13
-#: CMFDefault/skins/zpt_control/folder_up_control.py:13
-#: CMFTopic/skins/zpt_topic/topic_edit_control.py:11
-msgid "Nothing to change."
+#: CMFDefault/browser/document.py:42
+#: CMFDefault/skins/zpt_content/document_edit_template.pt:28
+#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:23
+msgid "structured-text"
 msgstr ""
 
-#: CMFDefault/browser/document.py:49
-#: CMFDefault/browser/metadata.py:223
-#: CMFDefault/formlib/form.py:100
-#: CMFDefault/skins/zpt_content/document_edit_form.py:34
-#: CMFDefault/skins/zpt_content/file_edit_form.py:25
-#: CMFDefault/skins/zpt_content/folder_edit_form.py:24
-#: CMFDefault/skins/zpt_content/image_edit_form.py:25
-#: CMFDefault/skins/zpt_content/link_edit_form.py:24
-#: CMFDefault/skins/zpt_content/metadata_edit_form.py:49
-#: CMFDefault/skins/zpt_content/newsitem_edit_form.py:28
-#: CMFDefault/skins/zpt_generic/password_form.py:37
-#: CMFDefault/skins/zpt_generic/reconfig_form.py:22
-#: CMFTopic/skins/zpt_topic/topic_edit_form.py:26
-#: CMFDefault/skins/zpt_generic/personalize_form.pt:83
-msgid "Change"
+#: CMFDefault/browser/document.py:43
+#: CMFDefault/skins/zpt_content/document_edit_template.pt:31
+#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:26
+msgid "plain text"
 msgstr ""
 
-#: CMFDefault/browser/document.py:54
-#: CMFDefault/browser/metadata.py:235
-#: CMFDefault/formlib/form.py:106
-#: CMFDefault/skins/zpt_content/document_edit_form.py:35
-#: CMFDefault/skins/zpt_content/file_edit_form.py:26
-#: CMFDefault/skins/zpt_content/folder_edit_form.py:25
-#: CMFDefault/skins/zpt_content/image_edit_form.py:26
-#: CMFDefault/skins/zpt_content/link_edit_form.py:25
-#: CMFDefault/skins/zpt_content/metadata_edit_form.py:51
-#: CMFDefault/skins/zpt_content/newsitem_edit_form.py:29
-#: CMFTopic/skins/zpt_topic/topic_edit_form.py:27
-msgid "Change and View"
+#: CMFDefault/browser/document.py:44
+#: CMFDefault/skins/zpt_content/document_edit_template.pt:34
+#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:29
+msgid "html"
 msgstr ""
 
+#: CMFDefault/browser/document.py:57
 #: CMFDefault/browser/favorite.py:39
 #: CMFDefault/browser/favorite.py:40
 #: CMFDefault/browser/link.py:42
 #: CMFDefault/browser/link.py:43
 #: CMFDefault/browser/metadata.py:54
 #: CMFDefault/browser/metadata.py:79
-#: CMFDefault/browser/templates/document_edit.pt:13
-#: CMFDefault/browser/templates/newsitem_edit.pt:12
+#: CMFDefault/browser/newsitem.py:41
 #: CMFDefault/skins/zpt_content/document_edit_template.pt:16
 #: CMFDefault/skins/zpt_content/file_edit_template.pt:15
 #: CMFDefault/skins/zpt_content/folder_edit_template.pt:15
@@ -168,6 +139,41 @@
 msgid "Title"
 msgstr ""
 
+#: CMFDefault/browser/document.py:61
+#: CMFDefault/browser/metadata.py:59
+#: CMFDefault/browser/metadata.py:84
+#: CMFDefault/skins/zpt_content/document_edit_template.pt:20
+#: CMFDefault/skins/zpt_content/file_edit_template.pt:19
+#: CMFDefault/skins/zpt_content/folder_edit_template.pt:22
+#: CMFDefault/skins/zpt_content/image_edit_template.pt:19
+#: CMFDefault/skins/zpt_content/metadata_edit_template.pt:44
+#: CMFDefault/skins/zpt_generic/metadata_help.pt:52
+#: CMFDefault/skins/zpt_generic/search_form_template.pt:67
+#: CMFTopic/skins/zpt_topic/topic_edit_template.pt:22
+msgid "Description"
+msgstr ""
+
+#: CMFDefault/browser/document.py:65
+#: CMFDefault/browser/metadata.py:119
+#: CMFDefault/browser/newsitem.py:45
+#: CMFDefault/skins/zpt_content/document_edit_template.pt:24
+#: CMFDefault/skins/zpt_content/metadata_edit_template.pt:90
+#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:19
+#: CMFDefault/skins/zpt_generic/metadata_help.pt:124
+msgid "Format"
+msgstr ""
+
+#: CMFDefault/browser/document.py:69
+#: CMFDefault/skins/zpt_content/document_edit_template.pt:38
+msgid "Upload"
+msgstr ""
+
+#: CMFDefault/browser/document.py:73
+#: CMFDefault/browser/newsitem.py:54
+#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:40
+msgid "Body"
+msgstr ""
+
 #: CMFDefault/browser/favorite.py:44
 #: CMFDefault/browser/link.py:47
 #: CMFDefault/skins/zpt_content/link_edit_template.pt:19
@@ -286,6 +292,24 @@
 msgid "Items moved up."
 msgstr ""
 
+#: CMFDefault/browser/folder.py:359
+#: CMFDefault/browser/folder.py:374
+#: CMFDefault/browser/folder.py:389
+#: CMFDefault/browser/folder.py:404
+#: CMFDefault/formlib/form.py:144
+#: CMFDefault/skins/zpt_content/document_edit_control.py:14
+#: CMFDefault/skins/zpt_content/folder_edit_control.py:9
+#: CMFDefault/skins/zpt_content/link_edit_control.py:13
+#: CMFDefault/skins/zpt_content/newsitem_edit_control.py:15
+#: CMFDefault/skins/zpt_control/folder_bottom_control.py:13
+#: CMFDefault/skins/zpt_control/folder_down_control.py:13
+#: CMFDefault/skins/zpt_control/folder_rename_control.py:17
+#: CMFDefault/skins/zpt_control/folder_top_control.py:13
+#: CMFDefault/skins/zpt_control/folder_up_control.py:13
+#: CMFTopic/skins/zpt_topic/topic_edit_control.py:11
+msgid "Nothing to change."
+msgstr ""
+
 #: CMFDefault/browser/folder.py:361
 #: CMFDefault/browser/folder.py:376
 #: CMFDefault/browser/folder.py:391
@@ -388,16 +412,6 @@
 msgid "Expiration Date"
 msgstr ""
 
-#: CMFDefault/browser/metadata.py:119
-#: CMFDefault/browser/templates/document_edit.pt:21
-#: CMFDefault/browser/templates/newsitem_edit.pt:16
-#: CMFDefault/skins/zpt_content/document_edit_template.pt:24
-#: CMFDefault/skins/zpt_content/metadata_edit_template.pt:90
-#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:19
-#: CMFDefault/skins/zpt_generic/metadata_help.pt:124
-msgid "Format"
-msgstr ""
-
 #: CMFDefault/browser/metadata.py:124
 #: CMFDefault/skins/zpt_content/metadata_edit_template.pt:97
 #: CMFDefault/skins/zpt_generic/metadata_help.pt:149
@@ -415,11 +429,40 @@
 msgid "Properties"
 msgstr ""
 
+#: CMFDefault/browser/metadata.py:223
+#: CMFDefault/formlib/form.py:100
+#: CMFDefault/skins/zpt_content/document_edit_form.py:34
+#: CMFDefault/skins/zpt_content/file_edit_form.py:25
+#: CMFDefault/skins/zpt_content/folder_edit_form.py:24
+#: CMFDefault/skins/zpt_content/image_edit_form.py:25
+#: CMFDefault/skins/zpt_content/link_edit_form.py:24
+#: CMFDefault/skins/zpt_content/metadata_edit_form.py:49
+#: CMFDefault/skins/zpt_content/newsitem_edit_form.py:28
+#: CMFDefault/skins/zpt_generic/password_form.py:37
+#: CMFDefault/skins/zpt_generic/reconfig_form.py:22
+#: CMFTopic/skins/zpt_topic/topic_edit_form.py:26
+#: CMFDefault/skins/zpt_generic/personalize_form.pt:83
+msgid "Change"
+msgstr ""
+
 #: CMFDefault/browser/metadata.py:229
 #: CMFDefault/skins/zpt_content/metadata_edit_form.py:50
 msgid "Change and Edit"
 msgstr ""
 
+#: CMFDefault/browser/metadata.py:235
+#: CMFDefault/formlib/form.py:106
+#: CMFDefault/skins/zpt_content/document_edit_form.py:35
+#: CMFDefault/skins/zpt_content/file_edit_form.py:26
+#: CMFDefault/skins/zpt_content/folder_edit_form.py:25
+#: CMFDefault/skins/zpt_content/image_edit_form.py:26
+#: CMFDefault/skins/zpt_content/link_edit_form.py:25
+#: CMFDefault/skins/zpt_content/metadata_edit_form.py:51
+#: CMFDefault/skins/zpt_content/newsitem_edit_form.py:29
+#: CMFTopic/skins/zpt_topic/topic_edit_form.py:27
+msgid "Change and View"
+msgstr ""
+
 #: CMFDefault/browser/metadata.py:251
 #: CMFDefault/skins/zpt_content/metadata_edit_template.pt:18
 msgid "Default"
@@ -435,20 +478,6 @@
 msgid "On"
 msgstr ""
 
-#: CMFDefault/browser/metadata.py:59
-#: CMFDefault/browser/metadata.py:84
-#: CMFDefault/browser/templates/document_edit.pt:17
-#: CMFDefault/skins/zpt_content/document_edit_template.pt:20
-#: CMFDefault/skins/zpt_content/file_edit_template.pt:19
-#: CMFDefault/skins/zpt_content/folder_edit_template.pt:22
-#: CMFDefault/skins/zpt_content/image_edit_template.pt:19
-#: CMFDefault/skins/zpt_content/metadata_edit_template.pt:44
-#: CMFDefault/skins/zpt_generic/metadata_help.pt:52
-#: CMFDefault/skins/zpt_generic/search_form_template.pt:67
-#: CMFTopic/skins/zpt_topic/topic_edit_template.pt:22
-msgid "Description"
-msgstr ""
-
 #: CMFDefault/browser/metadata.py:70
 #: CMFDefault/skins/zpt_content/metadata_edit_template.pt:15
 msgid "Enable Discussion?"
@@ -473,8 +502,8 @@
 msgstr ""
 
 #: CMFDefault/browser/newsitem.py:49
-#: CMFDefault/skins/zpt_content/newsitem_edit_control.py:11
-msgid "News Item changed."
+#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:33
+msgid "Lead-in"
 msgstr ""
 
 #: CMFDefault/browser/templates/batch_widgets.pt:10
@@ -493,44 +522,6 @@
 msgid "${DYNAMIC_CONTENT}"
 msgstr ""
 
-#: CMFDefault/browser/templates/document_edit.pt:25
-#: CMFDefault/browser/templates/newsitem_edit.pt:20
-#: CMFDefault/skins/zpt_content/document_edit_template.pt:28
-#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:23
-msgid "structured-text"
-msgstr ""
-
-#: CMFDefault/browser/templates/document_edit.pt:28
-#: CMFDefault/browser/templates/newsitem_edit.pt:23
-#: CMFDefault/skins/zpt_content/document_edit_template.pt:31
-#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:26
-msgid "plain text"
-msgstr ""
-
-#: CMFDefault/browser/templates/document_edit.pt:31
-#: CMFDefault/browser/templates/newsitem_edit.pt:26
-#: CMFDefault/skins/zpt_content/document_edit_template.pt:34
-#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:29
-msgid "html"
-msgstr ""
-
-#: CMFDefault/browser/templates/document_edit.pt:35
-#: CMFDefault/skins/zpt_content/document_edit_template.pt:38
-msgid "Upload"
-msgstr ""
-
-#: CMFDefault/browser/templates/document_edit.pt:5
-#: CMFDefault/browser/templates/newsitem_edit.pt:5
-#: CMFDefault/skins/zpt_content/document_edit_template.pt:5
-#: CMFDefault/skins/zpt_content/file_edit_template.pt:5
-#: CMFDefault/skins/zpt_content/folder_edit_template.pt:5
-#: CMFDefault/skins/zpt_content/image_edit_template.pt:5
-#: CMFDefault/skins/zpt_content/link_edit_template.pt:5
-#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:5
-#: CMFTopic/skins/zpt_topic/topic_edit_template.pt:5
-msgid "Edit: ${obj_title}"
-msgstr ""
-
 #: CMFDefault/browser/templates/folder_contents.pt:11
 #: CMFDefault/skins/zpt_generic/folder_contents_template.pt:15
 msgid "[Link]"
@@ -581,16 +572,6 @@
 msgid "Link: ${link}"
 msgstr ""
 
-#: CMFDefault/browser/templates/newsitem_edit.pt:30
-#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:33
-msgid "Lead-in"
-msgstr ""
-
-#: CMFDefault/browser/templates/newsitem_edit.pt:37
-#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:40
-msgid "Body"
-msgstr ""
-
 #: CMFDefault/browser/utils.py:273
 msgid "Previous item"
 msgstr ""
@@ -620,7 +601,7 @@
 msgstr ""
 
 #: CMFDefault/exceptions.py:68
-#: CMFDefault/formlib/widgets.py:52
+#: CMFDefault/formlib/widgets.py:62
 msgid "Invalid email address."
 msgstr ""
 
@@ -629,10 +610,6 @@
 msgid "Edit ${obj_type}"
 msgstr ""
 
-#: CMFDefault/formlib/form.py:127
-msgid "This resource is locked via webDAV."
-msgstr ""
-
 #: CMFDefault/formlib/form.py:141
 #. Default: ""
 msgid "${obj_type} changed."
@@ -943,6 +920,20 @@
 msgid "Submit ${objectid} for Review."
 msgstr ""
 
+#: CMFDefault/skins/zpt_content/document_edit_control.py:10
+msgid "Document changed."
+msgstr ""
+
+#: CMFDefault/skins/zpt_content/document_edit_template.pt:5
+#: CMFDefault/skins/zpt_content/file_edit_template.pt:5
+#: CMFDefault/skins/zpt_content/folder_edit_template.pt:5
+#: CMFDefault/skins/zpt_content/image_edit_template.pt:5
+#: CMFDefault/skins/zpt_content/link_edit_template.pt:5
+#: CMFDefault/skins/zpt_content/newsitem_edit_template.pt:5
+#: CMFTopic/skins/zpt_topic/topic_edit_template.pt:5
+msgid "Edit: ${obj_title}"
+msgstr ""
+
 #: CMFDefault/skins/zpt_content/file_edit_control.py:8
 msgid "File changed."
 msgstr ""
@@ -997,6 +988,10 @@
 msgid "Resource Metadata: ${obj_title}"
 msgstr ""
 
+#: CMFDefault/skins/zpt_content/newsitem_edit_control.py:11
+msgid "News Item changed."
+msgstr ""
+
 #: CMFDefault/skins/zpt_content/transition_form.pt:11
 msgid "Transition of ${objectid}."
 msgstr ""
@@ -1136,7 +1131,6 @@
 msgstr ""
 
 #: CMFDefault/skins/zpt_generic/discussion_reply_form.py:41
-#: CMFDefault/browser/templates/document_edit.pt:41
 #: CMFDefault/skins/zpt_content/document_edit_template.pt:44
 msgid "Edit"
 msgstr ""
@@ -2102,16 +2096,16 @@
 msgid "Query Parameters:"
 msgstr ""
 
-#: DCWorkflow/DCWorkflow.py:280
+#: DCWorkflow/DCWorkflow.py:281
 msgid "Object is in an undefined state."
 msgstr ""
 
-#: DCWorkflow/DCWorkflow.py:285
+#: DCWorkflow/DCWorkflow.py:286
 #. Default: ""
 msgid "Transition '${action_id}' is not triggered by a user action."
 msgstr ""
 
-#: DCWorkflow/DCWorkflow.py:468
+#: DCWorkflow/DCWorkflow.py:469
 #. Default: ""
 msgid "Destination state undefined: ${state_id}"
 msgstr ""

Modified: CMF/trunk/CMFDefault/skins/zpt_generic/zpt_stylesheet.css
===================================================================
--- CMF/trunk/CMFDefault/skins/zpt_generic/zpt_stylesheet.css	2007-02-04 14:54:50 UTC (rev 72361)
+++ CMF/trunk/CMFDefault/skins/zpt_generic/zpt_stylesheet.css	2007-02-04 15:31:51 UTC (rev 72362)
@@ -448,6 +448,8 @@
 .widget label    { width: 108px; float: left; padding: 0 4px;
                    min-height: 17px; height: auto !important; height: 17px;
                    text-align: right; font-size: 80%; font-weight: bold; }
+.widget .data label { width: auto; float: none;
+                      font-size: inherit; font-weight: normal; }
 .widget .field   { width: 604px; float: left;
                    font-family: inherit; font-size: inherit; }
 .widget.split .field { width: 244px; }
@@ -455,13 +457,16 @@
                    min-height: 30px; height: auto !important; height: 30px;
                    background-color: #ffffff; }
 .widget.split .data { width: 240px; }
-.widget input.textType, .widget select, .widget textarea {
-                   width: 596px; margin: 0 -2px; padding: 0 2px;
+.widget input.fileType,
+.widget input.textType,
+.widget select,
+.widget textarea { width: 596px; margin: 0 -2px; padding: 0 2px;
                    font-family: inherit; font-size: inherit; }
 .widget select   { padding: 0 }
 .widget input.textType {
                    min-height: 30px; height: auto !important; height: 24px; }
-.widget.split input.textType, .widget.split textarea { width: 236px; }
+.widget.split input.textType,
+.widget.split textarea { width: 236px; }
 .widget.split select { width: 244px; }
 .widget ul       { margin: 0 0 0 10px; padding: 0 0 0 10px;
                    list-style-type: disc; }



More information about the Checkins mailing list