2 commits - docs/main moksha/api moksha/lib
by John (J5) Palmieri
docs/main/MokshaContainerWidgets.rst | 203 ++++++++
moksha/api/widgets/containers/tabbedcontainer.py | 28 -
moksha/api/widgets/containers/templates/dashboardcontainer.mak | 2
moksha/lib/helpers.py | 238 ++++++++--
4 files changed, 428 insertions(+), 43 deletions(-)
New commits:
commit 8ca724eb7825f3f40009363dbff933e926db4926
Merge: 148356d... a9febba...
Author: John (J5) Palmieri <johnp(a)redhat.com>
Date: Tue Feb 3 15:51:38 2009 -0500
Merge branch 'master' of git+ssh://johnp@git.fedorahosted.org/git/moksha
commit 148356d35522065b269d6c6abf359f53c0029096
Author: John (J5) Palmieri <johnp(a)redhat.com>
Date: Tue Feb 3 15:50:55 2009 -0500
add documentation to container widgets
diff --git a/docs/main/MokshaContainerWidgets.rst b/docs/main/MokshaContainerWidgets.rst
new file mode 100644
index 0000000..839d07c
--- /dev/null
+++ b/docs/main/MokshaContainerWidgets.rst
@@ -0,0 +1,203 @@
+Moksha Container Widgets
+========================
+
+Moksha container widgets provide configurable containers for loading Moksha
+applications and widgets. They are used for layout, navigation and dynamic
+loading of content on your site. The configuration format for specifying
+components to load into containers make it easy to dynamically store and update
+configuration information including location and default settings of an
+application. Containers can load applications asynchronously through JavaScript
+or pull in a widget during the initial request. Since containers are widgets
+themselves containers can be nested.
+
+Configuration Format
+--------------------
+
+Configuration for each container is done using a restricted python syntax and
+can be passed to the container as a string or a Python object. Strings are
+mainly used when reading from a configuration file but can also be used to avoid
+having to import the configuration wrapper objects.
+
+Here is an example of what a configuration line could look like:
+
+.. code-block:: python
+
+ dashboard_layout =
+ [Category('Test Applications', [
+ MokshaApp('Hello World 1',
+ 'moksha.helloworld',
+ {'name':'J5'}),
+ App('Hello World 2',
+ '/appz/moksha.helloworld',
+ {'name':'Luke'}),
+ MokshaWidget('Hello World 3',
+ 'moksha.helloworldwidget',
+ {'name':'Anonymous'}, auth=Not(not_anonymous()))
+ ]
+ )
+ ]
+
+This would be a configuration for the dashboard container discussed bellow.
+It defines one category, two applications and a widget. How this is laid out
+in your final view is up to the template and css. The default view is setup
+to load each component into it's category div in order. You can then specify
+CSS to position those categories in your layout.
+
+Lets look at each individual component.
+
+* Category 'Test Applications' - This tells the container that you want to group
+a number of applications under the 'Test Application' title which in essence
+creates a div with class 'test_applications'. You may also want to specify
+css_class instead of relying on the label.
+
+* MokshaApp 'Hello World 1': here we dynamically load the moksha application
+installed on the ``moksha.application`` entry point as ``moksha.helloworld``.
+We then send it a dictionary of keys - in this case the key 'name'.
+
+* App 'Hello World 2': This is the same as the above but instead of giving
+the application name we give it the url the application is mounted on. We
+also send in different configuration data. The App object allows for non
+moksha web apps and static content to be placed in a container.
+
+* MokshaWidget 'Hello World 3': Again this is the same as above but we pass in
+a widget installed on the ``moksha.widget`` entry point as
+``moksha.helloworldwidget``. We also pass in a auth predicate. If the
+authorization evaluates to True, in this case if the user is anonymous, the
+widget will be embedded in the page. If not then it is removed. Predicates
+are defined by the :module:`repoze.what.authorize` module and can be added to
+any ConfigWrapper.
+
+Tabbed Container
+----------------
+
+:class:`moksha.api.widgets.containers.TabbedContainer`
+
+A tabbed container is a container that places components in a jQuery.ui.tabs
+javascript widget. Applications are then dynamically loaded when that tab is
+selected. Widgets placed in a tabbed container are continuously running while
+the tabbed container is active even if not visible. It is important to not load
+widgets that have a high resource requirement. Tabbed containers must be
+subclassed in order to point it to the correct resources.
+
+Here is an example on how to subclass a TabbedContainer:
+
+mainnav.py
+.. code-block:: python
+ from moksha.api.widgets.containers import TabbedContainer
+
+ class MainNav(TabbedContainer):
+ template = 'mako:myapp.templates.mainnav'
+ config_key = 'myapp.mainnav.apps'
+
+mainnav.mak
+.. code-block:: html
+ <div>
+ <ul id="${id}">
+ % for t in tabs:
+ <li>
+ % if t.has_key('url'):
+ <a href="${t['url']}" title="${t['label']} Page">
+ ${t['label']}
+ </a>
+ % else
+ ${t['label']}
+ % endif
+ </li>
+ % endfor
+ </ul>
+ </div>
+ <div id="content">
+ % for t in tabs:
+ <div id="${t['label']}_Page">
+ % if t.has_key('widget'):
+ ${t['widget'](t['params'])}
+ % endif
+ </div>
+ % endfor
+ </div>
+
+development.ini
+.. code-block:: python
+ [DEFAULT]
+ myapp.mainnav.apps = (MokshaApp('Home', 'myapp.home'),
+ MokshaApp('2nd Tab', 'myapp.tab2'),
+ MokshaApp('3rd Tab','myapp.tab3',
+ auth=not_anonymous()),
+ MokshaApp('4th Tab', 'myapp.tab4',
+ auth=Not(not_anonymous())
+ )
+ )
+
+It should be noted that the template boilerplate should be handled automatically
+in the future.
+
+Dashboard Container
+-------------------
+
+:class:`moksha.api.widgets.containers.DashboardContainer`
+
+A dashboard container is a container that places components in a
+jQuery.ui.sortable javascript widget. Applications are dynamically loaded in
+the order they are placed in the configuration. Dashboard containers must be
+subclassed in order to point it to the correct resources.
+
+Here is an example on how to subclass a DashboardContainer:
+
+mainnav.py
+.. code-block:: python
+ from moksha.api.widgets.containers import DashboardContainer
+
+ class HomePageContainer(DashboardContainer):
+ template = 'mako:myapp.templates.homepagecontainer'
+ layout = [Category('left-content-column',
+ [App('Banner', '/static-html/sitebanner.html'),
+ MokshaApp('Stable Updates','myapp.updates/table',
+ {"some_json":'{"status":"stable"}'}
+ ),
+ MokshaApp('Testing Updates','myapp.updates/table',
+ {"some_json":'{"status":"testing"}'}
+ ),
+ ]),
+ Category('right-content-column',
+ MokshaWidget(None, 'myapp.loginwidget',
+ auth=Not(not_anonymous())
+ )
+ )
+ ]
+
+homepagecontainer.mak
+.. code-block:: html
+ <div id="${id}">
+ <div>
+ <div id="right-content-column">
+ ${applist_widget(category = 'right-content-column', layout = layout)}
+ </div>
+ <div id="left-content-column">
+ ${applist_widget(category = 'left-content-column', layout = layout)}
+ </div>
+ </div>
+ </div>
+
+Notice above that I decided to use the layout calls variable instead of a
+configuration key. Either form is acceptable for any container.
+
+Issues
+------
+
+Moving from a model where you piece everything together on the server to
+dynamically loading content in the browser means that there are some issues
+to consider.
+
+* Id's may clash. It is suggested that when using jQuery
+or any other javascript dom tool to generate a uuid and do all of your
+selections relative to that id. It is also suggested you namespace your id's
+and only use classes to style.
+
+* Javascript may load more than once. If all you uses is widgets you are fine
+as ToscaWidgets will take care of duplicate resource requests. However a
+powerful concept in moksha is the ability to load applications asynchronously
+so that the user does not have to wait for the server to finish processing a
+page before any data is streamed to them. It is suggested you make heavy use
+of global resources in order to aleviate the issue. At some point we may
+introduce a way for the browser to filter out already loaded javascript and
+other resources.
\ No newline at end of file
diff --git a/moksha/api/widgets/containers/tabbedcontainer.py b/moksha/api/widgets/containers/tabbedcontainer.py
index 247cd98..1111084 100644
--- a/moksha/api/widgets/containers/tabbedcontainer.py
+++ b/moksha/api/widgets/containers/tabbedcontainer.py
@@ -8,22 +8,26 @@ import urllib
"""
:Name: TabbedContainer
:Type: Container
-
-:Notes: We may need to create a tab javascript object that inherits from
- jQuery.ui.tabs
"""
class TabbedContainer(JQueryUITabs):
"""
:tabs: An ordered list of application tabs to display
- Application descriptors can come is a couple of forms
-
- * tuple - (label, url, {request parameters})
- * App class - App(label = label,
- url = url,
- req_params = {request parameters})
- * MokshaApp class - MokshaApp(label = label,
- application = moksha app name,
- req_params = {request parameters})
+ Application descriptors come from the config wrappers in
+ moksha.lib.helpers
+
+ tabs can either be in serialized string format or as a list of
+ config wrapper objects. Using strings means you don't have to
+ import the wrappers and predicates but can get unwieldy if there
+ is a long list of wrappers
+
+ :config_key: the configuration key used to store the serialized tab config
+ in a configuration file instead of embeding it in the widget
+
+ :template: you must provide a template in order to get styling correct. The
+ default template has minimal functionality. The documentation
+ for jQuery.UI.Tabs can be found at http://ui.jquery.org.
+ FIXME: Write a tutorial and provide helper widgets so
+ creating a template becomes really easy.
"""
css=[] # remove the default css
template = 'mako:moksha.api.widgets.containers.templates.tabbedcontainer'
diff --git a/moksha/api/widgets/containers/templates/dashboardcontainer.mak b/moksha/api/widgets/containers/templates/dashboardcontainer.mak
index 265bfa1..dd4e57d 100644
--- a/moksha/api/widgets/containers/templates/dashboardcontainer.mak
+++ b/moksha/api/widgets/containers/templates/dashboardcontainer.mak
@@ -1,7 +1,7 @@
<div>
Sample Template - you should subclass and create your own layout
<div id="container">
- % for c in layout:
+ % for c in layout:
${applist_widget(category = c)}
% endfor
</div>
diff --git a/moksha/lib/helpers.py b/moksha/lib/helpers.py
index 26510ec..1171aec 100644
--- a/moksha/lib/helpers.py
+++ b/moksha/lib/helpers.py
@@ -1,8 +1,8 @@
from webhelpers import date, feedgenerator, html, number, misc, text
-from repoze.what.predicates import (Not, Predicate, All, Any, has_all_permissions,
- has_any_permission, has_permission,
- in_all_groups, in_any_group, in_group,
+from repoze.what.predicates import (Not, Predicate, All, Any, has_all_permissions,
+ has_any_permission, has_permission,
+ in_all_groups, in_any_group, in_group,
is_user, not_anonymous)
import urllib
@@ -15,8 +15,32 @@ import moksha
scrub_filter = re.compile('[^_a-zA-Z0-9-]')
class ConfigWrapper(object):
+ """ Base class for container configuration wrappers
+
+ Configuration Wrappers are python objects which can be embedded in
+ configuration stores (files and databases) or used directly to
+ insert web applications into containers. Wrappers also integrate
+ repoze.what predicates which are checked when the wrapper is processed.
+ If the prediates return False the web application is not displayed to
+ the user. This makes it easy to build composite Moksha applications
+ where what the user is show is dictated by their authorization level.
+
+ Configuration wrapper are only standard data structures with the ability
+ to process predicates. A container widget is needed to convert the
+ configuration into rendered applications on a page.
+
+ Derive from this class to create new configuration syntax
+
+ """
+
@staticmethod
def process_wrappers(wrappers):
+ """ Helper method for evlauating a list of wrappers
+
+ :returns: a list of hashes for each of the application
+ that will be displayed
+ """
+
result = []
if isinstance(wrappers, ConfigWrapper):
w = wrappers.process()
@@ -27,39 +51,100 @@ class ConfigWrapper(object):
w = item.process()
if w:
result.append(w)
-
+
return result
-
+
def process(self):
+ """Override this in derived classes to return a hash representing
+ the configuration option
+ """
return None
class Category(ConfigWrapper):
- def __init__(self, label="", apps=None, auth=None):
+ """A configuration wrapper class that displays a list of application
+ and/or widget wrappers
+
+ :Example:
+ left_column = Category("Left Column Apps",
+ [App(...), App(...)],
+ not_anonymous())
+ """
+
+ def __init__(self, label="", apps=None, css_class=None, auth=None):
+ """
+ :label: The category heading the container should use when rendering
+ a category. Labels for categories are not rendered in the
+ default template. If a css_class is not defined the label
+ becomes the css class for the div once it has been scrubbed
+ by making it all lower case and replacing illegal characters
+ with underscores.
+ :apps: A list of wrappers representing application configurations
+ :auth: A list of predicates which are evaluate before the wrapper
+ is sent to the container. If it evaluates to False it does not
+ get sent.
+ :css_class: Either a string or list of strings defining the css class
+ for this category
+ """
+
self.label = label
self.apps = apps or []
self.auth = auth or []
-
- def process(self):
+ if not css_class:
+ css_class = scrub_filter.sub('_', self.label.lower())
+ elif isinstance(css_class, list) or isinstance(css_class, tuple):
+ css_class = ' '.join(css_class)
+
+ self.css_class = css_class
+
+ def process(self):
+ """Check the predicates and construct the dict
+
+ :returns: a dict with all the configuration data for this category
+ """
+
if not check_predicates(self.auth):
return None
-
+
id = uuid.uuid4()
- css_class = scrub_filter.sub('_', self.label.lower())
apps = self.process_wrappers(self.apps)
return {'label': self.label, 'apps': apps, 'id': id, 'css_class': css_class}
class App(ConfigWrapper):
+ """A configuration wrapper class that displays an application pointed to
+ by a url
+
+ :Example:
+ hello_app = App('Hello World App',
+ '/appz/moksha.helloworld',
+ {'greeter_name': 'J5'},
+ not_anonymous())
+ """
def __init__(self, label="", url="", req_params=None, auth=None):
+ """
+ :label: The title the application should use when rendering
+ in a container. Leave this blank if you do not wish the
+ application to be visibly labeled when rendered
+ :url: the url to load this application from
+ :req_params: A dict of request parameters to send to the application
+ when loading it from a url
+ :auth: A list of predicates which are evaluate before the wrapper
+ is sent to the container. If it evaluates to False it does not
+ get sent.
+ """
self.label = label
self.url = url
self.req_params = req_params or {}
self.auth = auth or []
-
+
def process(self):
+ """Check the predicates and construct the dict
+
+ :returns: a dict with all the configuration data for this application
+ """
if not check_predicates(self.auth):
return None
-
+
query_str = ""
if self.req_params:
query_str = "?" + urllib.urlencode(self.req_params)
@@ -69,50 +154,131 @@ class App(ConfigWrapper):
return {'label': self.label, 'url': self.url + query_str, 'id': id}
class MokshaApp(App):
+ """A configuration wrapper class that displays a Moksa application
+ selected by name. This differes from App in that you don't have to
+ know the url, only the name the application is mounted under. In
+ future versions we may also try and get the auth predicated from
+ the application's own configuration.
+
+ :Example:
+ hello_app = App('Hello World Moksha App',
+ 'moksha.helloworld',
+ {'greeter_name': 'J5'},
+ not_anonymous())
+
+ """
+
def __init__(self, label="", moksha_app="", req_params=None, auth=None):
+ """
+ :label: The title the application should use when rendering
+ in a container. Leave this blank if you do not wish the
+ application to be visibly labeled when rendered
+ :moksha_app: the name of the entry point registered under the
+ moksha.application section
+ :req_params: A dict of request parameters to send to the application
+ when loading it
+ :auth: A list of predicates which are evaluate before the wrapper
+ is sent to the container. If it evaluates to False it does not
+ get sent.
+ """
# FIXME figure out how to pull auth info from an app
super(MokshaApp, self).__init__(label, '/appz/' + moksha_app, req_params, auth)
class Widget(ConfigWrapper):
+ """A configuration wrapper class that displays a ToscaWidget. Use this
+ instead of an App when the time it takes to load an application from a url
+ is more than the time it takes to just embed it in the template. Widgets
+ also lack the ability to respond to remote requests via controllers.
+
+ :Example:
+ class HelloWidget(tw.api.Widget):
+ pass
+
+ hello_widget = Widget('Hello World Widget',
+ HelloWidget(),
+ {'greeter_name': 'J5'},
+ not_anonymous())
+
+ """
def __init__(self, label="", widget="", params=None, auth=None):
+ """
+ :label: The title the widget should use when rendering
+ in a container. Leave this blank if you do not wish the
+ widget to be visibly labeled when rendered
+ :widget: the ToscaWidget to be rendered
+ :params: A dict of parameters to send to the widgets update_params
+ method when rendering it
+ :auth: A list of predicates which are evaluate before the wrapper
+ is sent to the container. If it evaluates to False it does not
+ get sent.
+ """
self.label = label
self.widget = widget
self.params = params or {}
self.auth = auth or []
-
+
def process(self):
if not check_predicates(self.auth):
return None
-
+
id = uuid.uuid4()
- return {'label': self.label, 'widget': self.widget , 'params':self.params, 'id': id}
+ return {'label': self.label, 'widget': self.widget , 'params':self.params, 'id': id}
class MokshaWidget(Widget):
+ """A configuration wrapper class that displays a ToscaWidget registered
+ in Moksha's widget dictionary.
+
+ :Example:
+ hello_moksha_widget = Widget('Hello Moksha Widget',
+ 'moksha.hello',
+ {'greeter_name': 'J5'},
+ not_anonymous())
+
+ """
def __init__(self, label="", name="", params=None, auth=None):
- print "********************** ", moksha._widgets
+ """
+ :label: The title the widget should use when rendering
+ in a container. Leave this blank if you do not wish the
+ widget to be visibly labeled when rendered
+ :name: the name of the entry point registered under the
+ moksha.widget section
+ :params: A dict of parameters to send to the widgets update_params
+ method when rendering it
+ :auth: A list of predicates which are evaluate before the wrapper
+ is sent to the container. If it evaluates to False it does not
+ get sent.
+ """
widget = moksha._widgets[name]['widget']
- super(MokshaWidget, self).__init__(label=label, widget=widget, params=params, auth=auth)
+ super(MokshaWidget, self).__init__(label=label, widget=widget, params=params, auth=auth)
+
+# setup the dictionary of acceptable callables when eval'ing predicates from
+# a configuration file - this makes up the keywords of the predicate
+# configuration format
_safe_predicate_callables = {
'Not': Not,
'All': All,
- 'Any': Any,
- 'has_all_permissions': has_all_permissions,
- 'has_any_permission': has_any_permission,
- 'has_permission': has_permission,
- 'in_all_groups': in_all_groups,
- 'in_any_group': in_any_group,
- 'in_group': in_group,
- 'is_user': is_user,
+ 'Any': Any,
+ 'has_all_permissions': has_all_permissions,
+ 'has_any_permission': has_any_permission,
+ 'has_permission': has_permission,
+ 'in_all_groups': in_all_groups,
+ 'in_any_group': in_any_group,
+ 'in_group': in_group,
+ 'is_user': is_user,
'not_anonymous': not_anonymous}
+# setup the dictionary of acceptable callables when eval'ing config wrappers
+# from a configuration file - this makes up the keywords of the config wrapper
+# configuration format
_app_config_callables = {'Category': Category,
'MokshaApp': MokshaApp,
'Widget': Widget,
'App': App,
'MokshaWidget': MokshaWidget}
+# config wrapper configuration can also utilize predicates
_app_config_callables.update(_safe_predicate_callables)
@@ -120,24 +286,28 @@ def eval_app_config(config_str):
"""
Safely evaluates application configuration strings for storing application
layout data in a moksha configuration file
+
+ :return: the evaluated configuration wrapper configuration
"""
return eval(config_str, {"__builtins__":None}, _app_config_callables)
def eval_predicates(predicate_str):
- """
+ """
Safely evaluates a string of predicates
+
+ :return: the evaluated predicate configuration
"""
return eval(predicate_str, {"__builtins__":None}, _safe_predicate_callables)
def check_predicates(predicates):
"""
- Using the current WSGI environment evaluate a list of predicates.
+ Using the current WSGI environment run a list of predicates.
This can only be used when inside a WSGI request
- Return False is any one is False
- Return True if they are all True
+ :return: False is any one is False
+ :return: True if they are all True
"""
from pylons import request
@@ -148,7 +318,7 @@ def check_predicates(predicates):
return False
return True
-
+
if(isinstance(predicates, Predicate)):
return predicates.eval_with_environ(request.environ)
@@ -156,6 +326,14 @@ def check_predicates(predicates):
def eval_and_check_predicates(predicate_str):
+ """
+ Using the current WSGI environment evaluate a predicate sting and run
+ the resulting predicate list. This can only be used when inside a WSGI
+ request
+
+ :return: False is any one is False
+ :return: True if they are all True
+ """
p = eval_predicates(predicate_str)
return check_predicates(p)
15 years, 4 months
moksha/api
by Luke Macken
moksha/api/widgets/chat/controllers/chat.py | 1 -
1 file changed, 1 deletion(-)
New commits:
commit a9febba1946b82f72b6541adc33846ac6acd18d3
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:32:36 2009 -0500
Remove an unnecessary print statement
diff --git a/moksha/api/widgets/chat/controllers/chat.py b/moksha/api/widgets/chat/controllers/chat.py
index 18e9df1..5017262 100644
--- a/moksha/api/widgets/chat/controllers/chat.py
+++ b/moksha/api/widgets/chat/controllers/chat.py
@@ -108,7 +108,6 @@ class ChatController(BaseController):
opts['roomId'] = request.headers.get('referer', 'default')
opts['protocol'] = self.config['backendProtocol']
opts['addr'] = self.config['backendAddr']
- print "op_info returning [True, %r]" % opts
return [True, opts]
def op_not_found(self):
15 years, 4 months
moksha/controllers
by Luke Macken
moksha/controllers/root.py | 2 ++
1 file changed, 2 insertions(+)
New commits:
commit b08c956afce2ef112210b8555ccfa242488938ba
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:20:50 2009 -0500
Reset the overridden template
diff --git a/moksha/controllers/root.py b/moksha/controllers/root.py
index 95cac52..8a5a8b3 100644
--- a/moksha/controllers/root.py
+++ b/moksha/controllers/root.py
@@ -49,6 +49,8 @@ class RootController(BaseController):
if not chrome or not getattr(tmpl_context.widget, 'visible', True):
override_template(self.widgets, 'mako:moksha.templates.widget')
return dict(options={})
+ else:
+ override_template(self.widgets, 'mako:moksha.templates.widgetcontainer')
tmpl_context.container = container
kw['id'] = widget + '_container'
return dict(title=moksha._widgets[widget]['name'], container_options=kw)
15 years, 4 months
moksha/api
by Luke Macken
moksha/api/widgets/chat/controllers/chat.py | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
New commits:
commit 102d8e3443093036da5b83b9371d65b7e3aadd3b
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:16:21 2009 -0500
Support Python 2.4's urlparse
diff --git a/moksha/api/widgets/chat/controllers/chat.py b/moksha/api/widgets/chat/controllers/chat.py
index 05f5565..18e9df1 100644
--- a/moksha/api/widgets/chat/controllers/chat.py
+++ b/moksha/api/widgets/chat/controllers/chat.py
@@ -41,8 +41,13 @@ class ChatController(BaseController):
log.info('No `chat.backend` defined; disabling chat functionality')
return
backend = urlparse.urlparse(backend)
- self.config['backendProtocol'] = backend.scheme
- self.config['backendAddr'] = [backend.hostname, backend.port]
+ if hasattr(backend, 'scheme'): # Python 2.5+
+ self.config['backendProtocol'] = backend.scheme
+ self.config['backendAddr'] = [backend.hostname, backend.port]
+ else: # Python 2.4
+ self.config['backendProtocol'] = backend[0]
+ host, port = backend[1].split(':')
+ self.config['backendAddr'] = [host, port]
self.config['startBuiltin'] = config.get('chat.builtin', False)
self.config['rooms'] = {}
display_opts = {
15 years, 4 months
11 commits - development.ini docs/conf.py docs/_static moksha/api moksha/controllers moksha/middleware moksha/streams moksha/templates orbited.cfg setup.py
by Luke Macken
development.ini | 17
docs/_static/moksha-architecture.png |binary
docs/conf.py | 8
moksha/api/menus/default.py | 1
moksha/api/menus/widgets.py | 1
moksha/api/widgets/chat/__init__.py | 2
moksha/api/widgets/chat/chat.py | 28 -
moksha/api/widgets/chat/controllers/__init__.py | 1
moksha/api/widgets/chat/controllers/chat.py | 110 ++++
moksha/api/widgets/chat/static/bootstrap.js | 431 ----------------
moksha/api/widgets/chat/static/chat.css | 119 ++++
moksha/api/widgets/chat/static/index.html | 9
moksha/api/widgets/chat/static/themes/dojo/chrome.js | 83 +++
moksha/api/widgets/chat/static/themes/dojo/gui.js | 19
moksha/api/widgets/chat/static/themes/dojo/index.html | 226 ++++++++
moksha/api/widgets/chat/static/themes/dojo/style.css | 151 +++++
moksha/api/widgets/chat/static/themes/simple/chrome.js | 83 +++
moksha/api/widgets/chat/static/themes/simple/gui.js | 216 ++++++++
moksha/api/widgets/chat/static/themes/simple/index.html | 58 ++
moksha/api/widgets/chat/static/themes/simple/style.css | 151 +++++
moksha/api/widgets/chat/templates/bootstrap.mak | 420 +++++++++++++++
moksha/api/widgets/chat/templates/chat.mak | 31 +
moksha/api/widgets/feed/live.py | 2
moksha/controllers/root.py | 15
moksha/middleware/middleware.py | 21
moksha/streams/demo/demostream.py | 2
moksha/templates/widget.mak | 3
moksha/templates/widgetcontainer.mak | 7
orbited.cfg | 1
setup.py | 3
30 files changed, 1748 insertions(+), 471 deletions(-)
New commits:
commit 64d974bb47bf8bbc3c6c6605d34f4e0c0a4e888f
Merge: 8beb002... d299d2f...
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:08:10 2009 -0500
Merge branch 'master' of git+ssh://git.fedorahosted.org/git/moksha
commit 8beb002390520492912c877800fc183d5a7f19bc
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:07:51 2009 -0500
Some minor tweaks
diff --git a/docs/conf.py b/docs/conf.py
index 0dfe727..c774831 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -155,7 +155,7 @@ code_path = test_path = os.path.dirname(os.path.abspath(__file__)) + os.path.sep
#
# Enable the ToscaWidgets WidgetBrowser
#
-try:
- extensions.append("widgetbrowser.sphinx_ext")
-except ImportError:
- print "Unable to import the WidgetBrowser; skipping."
+#try:
+# extensions.append("widgetbrowser.sphinx_ext")
+#except ImportError:
+# print "Unable to import the WidgetBrowser; skipping."
diff --git a/moksha/api/menus/default.py b/moksha/api/menus/default.py
index 920440c..a420036 100644
--- a/moksha/api/menus/default.py
+++ b/moksha/api/menus/default.py
@@ -32,6 +32,7 @@ class MokshaContextMenu(MokshaContextualMenu):
<a href="https://fedorahosted.org/moksha/">Wiki</a>
"""
+
class MokshaDefaultMenu(MokshaMenu):
menus = ['Moksha', 'Applications', 'Widgets', 'Fedora']
diff --git a/moksha/api/menus/widgets.py b/moksha/api/menus/widgets.py
index 97c3618..48db2e9 100644
--- a/moksha/api/menus/widgets.py
+++ b/moksha/api/menus/widgets.py
@@ -44,7 +44,6 @@ class MokshaMenuBase(Widget):
'hasImages', 'fadeTime', 'adjustLeft', 'adjustTop', 'opacity',
'shadow', 'fadeInTime', 'fadeOutTime', 'overflow', 'effect']
-
rootMenuSelector = 'rootVoices'
menuSelector = 'menuContainer'
callback = '/appz/menu'
diff --git a/moksha/api/widgets/feed/live.py b/moksha/api/widgets/feed/live.py
index bf16e2d..3f3c0c1 100644
--- a/moksha/api/widgets/feed/live.py
+++ b/moksha/api/widgets/feed/live.py
@@ -37,4 +37,4 @@ class LiveFeedWidget(LiveWidget):
def update_params(self, d):
super(LiveFeedWidget, self).update_params(d)
- d['feed'] = Feed(d['id'], url=d.get('url', 'http://lewk.org/rss'))
+ d['feed'] = Feed(d['id'], url=d.get('url', 'http://doggdot.us/rss'))
diff --git a/moksha/streams/demo/demostream.py b/moksha/streams/demo/demostream.py
index f8b38c5..ab47a6b 100644
--- a/moksha/streams/demo/demostream.py
+++ b/moksha/streams/demo/demostream.py
@@ -19,7 +19,7 @@ class MokshaDemoDataStream(PollingDataStream):
n = 0
# Feed demo specific variables
- feed_entries = Feed(url='http://lewk.org/rss').entries()
+ feed_entries = Feed(url='http://doggdot.us/rss').entries()
i = 0
def poll(self):
commit 49400c85b418c7676e52fd1ed00213594931bbd5
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:06:49 2009 -0500
Add the chat app and widget to our entrypoints
diff --git a/setup.py b/setup.py
index d86914d..5892dcd 100644
--- a/setup.py
+++ b/setup.py
@@ -53,11 +53,12 @@ setup(
[moksha.application]
menu = moksha.api.menus.controllers:MokshaMenuController
+ chat = moksha.api.widgets.chat.controllers:ChatController
[moksha.widget]
liveflot = moksha.api.widgets.flot:LiveFlotWidget
livefeed = moksha.api.widgets.feed.live:LiveFeedWidget
- #chat = moksha.api.widgets.chat:LiveChatWidget
+ chat = moksha.api.widgets.chat:LiveChatWidget
#livegraph = moksha.examples.livegraph:LiveGraphWidget
#grid = moksha.api.widgets:Grid
commit 5a250a608711f7ddd8bfdeef402aa47c1102cd75
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:06:35 2009 -0500
Clean up our orbited configuration
diff --git a/orbited.cfg b/orbited.cfg
index c943345..6006d84 100644
--- a/orbited.cfg
+++ b/orbited.cfg
@@ -8,7 +8,6 @@ stomp://:61613
[access]
* -> localhost:61613
-* -> localhost:9999
* -> irc.freenode.net:6667
[global]
commit 3c5e7213c0e56773e738db1621832445479889ba
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:06:03 2009 -0500
Allow /widgets to render without the container chrome.
diff --git a/moksha/controllers/root.py b/moksha/controllers/root.py
index b51cdfb..95cac52 100644
--- a/moksha/controllers/root.py
+++ b/moksha/controllers/root.py
@@ -18,7 +18,8 @@
import moksha
-from tg import expose, flash, require, tmpl_context, redirect, validate
+from tg import (expose, flash, require, tmpl_context, redirect, validate,
+ override_template)
from tg.controllers import WSGIAppController
from repoze.what import predicates
@@ -40,15 +41,17 @@ class RootController(BaseController):
def index(self):
tmpl_context.menu_widget = moksha.menus['default_menu']
tmpl_context.contextual_menu_widget = moksha.menus['contextual_menu']
- return {'title': 'Moksha'}
+ return dict(title='Moksha')
- @expose('mako:moksha.templates.widget')
- def widgets(self, widget, **kw):
+ @expose('mako:moksha.templates.widgetcontainer')
+ def widgets(self, widget, chrome=True, **kw):
tmpl_context.widget = moksha.get_widget(widget)
+ if not chrome or not getattr(tmpl_context.widget, 'visible', True):
+ override_template(self.widgets, 'mako:moksha.templates.widget')
+ return dict(options={})
tmpl_context.container = container
kw['id'] = widget + '_container'
- return {'title': moksha._widgets[widget]['name'],
- 'container_options': kw}
+ return dict(title=moksha._widgets[widget]['name'], container_options=kw)
@expose('moksha.templates.login')
def login(self, **kw):
diff --git a/moksha/templates/widget.mak b/moksha/templates/widget.mak
index 57e436f..a66cfcd 100644
--- a/moksha/templates/widget.mak
+++ b/moksha/templates/widget.mak
@@ -1,7 +1,6 @@
<html>
<head></head>
<body>
- ${tmpl_context.container(content=tmpl_context.widget, title=title,
- **container_options)}
+ ${tmpl_context.widget(**options)}
</body>
</html>
commit c31faf7968ef53af0d15c9917785111e34046313
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:04:24 2009 -0500
Add a default chat configuration to our development.ini
diff --git a/development.ini b/development.ini
index bdb6165..78962c9 100644
--- a/development.ini
+++ b/development.ini
@@ -38,6 +38,23 @@ stomp_pass = guest
# Optional AMQP Broker.
#amqp_broker = guest/guest@localhost
+# Moksha chat configuration
+
+# Use a built-in IRC server
+#chat.backend = irc://localhost:9999
+#chat.builtin = true
+chat.backend = irc://irc.freenode.net:6667
+chat.rooms = default
+chat.default.staticRoomName = moksha
+chat.default.roomAssignmentMode = static
+chat.default.display.greeting = Moksha Chat
+#chat.default.display.floating = true
+#chat.default.display.floatingToggle = false
+#chat.default.display.width = 400
+#chat.default.display.height = 300
+#chat.default.display.theme = simple
+#chat.default.display.resizable = true
+
[server:main]
use = egg:Paste#http
host = localhost
commit 212c3c946fd05152c0d7c3f98332a273730623d9
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:03:43 2009 -0500
Add a widgetcontainer mako template
diff --git a/moksha/templates/widgetcontainer.mak b/moksha/templates/widgetcontainer.mak
new file mode 100644
index 0000000..57e436f
--- /dev/null
+++ b/moksha/templates/widgetcontainer.mak
@@ -0,0 +1,7 @@
+<html>
+ <head></head>
+ <body>
+ ${tmpl_context.container(content=tmpl_context.widget, title=title,
+ **container_options)}
+ </body>
+</html>
commit 78bc597ed13968bd484ef92b6472aad40f9d6105
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:03:20 2009 -0500
Improve our moksha-architecture image
diff --git a/docs/_static/moksha-architecture.png b/docs/_static/moksha-architecture.png
index 6d6001c..43a0e3c 100644
Binary files a/docs/_static/moksha-architecture.png and b/docs/_static/moksha-architecture.png differ
commit 7c8ccb1ec23b3ae4052808f6479257a953f71aca
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 17:02:36 2009 -0500
Add a live chat widget
diff --git a/moksha/api/widgets/chat/__init__.py b/moksha/api/widgets/chat/__init__.py
index a5db829..ea5396b 100644
--- a/moksha/api/widgets/chat/__init__.py
+++ b/moksha/api/widgets/chat/__init__.py
@@ -1 +1 @@
-from chat import LiveChatWidget
+from chat import LiveChatWidget, LiveChatFrameWidget
diff --git a/moksha/api/widgets/chat/chat.py b/moksha/api/widgets/chat/chat.py
index 2fb1b35..d05a2ff 100644
--- a/moksha/api/widgets/chat/chat.py
+++ b/moksha/api/widgets/chat/chat.py
@@ -16,10 +16,34 @@
# Copyright 2008, Red Hat, Inc.
# Authors: Luke Macken <lmacken(a)redhat.com>
-from tw.api import Widget, JSLink
+from tw.api import Widget, JSLink, CSSLink
+from tw.jquery import jquery_js
+
+from moksha.api.widgets.orbited import orbited_js
+
+irc2_js = JSLink(filename='static/irc2.js',
+ javascript=[orbited_js],
+ modname=__name__)
+
+willowchat_js = JSLink(filename='static/willowchat.js',
+ javascript=[jquery_js, irc2_js],
+ modname=__name__)
+
+gui_js = JSLink(filename='static/gui.js',
+ javascript=[willowchat_js],
+ modname=__name__)
+
+willowchat_css = CSSLink(filename='static/style.css', modname=__name__)
+
class LiveChatWidget(Widget):
params = ['bootstrap']
- bootstrap = JSLink(filename='static/bootstrap.js', modname=__name__)
+ bootstrap = JSLink(link='/appz/chat/bootstrap')
template = '<div id="willowchat" reposition="true">${bootstrap}</div>'
visible = False
+
+
+class LiveChatFrameWidget(Widget):
+ template = 'mako:moksha.api.widgets.chat.templates.chat'
+ javascript = [orbited_js, willowchat_js, irc2_js, gui_js, jquery_js]
+ css = [willowchat_css]
diff --git a/moksha/api/widgets/chat/controllers/__init__.py b/moksha/api/widgets/chat/controllers/__init__.py
new file mode 100644
index 0000000..67c0135
--- /dev/null
+++ b/moksha/api/widgets/chat/controllers/__init__.py
@@ -0,0 +1 @@
+from chat import ChatController
diff --git a/moksha/api/widgets/chat/controllers/chat.py b/moksha/api/widgets/chat/controllers/chat.py
new file mode 100644
index 0000000..05f5565
--- /dev/null
+++ b/moksha/api/widgets/chat/controllers/chat.py
@@ -0,0 +1,110 @@
+# This file is part of Moksha.
+#
+# Moksha is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Moksha is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Moksha. If not, see <http://www.gnu.org/licenses/>.
+#
+# Copyright 2008, Red Hat, Inc.
+# Authors: Luke Macken <lmacken(a)redhat.com>
+
+import logging
+log = logging.getLogger(__name__)
+
+import urlparse
+urlparse.uses_netloc.append('irc')
+
+from tg import expose, tmpl_context, config
+from pylons import request
+
+from moksha.lib.base import BaseController
+from moksha.api.widgets.chat import LiveChatFrameWidget
+
+chat_frame_widget = LiveChatFrameWidget('chat_frame')
+
+
+class ChatController(BaseController):
+
+ def __init__(self, *args, **kw):
+ super(ChatController, self).__init__(*args, **kw)
+ self.config = {}
+ backend = config.get('chat.backend')
+ if not backend:
+ log.info('No `chat.backend` defined; disabling chat functionality')
+ return
+ backend = urlparse.urlparse(backend)
+ self.config['backendProtocol'] = backend.scheme
+ self.config['backendAddr'] = [backend.hostname, backend.port]
+ self.config['startBuiltin'] = config.get('chat.builtin', False)
+ self.config['rooms'] = {}
+ display_opts = {
+ 'floating': True, 'floatingToggle': False, 'width': 400 ,
+ 'height': 300, 'theme': 'simple', 'resizable': True,
+ 'greeting': 'Moksha Chat',
+ }
+ for room in config['chat.rooms'].replace(',', ' ').split():
+ self.config['rooms'][room] = {'display': {}}
+ for display_opt, default in display_opts.items():
+ self.config['rooms'][room]['display'][display_opt] = \
+ config.get('chat.%s.display.%s' % (room, display_opt),
+ default)
+ self.config['rooms'][room]['roomAssignmentMode'] = \
+ config.get('chat.%s.roomAssignmentMode' % room, 'static')
+ self.config['rooms'][room]['staticRoomName'] = \
+ config.get('chat.%s.staticRoomName' % room, room)
+
+ log.debug('Chat config = %r' % self.config)
+
+ @expose('mako:moksha.templates.widget')
+ def index(self, *args, **kw):
+ tmpl_context.widget = chat_frame_widget
+ return dict(options={})
+
+ @expose('mako:moksha.api.widgets.chat.templates.bootstrap',
+ content_type='text/javascript')
+ def bootstrap(self, *args, **kw):
+ return dict(host=request.headers['host'])
+
+ @expose('json')
+ def rooms(self, roomClass='default', op=None, jsonp=None):
+ opts = None
+ if op:
+ opts = self.jsonp(getattr(self, 'op_%s' % op,
+ self.op_not_found)(roomClass), jsonp)
+ else:
+ opts = self.jsonp([False, ["op not specified"]], jsonp)
+ return opts
+
+ def jsonp(self, result, jsonp=None):
+ result = str(result).replace('True', 'true').replace('False', 'false')
+ if jsonp:
+ result = jsonp + '(' + result + ')'
+ return result
+
+ def op_info(self, roomClass):
+ if roomClass not in self.config['rooms']:
+ return [False, ['unknown room class: ' + roomClass]]
+ info = self.config['rooms'][roomClass]
+ displayInfo = info.get('display', {})
+ opts = {}
+ opts.update(displayInfo)
+ mode = info.get('roomAssignmentMode', 'static')
+ if mode == 'static':
+ opts['roomId'] = info.get('staticRoomName', 'default')
+ elif mode == 'referrer':
+ opts['roomId'] = request.headers.get('referer', 'default')
+ opts['protocol'] = self.config['backendProtocol']
+ opts['addr'] = self.config['backendAddr']
+ print "op_info returning [True, %r]" % opts
+ return [True, opts]
+
+ def op_not_found(self):
+ return [False, ['unrecognized op']]
diff --git a/moksha/api/widgets/chat/static/bootstrap.js b/moksha/api/widgets/chat/static/bootstrap.js
deleted file mode 100644
index ea0ac1f..0000000
--- a/moksha/api/widgets/chat/static/bootstrap.js
+++ /dev/null
@@ -1,431 +0,0 @@
-(function() {
-var gatherOptions = function(roomOptions, container) {
- console.log('grandma');
- var opts = {}
- var containerOptions = [ 'width', 'height', 'roomClass', 'reposition', 'theme' ]
- for (var key in roomOptions) {
- opts[key] = roomOptions[key]
- }
- for (var i = 0; i < containerOptions.length; ++i) {
- var key = containerOptions[i]
- var option = container.getAttribute(key)
- if (option) {
- opts[key] = option
- }
- }
- if (!opts.roomId) {
- var roomId = container.getAttribute('roomId')
- opts.roomId = roomId ? roomId : 'default'
- }
- if (!opts.width) {
- opts.width = 350;
- }
- if (!opts.height) {
- opts.height = 240;
- }
- if (!opts.theme) {
- opts.theme = 'simple'
- }
- if (!opts.greeting) {
- opts.greeting = "Willow Chat"
- }
- return opts
-}
-
-var loaded = function() {
- if (!window.WillowChat) {
- WillowChat = {}
- }
- if (!WillowChat.util) {
- WillowChat.util = {}
- }
- var container = document.getElementById('willowchat');
- if (!container) {
- container = document.createElement('div');
- container.setAttribute('id', 'willowchat');
- }
-
-
- var roomClass = container.getAttribute('roomClass');
- var roomClass= roomClass ? roomClass : 'default'
- s = document.createElement('script')
- //var baseurl = 'http://' + WillowChat.hostHeader
- // @@ HACK
- var baseurl = 'http://localhost:8080';
- var orbitedurl = 'http://localhost:8007';
- s.src = orbitedurl + '/rooms?jsonp=WillowChat.util.optionsCallback&op=info&roomClass=' + roomClass
-
- WillowChat.util.optionsCallback = function(result) {
- success = result[0]
- if (!success) {
- throw new Error("Failed: " + result[1])
- }
- room = result[1]
- var opts = gatherOptions(room, container);
- ifr = document.createElement('iframe')
- ifr.src = baseurl + '/toscawidgets/resources/moksha.live.chat.chat/static/index.html?json=1&opts=' + escape(JSON.stringify(opts))
- ifr.style.border = "0px";
- ifr.style.width = "100%";
- ifr.style.height = "100%";
- var chromeCallback = function() {
- container.appendChild(ifr)
- chrome = new WillowChat.chrome.map[opts.theme](container, opts)
- }
- var s = document.createElement('script')
- // TODO: whats the deal with this? Can't we do any better?
- s.src = baseurl + '/toscawidgets/resources/moksha.live.chat.chat/static/chrome.js?ienocache=' + Math.floor(Math.random()*10000)
- if (s.addEventListener) {
- s.addEventListener('load', chromeCallback, false)
- }
- else if (s.attachEvent) {
- s.attachEvent('onreadystatechange', function() {
- if (s.readyState == "loaded") {
- chromeCallback()
- }
- })
- }
- if (true) {
- var k = 3 + 3
- }
-// var d = document.createElement('div')
-// d.innerHTML = 'go'
-// document.body.appendChild(d)
- document.getElementsByTagName('head')[0].appendChild(s)
- }
- document.getElementsByTagName('head')[0].appendChild(s)
-
-}
-
-
-$(document).ready(function() {
- loaded();
-});
-
-/*
-if (window.addEventListener) {
- window.addEventListener('load', loaded, false);
-}
-else if (window.attachEvent) {
- window.attachEvent('onload', loaded);
-}
-*/
-
-})();
-if (!this.JSON) {
-
-// Create a JSON object only if one does not already exist. We create the
-// object in a closure to avoid creating global variables.
-
- JSON = function () {
-
- function f(n) {
- // Format integers to have at least two digits.
- return n < 10 ? '0' + n : n;
- }
-
- Date.prototype.toJSON = function (key) {
-
- return this.getUTCFullYear() + '-' +
- f(this.getUTCMonth() + 1) + '-' +
- f(this.getUTCDate()) + 'T' +
- f(this.getUTCHours()) + ':' +
- f(this.getUTCMinutes()) + ':' +
- f(this.getUTCSeconds()) + 'Z';
- };
-
- String.prototype.toJSON =
- Number.prototype.toJSON =
- Boolean.prototype.toJSON = function (key) {
- return this.valueOf();
- };
-
- var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- gap,
- indent,
- meta = { // table of character substitutions
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\f': '\\f',
- '\r': '\\r',
- '"' : '\\"',
- '\\': '\\\\'
- },
- rep;
-
-
- function quote(string) {
-
-// If the string contains no control characters, no quote characters, and no
-// backslash characters, then we can safely slap some quotes around it.
-// Otherwise we must also replace the offending characters with safe escape
-// sequences.
-
- escapeable.lastIndex = 0;
- return escapeable.test(string) ?
- '"' + string.replace(escapeable, function (a) {
- var c = meta[a];
- if (typeof c === 'string') {
- return c;
- }
- return '\\u' + ('0000' +
- (+(a.charCodeAt(0))).toString(16)).slice(-4);
- }) + '"' :
- '"' + string + '"';
- }
-
-
- function str(key, holder) {
-
-// Produce a string from holder[key].
-
- var i, // The loop counter.
- k, // The member key.
- v, // The member value.
- length,
- mind = gap,
- partial,
- value = holder[key];
-
-// If the value has a toJSON method, call it to obtain a replacement value.
-
- if (value && typeof value === 'object' &&
- typeof value.toJSON === 'function') {
- value = value.toJSON(key);
- }
-
-// If we were called with a replacer function, then call the replacer to
-// obtain a replacement value.
-
- if (typeof rep === 'function') {
- value = rep.call(holder, key, value);
- }
-
-// What happens next depends on the value's type.
-
- switch (typeof value) {
- case 'string':
- return quote(value);
-
- case 'number':
-
-// JSON numbers must be finite. Encode non-finite numbers as null.
-
- return isFinite(value) ? String(value) : 'null';
-
- case 'boolean':
- case 'null':
-
-// If the value is a boolean or null, convert it to a string. Note:
-// typeof null does not produce 'null'. The case is included here in
-// the remote chance that this gets fixed someday.
-
- return String(value);
-
-// If the type is 'object', we might be dealing with an object or an array or
-// null.
-
- case 'object':
-
-// Due to a specification blunder in ECMAScript, typeof null is 'object',
-// so watch out for that case.
-
- if (!value) {
- return 'null';
- }
-
-// Make an array to hold the partial results of stringifying this object value.
-
- gap += indent;
- partial = [];
-
-// If the object has a dontEnum length property, we'll treat it as an array.
-
- if (typeof value.length === 'number' &&
- !(value.propertyIsEnumerable('length'))) {
-
-// The object is an array. Stringify every element. Use null as a placeholder
-// for non-JSON values.
-
- length = value.length;
- for (i = 0; i < length; i += 1) {
- partial[i] = str(i, value) || 'null';
- }
-
-// Join all of the elements together, separated with commas, and wrap them in
-// brackets.
-
- v = partial.length === 0 ? '[]' :
- gap ? '[\n' + gap +
- partial.join(',\n' + gap) + '\n' +
- mind + ']' :
- '[' + partial.join(',') + ']';
- gap = mind;
- return v;
- }
-
-// If the replacer is an array, use it to select the members to be stringified.
-
- if (rep && typeof rep === 'object') {
- length = rep.length;
- for (i = 0; i < length; i += 1) {
- k = rep[i];
- if (typeof k === 'string') {
- v = str(k, value);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- } else {
-
-// Otherwise, iterate through all of the keys in the object.
-
- for (k in value) {
- if (Object.hasOwnProperty.call(value, k)) {
- v = str(k, value);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- }
-
-// Join all of the member texts together, separated with commas,
-// and wrap them in braces.
-
- v = partial.length === 0 ? '{}' :
- gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
- mind + '}' : '{' + partial.join(',') + '}';
- gap = mind;
- return v;
- }
- }
-
-// Return the JSON object containing the stringify and parse methods.
-
- return {
- stringify: function (value, replacer, space) {
-
-// The stringify method takes a value and an optional replacer, and an optional
-// space parameter, and returns a JSON text. The replacer can be a function
-// that can replace values, or an array of strings that will select the keys.
-// A default replacer method can be provided. Use of the space parameter can
-// produce text that is more easily readable.
-
- var i;
- gap = '';
- indent = '';
-
-// If the space parameter is a number, make an indent string containing that
-// many spaces.
-
- if (typeof space === 'number') {
- for (i = 0; i < space; i += 1) {
- indent += ' ';
- }
-
-// If the space parameter is a string, it will be used as the indent string.
-
- } else if (typeof space === 'string') {
- indent = space;
- }
-
-// If there is a replacer, it must be a function or an array.
-// Otherwise, throw an error.
-
- rep = replacer;
- if (replacer && typeof replacer !== 'function' &&
- (typeof replacer !== 'object' ||
- typeof replacer.length !== 'number')) {
- throw new Error('JSON.stringify');
- }
-
-// Make a fake root object containing our value under the key of ''.
-// Return the result of stringifying the value.
-
- return str('', {'': value});
- },
-
-
- parse: function (text, reviver) {
-
-// The parse method takes a text and an optional reviver function, and returns
-// a JavaScript value if the text is a valid JSON text.
-
- var j;
-
- function walk(holder, key) {
-
-// The walk method is used to recursively walk the resulting structure so
-// that modifications can be made.
-
- var k, v, value = holder[key];
- if (value && typeof value === 'object') {
- for (k in value) {
- if (Object.hasOwnProperty.call(value, k)) {
- v = walk(value, k);
- if (v !== undefined) {
- value[k] = v;
- } else {
- delete value[k];
- }
- }
- }
- }
- return reviver.call(holder, key, value);
- }
-
-
-// Parsing happens in four stages. In the first stage, we replace certain
-// Unicode characters with escape sequences. JavaScript handles many characters
-// incorrectly, either silently deleting them, or treating them as line endings.
-
- cx.lastIndex = 0;
- if (cx.test(text)) {
- text = text.replace(cx, function (a) {
- return '\\u' + ('0000' +
- (+(a.charCodeAt(0))).toString(16)).slice(-4);
- });
- }
-
-// In the second stage, we run the text against regular expressions that look
-// for non-JSON patterns. We are especially concerned with '()' and 'new'
-// because they can cause invocation, and '=' because it can cause mutation.
-// But just to be safe, we want to reject all unexpected forms.
-
-// We split the second stage into 4 regexp operations in order to work around
-// crippling inefficiencies in IE's and Safari's regexp engines. First we
-// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
-// replace all simple value tokens with ']' characters. Third, we delete all
-// open brackets that follow a colon or comma or that begin the text. Finally,
-// we look to see that the remaining characters are only whitespace or ']' or
-// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
-
- if (/^[\],:{}\s]*$/.
-test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
-replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
-replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
-
-// In the third stage we use the eval function to compile the text into a
-// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
-// in JavaScript: it can begin a block or an object literal. We wrap the text
-// in parens to eliminate the ambiguity.
-
- j = eval('(' + text + ')');
-
-// In the optional fourth stage, we recursively walk the new structure, passing
-// each name/value pair to a reviver function for possible transformation.
-
- return typeof reviver === 'function' ?
- walk({'': j}, '') : j;
- }
-
-// If the text is not JSON parseable, then a SyntaxError is thrown.
-
- throw new SyntaxError('JSON.parse');
- }
- };
- }();
-}
diff --git a/moksha/api/widgets/chat/static/chat.css b/moksha/api/widgets/chat/static/chat.css
new file mode 100644
index 0000000..fe0d13f
--- /dev/null
+++ b/moksha/api/widgets/chat/static/chat.css
@@ -0,0 +1,119 @@
+#signin_popup {
+ width:220pt;
+ padding:2em;
+ padding-bottom:3em;
+ text-align:center;
+ background-color: #A4C9D6;
+ color:#0E3541;
+ border: 5pt solid #195263;
+ position:absolute;
+ top:80pt;
+ left:100pt;
+ z-index:2;
+}
+#nickname {
+ width:90pt;
+}
+#absorb_clicks {
+ position:absolute;
+ top:0;
+ left:0;
+ width:300pt;
+ height:200pt;
+}
+
+.chatbox {
+ width:490pt;
+ font: 100% Verdana, sans-serif;
+ background-color:#24758E;
+ padding:5pt;
+ position:relative;
+}
+.chatbox .left_column {
+ width: 360pt;
+}
+.chatbox .chathistory {
+ width:100%;
+ height:217pt;
+ overflow: auto;
+ background-color: white;
+}
+.chatbox .chathistory div {
+ background-color:#ffffee;
+ padding:.2em;
+ padding-left:1.5em;
+ text-indent: -1em;
+ border-bottom: 1px dotted #ccc;
+}
+.chatbox .chathistory .message .user {
+ width:5em;
+ font-weight: bold;
+ color:#DD7200;
+ padding-right:.2em;
+ text-align:right;
+}
+.chatbox .chathistory .message.self .user{
+ color: #B42600;
+}
+.chatbox .chathistory .message.mentioned{
+ background-color: #d5c5ff;
+}
+.chatbox .chathistory .message.private{
+ background-color: #ccc;
+}
+.chatbox .chathistory .message.ctcp {
+ background-color: #f0f;
+}
+.chatbox .chathistory .action,
+.chatbox .chathistory .action .user {
+ color: #D68850;
+ font-style:italic;
+}
+.chatbox .chathistory .action.self,
+.chatbox .chathistory .action.self .user {
+ color: #A94C3B;
+ font-style:italic;
+}
+.chatbox .chathistory .informative {
+ font-size: 85%;
+ font-style: italic;
+ color:#999;
+ text-align:center;
+ padding:.3em;
+}
+
+.chatbox .chatbox_input {
+ width:100%;
+ height:30pt;
+ margin:0;
+ margin-top:3pt;
+ padding:0;
+ border:0;
+ font-family: Verdana, sans-serif;
+}
+
+.chatbox .user_list {
+ float:right;
+ position: absolute;
+ padding:0;
+ margin:0;
+ right:5pt;
+ top:5pt;
+ background-color:white;
+ width:127pt;
+ height:250pt;
+ overflow:auto;
+}
+.chatbox .user_list .user_entry{
+ padding:.2em;
+ padding-left:8pt;
+ border-bottom: 1px dotted #ccc;
+}
+
+#events {
+ visibility:hidden;
+ border:0;
+ width:0;
+ height:0;
+/* display:none;*/
+}
diff --git a/moksha/api/widgets/chat/static/index.html b/moksha/api/widgets/chat/static/index.html
index 70e3f11..d4277bf 100644
--- a/moksha/api/widgets/chat/static/index.html
+++ b/moksha/api/widgets/chat/static/index.html
@@ -1,10 +1,9 @@
<!-- @@ HACK -->
-<script src="http://localhost:9000/static/Orbited.js"></script>
-<script src="/toscawidgets/resources/moksha.live.chat.chat/static/irc2.js"></script>
-<script src="/toscawidgets/resources/moksha.live.chat.chat/static/willowchat.js"></script>
-<script src="/toscawidgets/resources/moksha.live.chat.chat/static/gui.js"></script>
+<script src="/toscawidgets/resources/moksha.api.widgets.chat.chat/static/irc2.js"></script>
+<script src="/toscawidgets/resources/moksha.api.widgets.chat.chat/static/willowchat.js"></script>
+<script src="/toscawidgets/resources/moksha.api.widgets.chat.chat/static/gui.js"></script>
<script src="/toscawidgets/resources/tw.jquery.base/static/javascript/jquery-1.2.6.pack.js"></script>
-<link rel="stylesheet" type="text/css" href="/toscawidgets/resources/moksha.live.chat.chat/static/style.css"></link>
+<link rel="stylesheet" type="text/css" href="/toscawidgets/resources/moksha.api.widgets.chat.chat/static/style.css"></link>
<script>
TCPSocket = Orbited.TCPSocket
document.domain = document.domain;
diff --git a/moksha/api/widgets/chat/static/themes/dojo/chrome.js b/moksha/api/widgets/chat/static/themes/dojo/chrome.js
new file mode 100644
index 0000000..770701f
--- /dev/null
+++ b/moksha/api/widgets/chat/static/themes/dojo/chrome.js
@@ -0,0 +1,83 @@
+(function() {
+/* Boilerplate
+ */
+if (!window.WillowChat) {
+ Willowchat = {}
+}
+
+if (!WillowChat.chrome) {
+ WillowChat.chrome = {}
+}
+
+if (!WillowChat.chrome.map) {
+ WillowChat.chrome.map = {}
+}
+
+
+WillowChat.chrome.map.dojo = WillowChat.chrome.DojoChrome = function(container, opts) {
+ /* Set configured height
+ */
+ container.style.width = opts.width + 'px'
+ container.style.height = opts.height + 'px'
+ container.style.background = "white";
+ var toggles = document.createElement('div')
+ toggles.setAttribute('id', 'willowchatToggles')
+ var floatToggle = document.createElement('div')
+ floatToggle.setAttribute('id', 'willowchatFloat')
+ floatToggle.innerHTML = "↑"
+ var minToggle = document.createElement('div')
+ minToggle.setAttribute('id', 'willowchatMin')
+ minToggle.innerHTML = "-"
+ floatToggle.setAttribute('style', "float: left;")
+ minToggle.setAttribute('style', "float: left;")
+ var toggleElements = [ minToggle, floatToggle]
+ for (var i = 0; i < 2; ++i) {
+ var t = toggleElements[i]
+ t.style.width = "20px"
+ t.style.height = "20px"
+ t.style.textAlign = "center"
+ t.style.border = "1px solid black"
+ t.style.padding = "2px"
+ t.style.color = "white"
+ t.style.fontWeight = "bold"
+ t.style.background = "#005522"
+ t.style.cursor = "pointer"
+ t.style.cssFloat = "left"
+ t.style.styleFloat = "left"
+ t.style.display = "none"
+ }
+ container.appendChild(toggles)
+ toggles.appendChild(floatToggle)
+ toggles.appendChild(minToggle)
+
+ /* Should it be floating?
+ */
+ if (opts.floating) {
+ container.style.position = 'absolute';
+ container.style.bottom = '0';
+ container.style.right = '0';
+ minToggle.style.display = "block"
+ toggles.style.position = "absolute"
+ toggles.style.top = "1px"
+ toggles.style.left = "1px"
+ if (opts.reposition) {
+ container.parentNode.removeChild(container)
+ document.body.appendChild(container)
+
+ }
+ }
+ else {
+ var ifr = container.getElementsByTagName('iframe')[0]
+ toggles.style.marginBottom="-10px"
+ ifr.style.marginTop="10px"
+ container.removeChild(ifr)
+ container.appendChild(ifr)
+// toggles.style.marginBottom = "-5px";
+ }
+ if (opts.floatingToggle) {
+ floatToggle.style.display = "block"
+ }
+ toggles.style.border = "1ps solid yellow"
+}
+
+})();
\ No newline at end of file
diff --git a/moksha/api/widgets/chat/static/themes/dojo/gui.js b/moksha/api/widgets/chat/static/themes/dojo/gui.js
new file mode 100644
index 0000000..b042620
--- /dev/null
+++ b/moksha/api/widgets/chat/static/themes/dojo/gui.js
@@ -0,0 +1,19 @@
+
+
+if (!window.WillowChat) {
+ Willowchat = {}
+}
+
+if (!WillowChat.guis) {
+ WillowChat.guis = {}
+}
+
+if (!WillowChat.guis.map) {
+ WillowChat.guis.map = {}
+}
+
+var willowConnection = null;
+WillowChat.guis.map.dojo = WillowChat.guis.DojoGui = function(wc, opts) {
+ console.log("Neat? ", wc, opts);
+ willowConnection = wc;
+}
diff --git a/moksha/api/widgets/chat/static/themes/dojo/index.html b/moksha/api/widgets/chat/static/themes/dojo/index.html
new file mode 100644
index 0000000..fe50981
--- /dev/null
+++ b/moksha/api/widgets/chat/static/themes/dojo/index.html
@@ -0,0 +1,226 @@
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="style.css">
+<script src="http://localhost:9000/static/Orbited.js"></script>
+<script src="/toscawidgets/resources/moksha.api.widgets.chat.chat/static/irc2.js"></script>
+<script src="/toscawidgets/resources/moksha.api.widgets.chat.chat/static/willowchat.js"></script>
+<script src="/toscawidgets/resources/moksha.api.widgets.chat.chat/static/gui.js"></script>
+<script src="gui.js"></script>
+ <script>
+ TCPSocket = Orbited.TCPSocket
+ document.domain = document.domain
+/* Orbited.system.enabled = true;
+ Orbited.loggers['Orbited.XSDR'].enabled = false
+ Orbited.loggers['Orbited.CometSession'].enabled = true
+ Orbited.loggers['Orbited.TCPSocket'].enabled = true
+ Orbited.loggers['XSDRBridge'].enabled = true
+ Orbited.loggers['Orbited.CometTransports.LongPoll'].enabled = true
+ Orbited.loggers['Orbited.CometTransports.XHRStream'].enabled = true
+ Orbited.loggers['Orbited.CometTransports.HTMLFile'].enabled = true
+ Orbited.loggers['Orbited.CometTransports.SSE'].enabled = true
+ Orbited.loggers['WC.c.IRCClient'].enabled = true
+*/
+ </script>
+
+
+ <link rel="stylesheet"
+ href="http://o.aolcdn.com/dojo/1.2/dijit/themes/soria/soria.css">
+ <!-- load Dojo, and all the required modules -->
+
+ <script type="text/javascript">
+
+ djConfig = {
+ isDebug: true,
+ baseUrl: "./",
+ xdWaitSeconds: 10,
+ parseOnLoad: true
+ };
+ </script>
+
+ <script src="http://o.aolcdn.com/dojo/1.2/dojo/dojo.xd.js" ></script>
+
+ <script>
+
+ dojo.require("dijit.layout.BorderContainer");
+ dojo.require("dijit.layout.AccordionContainer");
+ dojo.require("dijit.layout.ContentPane");
+ dojo.require("dijit.layout.TabContainer");
+
+ dojo.require("dijit.form.TextBox");
+ dojo.require("dijit.form.Button");
+ dojo.require("dijit.Dialog");
+
+
+ var username = null;
+ var roomMembers = [];
+
+ dojo.addOnLoad(function() {
+ dijit.byId("logindialog").show();
+ console.log("hi?", willowConnection);
+
+
+ var mssageBox = dojo.byId("messageBox");
+ dojo.connect(mssageBox, "keyup", function(e) {
+ var key = e.charCode || e.keyCode || 0
+ if (key == dojo.keys.ENTER) {
+ var msg = mssageBox.value;
+ willowConnection.sendMessage(msg)
+ willowConnection.onMessage(username, msg);
+ mssageBox.value = "";
+ };
+ });
+
+
+ });
+
+ function login(nickname) {
+ username = nickname;
+ willowConnection.connect( {
+ username: nickname
+ });
+
+
+
+
+ willowConnection.onConnect = function(data) {
+ /* This is somewhat important -- without this IE can run into some
+ * issues with reconnecting
+ */
+
+ window.onbeforeunload = function() {
+ if (willowConnection.readyState == willowConnection.READY_STATE_CONNECTED) {
+ return 'Are you sure you want to quit and lose the chat connection?';
+ }
+ };
+ window.onunload = function() {
+ if (willowConnection.readyState == willowConnection.READY_STATE_CONNECTED) {
+ willowConnection.disconnect();
+ }
+ }
+
+
+ // $("<div class='informative'>Entered ChatRoom</div>").appendTo("#history");
+ for (var i = 0; i < willowConnection.presence.length; ++i) {
+ addName(willowConnection.presence[i]);
+ console.log("who?", willowConnection.presence[i]);
+ }
+ updateMembers();
+ console.log('connected', data, willowConnection.presence)
+ }
+ willowConnection.onMessage = function(sender, message) {
+ console.log('msg', sender, message);
+ chatArea.domNode.innerHTML += "<div>" + sender + ": " + message + "</div>";
+ scrollDown();
+ /* var messagediv = $('<div class="message"></div>');
+
+ if (sender == username) {
+ messagediv.addClass("self");
+ }
+ if (isSubstring(username, message)) {
+ messagediv.addClass("mentioned");
+ }
+ messagediv.html('<span class="user">' + sender + ':</span> ' +
+ sanitize(message))
+ .appendTo("#history");
+ scrollDown();*/
+ }
+ willowConnection.onAction = function(s, m) { console.log('action', s, m); }
+
+ willowConnection.onJoin = function(joiner) {
+ console.log('join', joiner);
+ addName(joiner);
+ updateMembers();
+ chatArea.domNode.innerHTML += "<span class='user'>" + joiner + "</span> has joined<br/>";
+ scrollDown();
+ }
+
+ willowConnection.onLeave = function(leaver) {
+ console.log('leave', leaver);
+ var idx = -1;
+ for(var i = 0; idx > -1 && i < roomMembers.length; i++) {
+ if(leaver == roomMembers[i]) {
+ idx = i;
+ }
+ }
+ roomMembers.splice(i, 1);
+ _updateMemberDisplay();
+ }
+
+ willowConnection.onDisconnect = function() { console.log('disconneted'); roomMembers = []; _updateMemberDisplay(); dijit.byId("logindialog").show();}
+ willowConnection.onUsernameTaken = function() {
+ console.log('onUsernameTaken', username);
+ username += '_'
+ try {
+ willowConnection.rename(username);
+ console.log('rename sent');
+ }
+ catch(e) {
+ console.log('rename failed:', e)
+ }
+ }
+
+
+
+
+
+ }
+
+ function addName(name) {
+ roomMembers.push(name);
+ // presenceContainer.domNode.innerHTML += name + "<br/>";
+ }
+
+
+ function updateMembers() {
+ roomMembers.sort();
+ _updateMemberDisplay();
+ }
+
+ function _updateMemberDisplay() {
+ presenceContainer.domNode.innerHTML = "";
+ for(var i = 0; i < roomMembers.length; i++) {
+ var member = roomMembers[i];
+ presenceContainer.domNode.innerHTML += member + "<br/>";
+ }
+ }
+
+ function scrollDown() {
+ chatArea.domNode.scrollTop = chatArea.domNode.scrollHeight;
+ }
+
+ function logout() {
+ willowConnection.disconnect();
+ }
+
+
+
+
+ </script>
+
+
+ </head>
+ <body class="soria" >
+ <div dojoType="dijit.Dialog" id="logindialog" title="Chat"
+ execute="login(arguments[0]['name'] ? arguments[0]['name'] : 'guest');">
+ <label for="name">Nickname: </label>
+ <input dojoType=dijit.form.TextBox type="text" maxlength="16" name="name" id="name" value="">
+ <button dojoType=dijit.form.Button type="submit">OK</button>
+ </div>
+
+ <div>
+ <div dojoType="dijit.layout.BorderContainer" style="width:100%; height:100%">
+ <div dojoType="dijit.layout.ContentPane" region="top" style="border:1px solid gray; height: 20px; background: #eee;">
+ WillowChat now with Dojo <button dojoType=dijit.form.Button onClick="logout();" style="font-size:10px;" type="submit">Leave</button>
+ </div>
+ <div dojoType="dijit.layout.ContentPane" region="left" jsId="presenceContainer" style="width: 100px; border:1px solid gray; background: #eee;" splitter="true" minSize="0" maxSize="250">
+
+ </div>
+ <div dojoType="dijit.layout.ContentPane" region="center" jsId="chatArea" style="border:1px solid gray; background: #eee;" >
+
+ </div>
+ <div dojoType="dijit.layout.ContentPane" region="bottom" style="height: 50px; border:1px solid gray; background: #eee;" >
+ <textarea id="messageBox" style="width:90%"></textarea>
+ </div>
+ </div>
+ </div>
+</html>
diff --git a/moksha/api/widgets/chat/static/themes/dojo/style.css b/moksha/api/widgets/chat/static/themes/dojo/style.css
new file mode 100644
index 0000000..29f0a19
--- /dev/null
+++ b/moksha/api/widgets/chat/static/themes/dojo/style.css
@@ -0,0 +1,151 @@
+html, body {
+ padding: 0px;
+ margin: 0px;
+ border: none;
+ _overflow: hidden;
+ }
+#presenceToggle {
+ border: 1px solid black;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ padding: 2px;
+ background: #005522;
+ color: white;
+ font-weight: bold;
+ cursor: pointer;
+}
+#presenceToggle:hover {
+}
+#presence {
+ float: right;
+ border: 1px solid gray;
+ width: 130px;
+ height: 83%;
+ margin-left: 3px;
+ margin-bottom: 0px;
+ overflow: hidden;
+}
+#presenceTitle {
+ text-decoration: underline;
+}
+#presenceList {
+ overflow: auto;
+ height: 90%;
+}
+#history {
+ border: 1px solid gray;
+ height: 83%;
+ overflow: auto;
+ margin-bottom: 0px;
+}
+#historySpacer {
+ border: 1px solid red;
+}
+#inputBar {
+ margin-top: 0px;
+ height: 15%;
+}
+#inputBar textarea {
+ width: 100%;
+ height: 100%;
+ border: 1px solid gray;
+}
+
+
+
+#popup {
+ position: absolute;
+ height: 100%;
+ width: 70%;
+ left: 15%;
+ z-index: 2;
+}
+
+#popupOuter {
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ position: relative;
+}
+#popupOuter[id] {display: table; position: static;}
+
+#popupMiddle {position: absolute; top: 50%;} /* for explorer only*/
+#popupMiddle[id] {display: table-cell; vertical-align: middle; position: static;}
+
+#popupContent {position: relative; top: -50%} /* for explorer only */
+/* optional: #popupContent[id] {position: static;} */
+
+#popupContent {
+ font-size: 120%;
+ text-align:center;
+ background: #005522;
+ color: white;
+ border: 5pt solid #50A572;
+ padding: 8px;
+ padding-bottom: 20px;
+ width: 100%;
+}
+#popupContent p {
+ font-size: 150%;
+}
+
+#absorbClicks {
+ position:absolute;
+ top:0;
+ left:0;
+ width:100%;
+ height:100%;
+}
+
+
+#history div {
+ background-color:#eeffee;
+ padding:.2em;
+ padding-left:1.5em;
+ text-indent: -1em;
+ border-bottom: 1px dotted #ccc;
+}
+#history .message .user {
+ width:5em;
+ font-weight: bold;
+ color:#DD7200;
+ padding-right:.2em;
+ text-align:right;
+}
+#history .message.self .user{
+ color: #B42600;
+}
+#history .message.mentioned{
+ background-color: #d5c5ff;
+}
+#history .message.private{
+ background-color: #ccc;
+}
+#history .message.ctcp {
+ background-color: #f0f;
+}
+#history .action {
+ color: #D68850;
+ font-style:italic;
+}
+#history .action {
+ color: #A94C3B;
+ font-style:italic;
+}
+#history .informative {
+ font-size: 85%;
+ font-style: italic;
+ color:#999;
+ text-align:center;
+ padding:.3em;
+}
+#presenceList .user_entry {
+ font-size: 90%;
+ padding:.2em;
+ padding-left:8pt;
+ border-bottom: 1px dotted #ccc;
+}
+
+
+
diff --git a/moksha/api/widgets/chat/static/themes/simple/chrome.js b/moksha/api/widgets/chat/static/themes/simple/chrome.js
new file mode 100644
index 0000000..4379ce6
--- /dev/null
+++ b/moksha/api/widgets/chat/static/themes/simple/chrome.js
@@ -0,0 +1,83 @@
+(function() {
+/* Boilerplate
+ */
+if (!window.WillowChat) {
+ Willowchat = {}
+}
+
+if (!WillowChat.chrome) {
+ WillowChat.chrome = {}
+}
+
+if (!WillowChat.chrome.map) {
+ WillowChat.chrome.map = {}
+}
+
+
+WillowChat.chrome.map.simple = WillowChat.chrome.SimpleChrome = function(container, opts) {
+ /* Set configured height
+ */
+ container.style.width = opts.width + 'px'
+ container.style.height = opts.height + 'px'
+ container.style.background = "white";
+ var toggles = document.createElement('div')
+ toggles.setAttribute('id', 'willowchatToggles')
+ var floatToggle = document.createElement('div')
+ floatToggle.setAttribute('id', 'willowchatFloat')
+ floatToggle.innerHTML = "↑"
+ var minToggle = document.createElement('div')
+ minToggle.setAttribute('id', 'willowchatMin')
+ minToggle.innerHTML = "-"
+ floatToggle.setAttribute('style', "float: left;")
+ minToggle.setAttribute('style', "float: left;")
+ var toggleElements = [ minToggle, floatToggle]
+ for (var i = 0; i < 2; ++i) {
+ var t = toggleElements[i]
+ t.style.width = "20px"
+ t.style.height = "20px"
+ t.style.textAlign = "center"
+ t.style.border = "1px solid black"
+ t.style.padding = "2px"
+ t.style.color = "white"
+ t.style.fontWeight = "bold"
+ t.style.background = "#005522"
+ t.style.cursor = "pointer"
+ t.style.cssFloat = "left"
+ t.style.styleFloat = "left"
+ t.style.display = "none"
+ }
+ container.appendChild(toggles)
+ toggles.appendChild(floatToggle)
+ toggles.appendChild(minToggle)
+
+ /* Should it be floating?
+ */
+ if (opts.floating) {
+ container.style.position = 'absolute';
+ container.style.bottom = '0';
+ container.style.right = '0';
+ minToggle.style.display = "block"
+ toggles.style.position = "absolute"
+ toggles.style.top = "1px"
+ toggles.style.left = "1px"
+ if (opts.reposition) {
+ container.parentNode.removeChild(container)
+ document.body.appendChild(container)
+
+ }
+ }
+ else {
+ var ifr = container.getElementsByTagName('iframe')[0]
+ toggles.style.marginBottom="-10px"
+ ifr.style.marginTop="10px"
+ container.removeChild(ifr)
+ container.appendChild(ifr)
+// toggles.style.marginBottom = "-5px";
+ }
+ if (opts.floatingToggle) {
+ floatToggle.style.display = "block"
+ }
+ toggles.style.border = "1ps solid yellow"
+}
+
+})();
diff --git a/moksha/api/widgets/chat/static/themes/simple/gui.js b/moksha/api/widgets/chat/static/themes/simple/gui.js
new file mode 100644
index 0000000..2b5083f
--- /dev/null
+++ b/moksha/api/widgets/chat/static/themes/simple/gui.js
@@ -0,0 +1,216 @@
+(function() {
+
+if (!window.WillowChat) {
+ console.log('no willochat found in gui?');
+ Willowchat = {}
+}
+
+if (!WillowChat.guis) {
+ WillowChat.guis = {}
+}
+
+if (!WillowChat.guis.map) {
+ WillowChat.guis.map = {}
+}
+var ENTER_KEY = 13
+WillowChat.guis.map.simple = WillowChat.guis.SimpleGui = function(wc, opts) {
+ var self = this;
+ self.presenceShown = true;
+ var logger = Orbited.getLogger('WillowChatTest');
+// logger.enabled = true;
+ var username = null;
+ var presence = {}
+
+ $('#nickname').focus();
+ $('#nickname').keypress( function(e) {
+ var key = e.charCode || e.keyCode || 0
+ if (key == ENTER_KEY) {
+ self.connect();
+ };
+ });
+ $('#chatboxInput').keypress(function(e) {
+ var key = e.charCode || e.keyCode || 0;
+ if (key == ENTER_KEY) {
+ self.sendMessage();
+ return false;
+ };
+ });
+ $("#popupContent p").html(opts.greeting)
+ $("#presenceToggle").click(function() {
+ if (self.presenceShown) {
+ self.presenceShown = false;
+ $("#presence").hide()
+ $("#presenceToggle").html('←');
+ }
+ else {
+ self.presenceShown = true;
+ $("#presence").show();
+ $("#presenceToggle").html('→');
+ }
+ if (wc.readyState == wc.READY_STATE_CONNECTED) {
+ $('#chatboxInput').focus();
+ }
+ else {
+ $('#nickname').focus();
+ }
+ })
+ var scrollDown = function() {
+ var box = $('#history')[0]
+ box.scrollTop = box.scrollHeight;
+ }
+ var isSubstring = function (sub, str) {
+ // case insensitive substring test
+ return str.toLowerCase().indexOf(sub.toLowerCase()) >= 0;
+ };
+ var sanitize = (function(str) {
+ // See http://bigdingus.com/2007/12/29/html-escaping-in-javascript/
+ var MAP = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ };
+ var repl = function(c) { return MAP[c]; };
+ return function(s) {
+ return s.replace(/[&<>'"]/g, repl);
+ };
+ })();
+
+ // TODO: test this out
+ var userRenamed = function(oldname, newname) {
+ $("<div class='informative rename'></div>")
+ .html("<span class='user'>" + oldname + "</span> is now known as " +
+ "<span class='user'>" + newname + "</span>")
+ .appendTo("#history");
+ scrollDown();
+ $("#user_" + oldname)
+ .attr("id", "user_" + newname)
+ .html(newname);
+ }
+
+ var addName = function (name) {
+ presence[name] = 1
+ $('<div class="user_entry" id="user_' + name + '">' + name + '</div>')
+ .appendTo("#presenceList");
+ };
+
+ var removeName = function (name) {
+ delete presence[name];
+ $(".user_list #user_" + name).remove()
+ };
+
+ var sortNames = function () {
+ $('#presenceList').empty();
+ var list = [];
+ for (var user in presence) {
+ list.push(user);
+ };
+ list.sort()
+ for (var i=0; i < list.length; i++) {
+ var user = list[i];
+ $('<div class="user_entry" id="user_' + user + '">' + user + '</div>')
+ .appendTo("#presenceList");
+ };
+ };
+ self.connect = function() {
+ username = $("#nickname").val();
+ if (!username) {
+ logger.debug('please enter a username');
+ return
+ }
+ $('#popup, #absorbClicks').hide();
+ $('#chatboxInput').focus();
+ $('#chatboxInput').val('')
+
+ connected = true;
+ wc.connect( {
+ username: username
+ })
+ wc.onConnect = function(data) {
+ /* This is somewhat important -- without this IE can run into some
+ * issues with reconnecting
+ */
+
+ window.onbeforeunload = function() {
+ if (wc.readyState == wc.READY_STATE_CONNECTED) {
+ return 'Are you sure you want to quit and lose the chat connection?';
+ }
+ };
+ window.onunload = function() {
+ if (wc.readyState == wc.READY_STATE_CONNECTED) {
+ wc.disconnect();
+ }
+ }
+
+
+ $("<div class='informative'>Entered ChatRoom</div>").appendTo("#history");
+ for (var i = 0; i < wc.presence.length; ++i) {
+ addName(wc.presence[i]);
+ }
+ sortNames();
+ logger.debug('connected', data, wc.presence)
+ }
+ wc.onMessage = function(sender, message) {
+ logger.debug('msg', sender, message);
+ var messagediv = $('<div class="message"></div>');
+
+ if (sender == username) {
+ messagediv.addClass("self");
+ }
+ if (isSubstring(username, message)) {
+ messagediv.addClass("mentioned");
+ }
+ messagediv.html('<span class="user">' + sender + ':</span> ' +
+ sanitize(message))
+ .appendTo("#history");
+ scrollDown();
+ }
+ wc.onAction = function(s, m) { logger.debug('action', s, m); }
+
+ wc.onJoin = function(joiner) {
+ logger.debug('join', joiner);
+ addName(joiner);
+ sortNames();
+ $("<div class='informative join'></div>")
+ .html("<span class='user'>" + joiner + '</span> has joined')
+ .appendTo("#history");
+ scrollDown();
+ }
+ wc.onLeave = function(leaver) {
+ logger.debug('leave', leaver);
+ removeName(leaver);
+ $("<div class='informative part'></div>")
+ .html("<span class='user'>" + leaver + '</span> left')
+ .appendTo("#history");
+ scrollDown();
+ }
+ wc.onDisconnect = function() { logger.debug('disconneted'); }
+ wc.onUsernameTaken = function() {
+ logger.debug('onUsernameTaken', username);
+ username += '_'
+ try {
+ wc.rename(username);
+ logger.debug('rename sent');
+ }
+ catch(e) {
+ logger.debug('rename failed:', e)
+ }
+ }
+ }
+ self.sendMessage = function() {
+ var msg = $('#chatboxInput').val();
+ wc.sendMessage(msg)
+ $('#chatboxInput').val('');
+ // the WillowChat server will not echo our message back, so simulate a send.
+ wc.onMessage(username, msg)
+ }
+ self.sendAction = function() {
+ var msg = getMessage()
+ wc.sendAction(msg);
+ logger.debug('action', username, msg)
+ }
+
+}
+})()
+
diff --git a/moksha/api/widgets/chat/static/themes/simple/index.html b/moksha/api/widgets/chat/static/themes/simple/index.html
new file mode 100644
index 0000000..ee659cf
--- /dev/null
+++ b/moksha/api/widgets/chat/static/themes/simple/index.html
@@ -0,0 +1,58 @@
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="style.css">
+<!-- @@ HACK -->
+<script src="http://localhost:9000/static/Orbited.js"></script>
+<script src="/toscawidgets/resources/moksha.api.widgets.chat.chat/static/irc2.js"></script>
+<script src="/toscawidgets/resources/moksha.api.widgets.chat.chat/static/willowchat.js"></script>
+<script src="/toscawidgets/resources/moksha.api.widgets.chat.chat/static/gui.js"></script>
+<script src="/toscawidgets/resources/tw.jquery.base/static/javascript/jquery-1.2.6.pack.js"></script>
+ <script src="gui.js"></script>
+ <script>
+ TCPSocket = Orbited.TCPSocket
+ document.domain = document.domain
+
+/* Orbited.system.enabled = true;
+ Orbited.loggers['Orbited.XSDR'].enabled = false
+ Orbited.loggers['Orbited.CometSession'].enabled = true
+ Orbited.loggers['Orbited.TCPSocket'].enabled = true
+ Orbited.loggers['XSDRBridge'].enabled = true
+ Orbited.loggers['Orbited.CometTransports.LongPoll'].enabled = true
+ Orbited.loggers['Orbited.CometTransports.XHRStream'].enabled = true
+ Orbited.loggers['Orbited.CometTransports.HTMLFile'].enabled = true
+ Orbited.loggers['Orbited.CometTransports.SSE'].enabled = true
+ Orbited.loggers['WC.c.IRCClient'].enabled = true
+*/
+ </script>
+
+ </head>
+ <body>
+<div id="popup">
+<div id="popupOuter">
+ <div id="popupMiddle">
+ <div id="popupContent">
+ <p>Greeting</p>
+ <label for="nickname">Nickname:</label>
+ <input type="text" id="nickname" maxlength="16" style="width: 10em;" value="" />
+ </div>
+ </div>
+</div>
+</div>
+
+
+ <div id="absorbClicks"></div>
+ <div id="presenceToggle">
+ →
+ </div>
+ <div id="presence">
+ <div id="presenceTitle">Presence</div>
+ <div id="presenceList">
+ </div>
+ </div>
+ <div id="history">
+ </div>
+ <div id="inputBar">
+ <textarea id="chatboxInput"></textarea>
+ </div>
+ </body>
+</html>
diff --git a/moksha/api/widgets/chat/static/themes/simple/style.css b/moksha/api/widgets/chat/static/themes/simple/style.css
new file mode 100644
index 0000000..29f0a19
--- /dev/null
+++ b/moksha/api/widgets/chat/static/themes/simple/style.css
@@ -0,0 +1,151 @@
+html, body {
+ padding: 0px;
+ margin: 0px;
+ border: none;
+ _overflow: hidden;
+ }
+#presenceToggle {
+ border: 1px solid black;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ padding: 2px;
+ background: #005522;
+ color: white;
+ font-weight: bold;
+ cursor: pointer;
+}
+#presenceToggle:hover {
+}
+#presence {
+ float: right;
+ border: 1px solid gray;
+ width: 130px;
+ height: 83%;
+ margin-left: 3px;
+ margin-bottom: 0px;
+ overflow: hidden;
+}
+#presenceTitle {
+ text-decoration: underline;
+}
+#presenceList {
+ overflow: auto;
+ height: 90%;
+}
+#history {
+ border: 1px solid gray;
+ height: 83%;
+ overflow: auto;
+ margin-bottom: 0px;
+}
+#historySpacer {
+ border: 1px solid red;
+}
+#inputBar {
+ margin-top: 0px;
+ height: 15%;
+}
+#inputBar textarea {
+ width: 100%;
+ height: 100%;
+ border: 1px solid gray;
+}
+
+
+
+#popup {
+ position: absolute;
+ height: 100%;
+ width: 70%;
+ left: 15%;
+ z-index: 2;
+}
+
+#popupOuter {
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ position: relative;
+}
+#popupOuter[id] {display: table; position: static;}
+
+#popupMiddle {position: absolute; top: 50%;} /* for explorer only*/
+#popupMiddle[id] {display: table-cell; vertical-align: middle; position: static;}
+
+#popupContent {position: relative; top: -50%} /* for explorer only */
+/* optional: #popupContent[id] {position: static;} */
+
+#popupContent {
+ font-size: 120%;
+ text-align:center;
+ background: #005522;
+ color: white;
+ border: 5pt solid #50A572;
+ padding: 8px;
+ padding-bottom: 20px;
+ width: 100%;
+}
+#popupContent p {
+ font-size: 150%;
+}
+
+#absorbClicks {
+ position:absolute;
+ top:0;
+ left:0;
+ width:100%;
+ height:100%;
+}
+
+
+#history div {
+ background-color:#eeffee;
+ padding:.2em;
+ padding-left:1.5em;
+ text-indent: -1em;
+ border-bottom: 1px dotted #ccc;
+}
+#history .message .user {
+ width:5em;
+ font-weight: bold;
+ color:#DD7200;
+ padding-right:.2em;
+ text-align:right;
+}
+#history .message.self .user{
+ color: #B42600;
+}
+#history .message.mentioned{
+ background-color: #d5c5ff;
+}
+#history .message.private{
+ background-color: #ccc;
+}
+#history .message.ctcp {
+ background-color: #f0f;
+}
+#history .action {
+ color: #D68850;
+ font-style:italic;
+}
+#history .action {
+ color: #A94C3B;
+ font-style:italic;
+}
+#history .informative {
+ font-size: 85%;
+ font-style: italic;
+ color:#999;
+ text-align:center;
+ padding:.3em;
+}
+#presenceList .user_entry {
+ font-size: 90%;
+ padding:.2em;
+ padding-left:8pt;
+ border-bottom: 1px dotted #ccc;
+}
+
+
+
diff --git a/moksha/api/widgets/chat/templates/__init__.py b/moksha/api/widgets/chat/templates/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/moksha/api/widgets/chat/templates/bootstrap.mak b/moksha/api/widgets/chat/templates/bootstrap.mak
new file mode 100644
index 0000000..a01cfd0
--- /dev/null
+++ b/moksha/api/widgets/chat/templates/bootstrap.mak
@@ -0,0 +1,420 @@
+(function() {
+var gatherOptions = function(roomOptions, container) {
+ var opts = {}
+ var containerOptions = [ 'width', 'height', 'roomClass', 'reposition', 'theme' ]
+ for (var key in roomOptions) {
+ opts[key] = roomOptions[key]
+ }
+ for (var i = 0; i < containerOptions.length; ++i) {
+ var key = containerOptions[i]
+ var option = container.getAttribute(key)
+ if (option) {
+ opts[key] = option
+ }
+ }
+ if (!opts.roomId) {
+ var roomId = container.getAttribute('roomId')
+ opts.roomId = roomId ? roomId : 'default'
+ }
+ if (!opts.width) {
+ opts.width = 350;
+ }
+ if (!opts.height) {
+ opts.height = 240;
+ }
+ if (!opts.theme) {
+ opts.theme = 'simple'
+ }
+ if (!opts.greeting) {
+ opts.greeting = "Willow Chat"
+ }
+ return opts
+}
+
+var loaded = function() {
+ if (!window.WillowChat) {
+ console.log('creating new willowchat?');
+ WillowChat = {}
+ }
+ if (!WillowChat.util) {
+ WillowChat.util = {}
+ }
+ var container = document.getElementById('willowchat');
+ if (!container) {
+ container = document.createElement('div');
+ container.setAttribute('id', 'willowchat');
+ }
+
+
+ var roomClass = container.getAttribute('roomClass');
+ var roomClass= roomClass ? roomClass : 'default'
+ s = document.createElement('script')
+ var baseurl = 'http://' + WillowChat.hostHeader
+ s.src = baseurl + '/appz/chat/rooms?jsonp=WillowChat.util.optionsCallback&op=info&roomClass=' + roomClass
+
+ WillowChat.util.optionsCallback = function(result) {
+ success = result[0]
+ if (!success) {
+ throw new Error("Failed: " + result[1])
+ }
+ room = result[1]
+ var opts = gatherOptions(room, container);
+ ifr = document.createElement('iframe')
+ var staticurl = baseurl + '/toscawidgets/resources/moksha.api.widgets.chat.chat/static'
+ ifr.src = '/appz/chat/index?json=1&opts=' + escape(JSON.stringify(opts))
+ ifr.style.border = "0px";
+ ifr.style.width = "100%";
+ ifr.style.height = "100%";
+ var chromeCallback = function() {
+ container.appendChild(ifr)
+ chrome = new WillowChat.chrome.map[opts.theme](container, opts)
+ }
+ var s = document.createElement('script')
+ // TODO: whats the deal with this? Can't we do any better?
+ s.src = staticurl + '/themes/' + opts.theme + '/chrome.js?ienocache=' + Math.floor(Math.random()*10000)
+ if (s.addEventListener) {
+ s.addEventListener('load', chromeCallback, false)
+ }
+ else if (s.attachEvent) {
+ s.attachEvent('onreadystatechange', function() {
+ if (s.readyState == "loaded") {
+ chromeCallback()
+ }
+ })
+ }
+ if (true) {
+ var k = 3 + 3
+ }
+// var d = document.createElement('div')
+// d.innerHTML = 'go'
+// document.body.appendChild(d)
+ document.getElementsByTagName('head')[0].appendChild(s)
+ }
+ document.getElementsByTagName('head')[0].appendChild(s)
+
+}
+
+$(document).ready(function(){
+ if (!window.WillowChat) { WillowChat = {} }; WillowChat.hostHeader = "${host}";
+ loaded();
+});
+
+})();
+if (!this.JSON) {
+
+// Create a JSON object only if one does not already exist. We create the
+// object in a closure to avoid creating global variables.
+
+ JSON = function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ Date.prototype.toJSON = function (key) {
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapeable.lastIndex = 0;
+ return escapeable.test(string) ?
+ '"' + string.replace(escapeable, function (a) {
+ var c = meta[a];
+ if (typeof c === 'string') {
+ return c;
+ }
+ return '\\u' + ('0000' +
+ (+(a.charCodeAt(0))).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// If the object has a dontEnum length property, we'll treat it as an array.
+
+ if (typeof value.length === 'number' &&
+ !(value.propertyIsEnumerable('length'))) {
+
+// The object is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// Return the JSON object containing the stringify and parse methods.
+
+ return {
+ stringify: function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ },
+
+
+ parse: function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' + ('0000' +
+ (+(a.charCodeAt(0))).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ }
+ };
+ }();
+}
diff --git a/moksha/api/widgets/chat/templates/chat.mak b/moksha/api/widgets/chat/templates/chat.mak
new file mode 100644
index 0000000..a3ab679
--- /dev/null
+++ b/moksha/api/widgets/chat/templates/chat.mak
@@ -0,0 +1,31 @@
+<script>
+ TCPSocket = Orbited.TCPSocket
+ document.domain = document.domain;
+</script>
+
+<div id="popup">
+<div id="popupOuter">
+ <div id="popupMiddle">
+ <div id="popupContent">
+ <p>Greeting</p>
+ <label for="nickname">Nickname:</label>
+ <input type="text" id="nickname" maxlength="16" style="width: 10em;" value="" />
+ </div>
+ </div>
+</div>
+</div>
+
+ <div id="absorbClicks"></div>
+ <div id="presenceToggle">
+ →
+ </div>
+ <div id="presence">
+ <div id="presenceTitle">Presence</div>
+ <div id="presenceList">
+ </div>
+ </div>
+ <div id="history">
+ </div>
+ <div id="inputBar">
+ <textarea id="chatboxInput"></textarea>
+ </div>
commit 1d1937d6bcd43ae87cac9e20e3cd262ae89ec14e
Merge: 3b2f959... faf2bc2...
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 16:06:22 2009 -0500
Merge branch 'master' of git+ssh://git.fedorahosted.org/git/moksha
commit 3b2f959d62ac45a7747977c2370f3c2b391b3343
Author: Luke Macken <lmacken(a)redhat.com>
Date: Mon Feb 2 13:01:53 2009 -0500
More load_renderers cleanups in our Middleware
diff --git a/moksha/middleware/middleware.py b/moksha/middleware/middleware.py
index 6f4c6a7..a24f5e6 100644
--- a/moksha/middleware/middleware.py
+++ b/moksha/middleware/middleware.py
@@ -238,7 +238,12 @@ class MokshaMiddleware(object):
self.menus[menu_entry.name] = menu_class(menu_entry.name)
def load_renderers(self):
- """ Load our template renderers with our application paths """
+ """ Load our template renderers with our application paths.
+
+ We are currently overloading TG2's default Mako renderer because
+ the default `escape` filter causes our widgets to show up as escaped HTML.
+
+ """
#template_paths = config['pylons.paths']['templates']
#moksha_dir = os.path.abspath(__file__ + '/../../../')
#for app in [{'path': moksha_dir}] + self.apps.values():
@@ -258,7 +263,6 @@ class MokshaMiddleware(object):
"""
def __init__(self, input_encoding, output_encoding,
imports, default_filters):
- print "\n\nusing dotted template lookups!\n\n"
self.input_encoding = input_encoding
self.output_encoding = output_encoding
self.imports = imports
@@ -303,9 +307,6 @@ class MokshaMiddleware(object):
config['pylons.app_globals'].mako_lookup = DottedTemplateLookup(
input_encoding='utf-8', output_encoding='utf-8',
imports=[], default_filters=[])
- # @@: these cause problems when displaying headers/footers
- #imports=['from webhelpers.html import escape'],
- #default_filters=['escape'])
else:
# if no dotted names support was required we will just setup
@@ -314,18 +315,8 @@ class MokshaMiddleware(object):
directories=self.paths['templates'],
module_directory=self.paths['templates'],
input_encoding='utf-8', output_encoding='utf-8',
- imports=['from webhelpers.html import escape'],
- default_filters=['escape'],
filesystem_checks=self.auto_reload_templates)
- #from genshi.template import TemplateLoader
- #def template_loaded(template):
- # "Plug-in our i18n function to Genshi."
- # template.filters.insert(0, Translator(ugettext))
- #config['pylons.app_globals'].genshi_loader = TemplateLoader(
- # search_path=template_paths, auto_reload=False,
- # callback=template_loaded)
-
def load_configs(self):
""" Load the configuration files for all applications.
15 years, 4 months
moksha/api
by John (J5) Palmieri
moksha/api/widgets/__init__.py | 3 -
moksha/api/widgets/containers/__init__.py | 1
moksha/api/widgets/containers/dashboardcontainer.py | 16 +++---
moksha/api/widgets/containers/tabbedcontainer.py | 16 +++---
moksha/api/widgets/containers/templates/dashboardcontainer.mak | 12 ++--
moksha/api/widgets/containers/templates/layout_applist.mak | 26 +++++-----
moksha/api/widgets/grid.py | 17 +++---
moksha/api/widgets/util.py | 4 -
8 files changed, 48 insertions(+), 47 deletions(-)
New commits:
commit d299d2f6e521e205dab790a11077866fd3ad2036
Author: John (J5) Palmieri <johnp(a)redhat.com>
Date: Mon Feb 2 16:58:37 2009 -0500
remove trailing whitespace
diff --git a/moksha/api/widgets/__init__.py b/moksha/api/widgets/__init__.py
index ceac0de..38b79ba 100644
--- a/moksha/api/widgets/__init__.py
+++ b/moksha/api/widgets/__init__.py
@@ -1,3 +1,4 @@
from live import LiveWidget
from util import *
-from grid import Grid
\ No newline at end of file
+from grid import Grid
+
diff --git a/moksha/api/widgets/containers/__init__.py b/moksha/api/widgets/containers/__init__.py
index 930fb99..3e3baa2 100644
--- a/moksha/api/widgets/containers/__init__.py
+++ b/moksha/api/widgets/containers/__init__.py
@@ -1,2 +1,3 @@
from dashboardcontainer import *
from tabbedcontainer import *
+
diff --git a/moksha/api/widgets/containers/dashboardcontainer.py b/moksha/api/widgets/containers/dashboardcontainer.py
index 3cd1b75..41732bf 100644
--- a/moksha/api/widgets/containers/dashboardcontainer.py
+++ b/moksha/api/widgets/containers/dashboardcontainer.py
@@ -1,4 +1,4 @@
-from moksha.api.widgets.layout.layout import layout_js, layout_css, ui_core_js, ui_draggable_js, ui_droppable_js, ui_sortable_js
+from moksha.api.widgets.layout.layout import layout_js, layout_css, ui_core_js, ui_draggable_js, ui_droppable_js, ui_sortable_js
from tw.api import Widget
from tw.jquery import jquery_js
@@ -8,10 +8,10 @@ from tg import config
class AppListWidget(Widget):
template = 'mako:moksha.api.widgets.containers.templates.layout_applist'
properties = ['layout', 'category']
-
- def update_params(self, d):
+
+ def update_params(self, d):
super(AppListWidget, self).update_params(d)
-
+
# we want to error out if there is no category
c = d['category']
if isinstance(c, basestring):
@@ -30,7 +30,7 @@ class DashboardContainer(Widget):
config_key = None
layout = []
- def update_params(self, d):
+ def update_params(self, d):
super(DashboardContainer, self).update_params(d)
layout = eval_app_config(config.get(self.config_key, "None"))
@@ -39,12 +39,12 @@ class DashboardContainer(Widget):
layout = eval_app_config(self.layout)
else:
layout = self.layout
-
+
# Filter out any None's in the layout which signify apps which are
# not allowed to run with the current session's authorization level
-
+
l = ConfigWrapper.process_wrappers(layout)
-
+
d['layout'] = l
d['applist_widget'] = applist_widget
return d
\ No newline at end of file
diff --git a/moksha/api/widgets/containers/tabbedcontainer.py b/moksha/api/widgets/containers/tabbedcontainer.py
index f83bf97..247cd98 100644
--- a/moksha/api/widgets/containers/tabbedcontainer.py
+++ b/moksha/api/widgets/containers/tabbedcontainer.py
@@ -5,21 +5,21 @@ from moksha.lib.helpers import eval_app_config, ConfigWrapper
import urllib
-"""
+"""
:Name: TabbedContainer
:Type: Container
:Notes: We may need to create a tab javascript object that inherits from
jQuery.ui.tabs
-"""
+"""
class TabbedContainer(JQueryUITabs):
"""
:tabs: An ordered list of application tabs to display
Application descriptors can come is a couple of forms
-
+
* tuple - (label, url, {request parameters})
- * App class - App(label = label,
- url = url,
+ * App class - App(label = label,
+ url = url,
req_params = {request parameters})
* MokshaApp class - MokshaApp(label = label,
application = moksha app name,
@@ -29,11 +29,11 @@ class TabbedContainer(JQueryUITabs):
template = 'mako:moksha.api.widgets.containers.templates.tabbedcontainer'
config_key = None # if set load config
tabs = ()
-
+
def update_params(self, d):
-
+
super(TabbedContainer, self).update_params(d)
-
+
tabs = eval_app_config(config.get(self.config_key, "None"))
if not tabs:
if isinstance(self.tabs, str):
diff --git a/moksha/api/widgets/containers/templates/dashboardcontainer.mak b/moksha/api/widgets/containers/templates/dashboardcontainer.mak
index dfd40ca..265bfa1 100644
--- a/moksha/api/widgets/containers/templates/dashboardcontainer.mak
+++ b/moksha/api/widgets/containers/templates/dashboardcontainer.mak
@@ -1,12 +1,12 @@
<div>
Sample Template - you should subclass and create your own layout
- <div id="container">
- % for c in layout:
+ <div id="container">
+ % for c in layout:
${applist_widget(category = c)}
% endfor
</div>
-
- <div id="overlay">
- <div id="preloader"><img src="/toscawidgets/resources/moksha.widgets.layout.layout/static/loader.gif" alt="" /></div>
- </div>
+
+ <div id="overlay">
+ <div id="preloader"><img src="/toscawidgets/resources/moksha.widgets.layout.layout/static/loader.gif" alt="" /></div>
+ </div>
</div>
diff --git a/moksha/api/widgets/containers/templates/layout_applist.mak b/moksha/api/widgets/containers/templates/layout_applist.mak
index 8791153..0fa4464 100644
--- a/moksha/api/widgets/containers/templates/layout_applist.mak
+++ b/moksha/api/widgets/containers/templates/layout_applist.mak
@@ -1,21 +1,21 @@
- <div id="${category['id']}" class="ui-sortable ${category['css_class']}">
- % for app in category['apps']:
- <dl class="sort">
- % if app['label']:
- <dt>${app['label']}</dt>
- % endif
- <dd id="${app['id']}">
+ <div id="${category['id']}" class="ui-sortable ${category['css_class']}">
+ % for app in category['apps']:
+ <dl class="sort">
+ % if app['label']:
+ <dt>${app['label']}</dt>
+ % endif
+ <dd id="${app['id']}">
% if app.has_key('widget'):
${app['widget'](app['params'])}
% endif
</dd>
-
+
% if app.has_key('url'):
<script type="text/javascript">
$("#${app['id']}").load("${app['url']}");
</script>
- % endif
-
- </dl>
- % endfor
- </div>
+ % endif
+
+ </dl>
+ % endfor
+ </div>
diff --git a/moksha/api/widgets/grid.py b/moksha/api/widgets/grid.py
index d380e4e..7469c3a 100644
--- a/moksha/api/widgets/grid.py
+++ b/moksha/api/widgets/grid.py
@@ -4,13 +4,13 @@ from tw.jquery.ui_core import jquery_ui_core_js
from tw.jquery import jQuery, jquery_js
import simplejson as json
-jquery_json_js = JSLink(filename='public/javascript/jquery.json.js',
+jquery_json_js = JSLink(filename='public/javascript/jquery.json.js',
modname='moksha', javascript=[jquery_js])
-jquery_template_js = JSLink(filename='public/javascript/jquery.template.js',
+jquery_template_js = JSLink(filename='public/javascript/jquery.template.js',
modname='moksha', javascript=[jquery_js])
-moksha_ui_grid_js = JSLink(filename='public/javascript/ui/moksha.ui.grid.js',
- modname='moksha',
- javascript=[jquery_ui_core_js,
+moksha_ui_grid_js = JSLink(filename='public/javascript/ui/moksha.ui.grid.js',
+ modname='moksha',
+ javascript=[jquery_ui_core_js,
jquery_template_js,
jquery_json_js])
@@ -20,7 +20,7 @@ class Grid(FormField):
'filters', 'unique_key', 'sort_key', 'sort_order',
'row_template', 'resource', 'resource_path',
'loading_throbber']
-
+
rows_per_page = 10
page_num = 1
total_rows = 0
@@ -32,12 +32,12 @@ class Grid(FormField):
resource = None
resource_path = None
loading_throbber = None
-
+
def update_params(self, d):
super(Grid, self).update_params(d)
if not getattr(d,"id",None):
raise ValueError, "Moksha Grid is supposed to have id"
-
+
grid_d = {}
for p in self.params:
v = d.get(p)
@@ -51,4 +51,3 @@ class Grid(FormField):
if d.get('uid'):
id += d.uid
self.add_call(jQuery("#%s" % id).mokshagrid(grid_d))
-
\ No newline at end of file
diff --git a/moksha/api/widgets/util.py b/moksha/api/widgets/util.py
index a41f375..384a585 100644
--- a/moksha/api/widgets/util.py
+++ b/moksha/api/widgets/util.py
@@ -5,8 +5,8 @@ class ContextAwareWidget(Widget):
'''Inherit from this widget class if you want your widget
to automatically get the pylons.tmpl_context in its dictionary
'''
-
+
def update_params(self, d):
super(ContextAwareWidget, self).update_params(d)
-
+
d['tmpl_context'] = tmpl_context
\ No newline at end of file
15 years, 4 months