Browse Source

Merge branch 'master' of bitbucket.org:lebouquetin/protov1

sferot 11 years ago
parent
commit
bf99066079

+ 2 - 2
.gitignore View File

8
 # Distribution / packaging
8
 # Distribution / packaging
9
 .Python
9
 .Python
10
 env/
10
 env/
11
-bin/
12
 build/
11
 build/
13
 develop-eggs/
12
 develop-eggs/
14
 dist/
13
 dist/
52
 # Sphinx documentation
51
 # Sphinx documentation
53
 docs/_build/
52
 docs/_build/
54
 
53
 
55
-# Vim
54
+# Vim and dev tools
56
 *.swp
55
 *.swp
56
+.idea
57
 
57
 
58
 # Virtualenv
58
 # Virtualenv
59
 tg2env/
59
 tg2env/

+ 16 - 8
bin/setup.sh View File

45
 echo
45
 echo
46
 echo
46
 echo
47
 echo "-------------------------"
47
 echo "-------------------------"
48
-echo "- setup project"
48
+echo "- install dependencies"
49
 echo "-------------------------"
49
 echo "-------------------------"
50
-cd pboard/
51
-python setup.py develop
50
+echo "-> psycopg2"
51
+echo "-> pillow"
52
+echo "-> beautifulsoup4"
53
+echo "-> tw.forms"
54
+echo "-> tgext.admin"
55
+pip install psycopg2
56
+pip install pillow
57
+pip install beautifulsoup4
58
+pip install tw.forms
59
+pip install tgext.admin
52
 echo
60
 echo
53
 echo
61
 echo
54
 
62
 
55
 echo
63
 echo
56
 echo
64
 echo
57
 echo "-------------------------"
65
 echo "-------------------------"
58
-echo "- install dependencies"
66
+echo "- setup project"
59
 echo "-------------------------"
67
 echo "-------------------------"
60
-echo "-> psycopg2"
61
-echo "-> pillow"
62
-pip install psycopg2
63
-pip install pillow
68
+cd pboard/
69
+python setup.py develop
64
 echo
70
 echo
65
 echo
71
 echo
66
 
72
 
73
+
74
+
67
 cd ${OLD_PATH}
75
 cd ${OLD_PATH}

+ 3 - 3
doc/database/pod-create-database-and-user.sh View File

1
 #!/bin/bash
1
 #!/bin/bash
2
-POD_DB_USER='pod_master'
3
-POD_DB_USER_PASSWORD='pod_master_password'
4
-POD_DB_NAME='pod'
2
+POD_DB_USER='pod_protov1_dev'
3
+POD_DB_USER_PASSWORD='pod_protov1_dev_password'
4
+POD_DB_NAME='pod_protov1_dev'
5
 
5
 
6
 # DB_HOST='127.0.0.1'
6
 # DB_HOST='127.0.0.1'
7
 # DB_PORT='5432'
7
 # DB_PORT='5432'

+ 1 - 1
pboard/development.ini View File

60
 # in development
60
 # in development
61
 
61
 
62
 #sqlalchemy.url = postgres://pboard:pboard@127.0.0.1:5432/pboarddb
62
 #sqlalchemy.url = postgres://pboard:pboard@127.0.0.1:5432/pboarddb
63
-sqlalchemy.url = postgres://pod_master:pod_master_password@127.0.0.1:5432/pod
63
+sqlalchemy.url = postgres://pod_protov1_dev:pod_protov1_dev_password@127.0.0.1:5432/pod_protov1_dev
64
 #echo shouldn't be used together with the logging module.
64
 #echo shouldn't be used together with the logging module.
65
 sqlalchemy.echo = false
65
 sqlalchemy.echo = false
66
 sqlalchemy.echo_pool = false
66
 sqlalchemy.echo_pool = false

+ 38 - 24
pboard/pboard/controllers/root.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 """Main Controller"""
2
 """Main Controller"""
3
+import pboard
3
 
4
 
4
 import tg
5
 import tg
5
 from tg import expose, flash, require, url, lurl, request, redirect, tmpl_context
6
 from tg import expose, flash, require, url, lurl, request, redirect, tmpl_context
6
 from tg.i18n import ugettext as _, lazy_ugettext as l_
7
 from tg.i18n import ugettext as _, lazy_ugettext as l_
7
 from tg import predicates
8
 from tg import predicates
8
-from pboard import model
9
-from pboard.model import DBSession, metadata
10
-# FIXME - D.A. - 2013-11-19
11
-# python3 port is not yet available for the tgext.admin module
12
-#
13
-# from tgext.admin.tgadminconfig import TGAdminConfig
14
-# from tgext.admin.controller import AdminController
9
+
10
+import tgext.admin.tgadminconfig as tgat
11
+import tgext.admin.controller as tgac
15
 
12
 
16
 from pboard.lib.base import BaseController
13
 from pboard.lib.base import BaseController
17
 from pboard.controllers.error import ErrorController
14
 from pboard.controllers.error import ErrorController
18
 
15
 
19
-import pboard.model as pbm
20
-import pboard.controllers as pbc
21
 from pboard.lib import dbapi as pld
16
 from pboard.lib import dbapi as pld
22
 from pboard.controllers import api as pbca
17
 from pboard.controllers import api as pbca
23
 from pboard.controllers import debug as pbcd
18
 from pboard.controllers import debug as pbcd
43
     must be wrapped around with :class:`tg.controllers.WSGIAppController`.
38
     must be wrapped around with :class:`tg.controllers.WSGIAppController`.
44
 
39
 
45
     """
40
     """
46
-    # FIXME - D.A. - 2013-11-19
47
-    # python3 port is not yet available for the tgext.admin module
48
-    #
49
-    # admin = AdminController(model, DBSession, config_type=TGAdminConfig)
41
+
42
+    admin = tgac.AdminController(
43
+        pm,
44
+        pm.DBSession,
45
+        config_type = tgat.TGAdminConfig
46
+    )
50
 
47
 
51
     api   = pbca.PODApiController()
48
     api   = pbca.PODApiController()
52
     debug = pbcd.DebugController()
49
     debug = pbcd.DebugController()
106
     @expose('pboard.templates.dashboard')
103
     @expose('pboard.templates.dashboard')
107
     @require(predicates.in_group('user', msg=l_('Please login to access this page')))
104
     @require(predicates.in_group('user', msg=l_('Please login to access this page')))
108
     def dashboard(self):
105
     def dashboard(self):
109
-      loCurrentUser   = pld.PODStaticController.getCurrentUser()
110
-      loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
106
+        loCurrentUser   = pld.PODStaticController.getCurrentUser()
107
+        loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
111
 
108
 
112
-      loLastModifiedNodes = loApiController.getLastModifiedNodes(10)
113
-      loWhatsHotNodes     = loApiController.getNodesByStatus('hot', 5)
114
-      loActionToDoNodes   = loApiController.getNodesByStatus('actiontodo', 5)
115
-      return dict(last_modified_nodes=loLastModifiedNodes, whats_hot_nodes=loWhatsHotNodes, action_to_do_nodes = loActionToDoNodes)
109
+        loLastModifiedNodes = loApiController.getLastModifiedNodes(10)
110
+        loWhatsHotNodes     = loApiController.getNodesByStatus('hot', 5)
111
+        loActionToDoNodes   = loApiController.getNodesByStatus('actiontodo', 5)
112
+        return dict(last_modified_nodes=loLastModifiedNodes, whats_hot_nodes=loWhatsHotNodes, action_to_do_nodes = loActionToDoNodes)
116
 
113
 
117
 
114
 
118
     @expose('pboard.templates.document')
115
     @expose('pboard.templates.document')
119
     @require(predicates.in_group('user', msg=l_('Please login to access this page')))
116
     @require(predicates.in_group('user', msg=l_('Please login to access this page')))
120
-    def document(self, node=0, came_from=lurl('/')):
117
+    def document(self, node=0, came_from=lurl('/'), highlight=''):
121
         """show the user dashboard"""
118
         """show the user dashboard"""
122
         loCurrentUser   = pld.PODStaticController.getCurrentUser()
119
         loCurrentUser   = pld.PODStaticController.getCurrentUser()
123
         loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
120
         loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
125
         # loRootNodeList   = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.parent_id==None).order_by(pbmd.PBNode.node_order).all()
122
         # loRootNodeList   = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.parent_id==None).order_by(pbmd.PBNode.node_order).all()
126
         loRootNodeList = loApiController.buildTreeListForMenu(pbmd.PBNodeStatus.getVisibleIdsList())
123
         loRootNodeList = loApiController.buildTreeListForMenu(pbmd.PBNodeStatus.getVisibleIdsList())
127
         liNodeId         = int(node)
124
         liNodeId         = int(node)
128
-        
125
+
129
         loCurrentNode    = None
126
         loCurrentNode    = None
130
         loNodeStatusList = None
127
         loNodeStatusList = None
131
         try:
128
         try:
133
           loCurrentNode    = loApiController.getNode(liNodeId)
130
           loCurrentNode    = loApiController.getNode(liNodeId)
134
         except Exception as e:
131
         except Exception as e:
135
           flash(_('Document not found'), 'error')
132
           flash(_('Document not found'), 'error')
136
-        
133
+
137
         # FIXME - D.A - 2013-11-07 - Currently, the code build a new item if no item found for current user
134
         # FIXME - D.A - 2013-11-07 - Currently, the code build a new item if no item found for current user
138
         # the correct behavior should be to redirect to setup page
135
         # the correct behavior should be to redirect to setup page
139
         if loCurrentNode is not None and "%s"%loCurrentNode.node_id!=node:
136
         if loCurrentNode is not None and "%s"%loCurrentNode.node_id!=node:
140
           redirect(tg.url('/document/%i'%loCurrentNode.node_id))
137
           redirect(tg.url('/document/%i'%loCurrentNode.node_id))
141
-          
138
+
142
         if loCurrentNode is None:
139
         if loCurrentNode is None:
143
           loCurrentNode = loApiController.getNode(0) # try to get an item
140
           loCurrentNode = loApiController.getNode(0) # try to get an item
144
           if loCurrentNode is not None:
141
           if loCurrentNode is not None:
150
             pm.DBSession.flush()
147
             pm.DBSession.flush()
151
             redirect(tg.url('/document/%i'%loCurrentNode.node_id))
148
             redirect(tg.url('/document/%i'%loCurrentNode.node_id))
152
 
149
 
153
-        return dict(root_node_list=loRootNodeList, current_node=loCurrentNode, node_status_list = loNodeStatusList)
150
+        return dict(
151
+            root_node_list=loRootNodeList,
152
+            current_node=loCurrentNode,
153
+            node_status_list = loNodeStatusList,
154
+            keywords = highlight
155
+        )
156
+
157
+    @expose('pboard.templates.search')
158
+    @require(predicates.in_group('user', msg=l_('Please login to access this page')))
159
+    def search(self, keywords=''):
160
+        loCurrentUser   = pld.PODStaticController.getCurrentUser()
161
+        loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
162
+
163
+        loFoundNodes = loApiController.searchNodesByText(keywords.split())
164
+
165
+        return dict(search_string=keywords, found_nodes=loFoundNodes)
166
+
167
+
154
 
168
 

+ 23 - 0
pboard/pboard/lib/dbapi.py View File

9
 from sqlalchemy.types import Unicode, Integer, DateTime, Text
9
 from sqlalchemy.types import Unicode, Integer, DateTime, Text
10
 from sqlalchemy.orm import relation, synonym
10
 from sqlalchemy.orm import relation, synonym
11
 from sqlalchemy.orm import joinedload_all
11
 from sqlalchemy.orm import joinedload_all
12
+import sqlalchemy as sqla
12
 
13
 
13
 from pboard.model import DeclarativeBase, metadata, DBSession
14
 from pboard.model import DeclarativeBase, metadata, DBSession
14
 from pboard.model import data as pbmd
15
 from pboard.model import data as pbmd
90
     return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).order_by(pbmd.PBNode.updated_at.desc()).limit(piMaxNodeNb).all()
91
     return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).order_by(pbmd.PBNode.updated_at.desc()).limit(piMaxNodeNb).all()
91
 
92
 
92
 
93
 
94
+  def searchNodesByText(self, plKeywordList, piMaxNodeNb=100):
95
+    """
96
+    Returns a list of nodes order by type, nodes which contain at least one of the keywords
97
+    """
98
+    liOwnerIdList = self._getUserIdListForFiltering()
99
+
100
+    loKeywordFilteringClauses = []
101
+    for keyword in plKeywordList:
102
+        loKeywordFilteringClauses.append(pbmd.PBNode.data_label.ilike('%'+keyword+'%'))
103
+        loKeywordFilteringClauses.append(pbmd.PBNode.data_content.ilike('%'+keyword+'%'))
104
+
105
+    loKeywordFilteringClausesAsOr = sqla.or_(*loKeywordFilteringClauses) # Combine them with or to a BooleanClauseList
106
+
107
+    loResultsForSomeKeywords = DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren"))\
108
+        .filter(loKeywordFilteringClausesAsOr)\
109
+        .filter(pbmd.PBNode.owner_id.in_(liOwnerIdList))\
110
+        .order_by(sqla.desc(pbmd.PBNode.node_type))\
111
+        .limit(piMaxNodeNb)\
112
+        .all()
113
+
114
+    return loResultsForSomeKeywords
115
+
93
   def getNodesByStatus(self, psNodeStatus, piMaxNodeNb=5):
116
   def getNodesByStatus(self, psNodeStatus, piMaxNodeNb=5):
94
     liOwnerIdList = self._getUserIdListForFiltering()
117
     liOwnerIdList = self._getUserIdListForFiltering()
95
     return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.node_status==psNodeStatus).order_by(pbmd.PBNode.updated_at).limit(piMaxNodeNb).all()
118
     return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.node_status==psNodeStatus).order_by(pbmd.PBNode.updated_at).limit(piMaxNodeNb).all()

+ 42 - 7
pboard/pboard/model/data.py View File

6
 from datetime import datetime
6
 from datetime import datetime
7
 from hashlib import sha256
7
 from hashlib import sha256
8
 
8
 
9
+import bs4
9
 from sqlalchemy import Table, ForeignKey, Column, Sequence
10
 from sqlalchemy import Table, ForeignKey, Column, Sequence
10
 from sqlalchemy.types import Unicode, Integer, DateTime, Text, LargeBinary
11
 from sqlalchemy.types import Unicode, Integer, DateTime, Text, LargeBinary
11
 from sqlalchemy.orm import relation, synonym, relationship
12
 from sqlalchemy.orm import relation, synonym, relationship
263
     return self.getChildrenOfType([PBNodeType.Comment], PBNode.getSortingKeyBasedOnDataDatetime, True)
264
     return self.getChildrenOfType([PBNodeType.Comment], PBNode.getSortingKeyBasedOnDataDatetime, True)
264
 
265
 
265
   def getIconClass(self):
266
   def getIconClass(self):
267
+    if self.node_type==PBNodeType.Data and self.getStaticChildNb()>0:
268
+      return PBNode.getIconClassForNodeType('folder')
269
+    else:
270
+      return PBNode.getIconClassForNodeType(self.node_type)
271
+
272
+  def getBreadCrumbNodes(self):
273
+    loNodes = []
274
+    if self._oParent!=None:
275
+      loNodes = self._oParent.getBreadCrumbNodes()
276
+      loNodes.append(self._oParent)
277
+    return loNodes
278
+
279
+  def getContentWithHighlightedKeywords(self, plKeywords, psPlainText):
280
+    if len(plKeywords)<=0:
281
+      return psPlainText
282
+
283
+    lsPlainText = psPlainText
284
+
285
+    for lsKeyword in plKeywords:
286
+      lsPlainText = re.sub('(?i)(%s)' % lsKeyword, '<strong>\\1</strong>', lsPlainText)
287
+
288
+    return lsPlainText
289
+
290
+
291
+  @classmethod
292
+  def getIconClassForNodeType(cls, psIconType):
266
     laIconClass = dict()
293
     laIconClass = dict()
267
     laIconClass['node']   = 'fa fa-folder-open'
294
     laIconClass['node']   = 'fa fa-folder-open'
268
     laIconClass['folder'] = 'fa fa-folder-open'
295
     laIconClass['folder'] = 'fa fa-folder-open'
269
     laIconClass['data']   = 'fa fa-file-text-o'
296
     laIconClass['data']   = 'fa fa-file-text-o'
270
 
297
 
271
-    laIconClass['file']   = 'fa fa-file-text-o'
298
+    laIconClass['file']   = 'fa fa-paperclip'
272
     laIconClass['event']  = 'fa fa-calendar'
299
     laIconClass['event']  = 'fa fa-calendar'
273
     laIconClass['contact'] = 'fa fa-user'
300
     laIconClass['contact'] = 'fa fa-user'
274
     laIconClass['comment'] = 'fa fa-comments-o'
301
     laIconClass['comment'] = 'fa fa-comments-o'
302
+    return laIconClass[psIconType]
303
+
275
 
304
 
276
-    if self.node_type==PBNodeType.Data and self.getStaticChildNb()>0:
277
-      return laIconClass['folder']
278
-    else:
279
-      return laIconClass[self.node_type]
280
-      
281
   def getUserFriendlyNodeType(self):
305
   def getUserFriendlyNodeType(self):
282
     laNodeTypesLng = dict()
306
     laNodeTypesLng = dict()
283
     laNodeTypesLng['node']   = 'Document' # FIXME - D.A. - 2013-11-14 - Make text translatable
307
     laNodeTypesLng['node']   = 'Document' # FIXME - D.A. - 2013-11-14 - Make text translatable
304
   def getFormattedTime(self, poDateTime, psDateTimeFormat = '%H:%M'):
328
   def getFormattedTime(self, poDateTime, psDateTimeFormat = '%H:%M'):
305
     return poDateTime.strftime(psDateTimeFormat)
329
     return poDateTime.strftime(psDateTimeFormat)
306
 
330
 
307
-  def getStatus(self):
331
+  def getStatus(self) -> PBNodeStatusItem:
308
     loStatus = PBNodeStatus.getStatusItem(self.node_status)
332
     loStatus = PBNodeStatus.getStatusItem(self.node_status)
309
     if loStatus.status_id!='automatic':
333
     if loStatus.status_id!='automatic':
310
       return loStatus
334
       return loStatus
328
       lsTruncatedLabel = self.data_label
352
       lsTruncatedLabel = self.data_label
329
     return lsTruncatedLabel
353
     return lsTruncatedLabel
330
 
354
 
355
+  def getTruncatedContentAsText(self, piCharNb):
356
+    lsPlainText = ''.join(bs4.BeautifulSoup(self.data_content).findAll(text=True))
357
+    lsTruncatedContent = ''
358
+
359
+    liMaxLength = int(piCharNb)
360
+    if len(lsPlainText)>liMaxLength:
361
+      lsTruncatedContent = lsPlainText[0:liMaxLength-1]+'…'
362
+    else:
363
+      lsTruncatedContent = lsPlainText
364
+    return lsTruncatedContent
365
+
331
   def getTagList(self):
366
   def getTagList(self):
332
     loPattern = re.compile('(^|\s|@)@(\w+)')
367
     loPattern = re.compile('(^|\s|@)@(\w+)')
333
     loResults = re.findall(loPattern, self.data_content)
368
     loResults = re.findall(loPattern, self.data_content)

+ 12 - 3
pboard/pboard/public/css/style.css View File

122
   position:fixed;
122
   position:fixed;
123
   left:0;
123
   left:0;
124
   top:0;
124
   top:0;
125
-  z-index:0 !important;
125
+  z-index:2001 !important;
126
   background-color:white;
126
   background-color:white;
127
+
128
+  padding: 0.5em 0.5em 0.5em 0.5em;
127
   
129
   
128
   filter: alpha(opacity=90); /* internet explorer */
130
   filter: alpha(opacity=90); /* internet explorer */
129
   -khtml-opacity: 0.9;      /* khtml, old safari */
131
   -khtml-opacity: 0.9;      /* khtml, old safari */
132
 }
134
 }
133
 
135
 
134
 .full-size-overlay-inner {
136
 .full-size-overlay-inner {
135
-  margin: 3.5em 0.5em 0.5em 0.5em;
137
+  margin: 0.5em 1em 0.5em 0em;
136
   overflow: auto;
138
   overflow: auto;
137
-  max-height: 85%;
139
+  max-height: 90%;
138
 }
140
 }
139
 
141
 
140
 
142
 
181
 }
183
 }
182
 
184
 
183
 .navbar .nav > li > a.pod-do-not-display { display: none; }
185
 .navbar .nav > li > a.pod-do-not-display { display: none; }
186
+
187
+/* SEARCH RESULTS SCREEN */
188
+
189
+div.search-result-item > h5 {
190
+  margin-bottom: 0;
191
+}
192
+

+ 5 - 2
pboard/pboard/public/javascript/pod.js View File

6
       $('.pod-toggle-full-screen-button > i').removeClass('fa-compress')
6
       $('.pod-toggle-full-screen-button > i').removeClass('fa-compress')
7
       $('.pod-toggle-full-screen-button > i').addClass('fa-expand')
7
       $('.pod-toggle-full-screen-button > i').addClass('fa-expand')
8
     } else {
8
     } else {
9
+      // Toggle from normal to fullscreen
9
       $(outerWidgetId).addClass('full-size-overlay');
10
       $(outerWidgetId).addClass('full-size-overlay');
10
       $(innerWidgetId).addClass('full-size-overlay-inner');
11
       $(innerWidgetId).addClass('full-size-overlay-inner');
11
       $('.pod-toggle-full-screen-button > i').removeClass('fa-expand')
12
       $('.pod-toggle-full-screen-button > i').removeClass('fa-expand')
94
     $("#current-document-content-edit-button" ).click(function() {
95
     $("#current-document-content-edit-button" ).click(function() {
95
       $("#current-document-content" ).css("display", "none");
96
       $("#current-document-content" ).css("display", "none");
96
       $("#current-document-content-edit-form" ).css("display", "block");
97
       $("#current-document-content-edit-form" ).css("display", "block");
98
+      $("#current-document-toobar").css("display", "none");
97
     });
99
     });
98
     $("#current-document-content" ).dblclick(function() {
100
     $("#current-document-content" ).dblclick(function() {
99
       $("#current-document-content" ).css("display", "none");
101
       $("#current-document-content" ).css("display", "none");
100
       $("#current-document-content-edit-form" ).css("display", "block");
102
       $("#current-document-content-edit-form" ).css("display", "block");
101
     });
103
     });
102
-    $("#current-document-content-edit-cancel-button" ).click(function() {
104
+    $("#current-document-content-edit-cancel-button, #current-document-content-edit-cancel-button-top" ).click(function() {
103
       $("#current-document-content" ).css("display", "block");
105
       $("#current-document-content" ).css("display", "block");
104
       $("#current-document-content-edit-form" ).css("display", "none");
106
       $("#current-document-content-edit-form" ).css("display", "none");
107
+      $("#current-document-toobar").css("display", "block");
105
     });
108
     });
106
-    $('#current-document-content-edit-save-button').on('click', function(e){
109
+    $('#current-document-content-edit-save-button, #current-document-content-edit-save-button-top').on('click', function(e){
107
       // We don't want this to act as a link so cancel the link action
110
       // We don't want this to act as a link so cancel the link action
108
       e.preventDefault();
111
       e.preventDefault();
109
       $('#current_node_textarea_wysiwyg').cleanHtml();
112
       $('#current_node_textarea_wysiwyg').cleanHtml();

+ 194 - 0
pboard/pboard/templates/document-widgets.mak View File

1
+<%inherit file="local:templates.master"/>
2
+<%namespace name="POD" file="pboard.templates.pod"/>
3
+
4
+<%def name="node_treeview_for_set_parent_menu(node_id, node_list, indentation=-1)">
5
+  % if indentation==-1:
6
+    <li>
7
+      <a href="${tg.url('/api/set_parent_node?node_id=%i&new_parent_id=0'%(current_node.node_id))}">
8
+        <i class="fa fa-file-text-o"></i> ${_('Home')}
9
+      </a>
10
+      ${node_treeview_for_set_parent_menu(node_id, node_list, 0)}
11
+    </li>
12
+  % else:
13
+    % if len(node_list)>0:
14
+      <ul style="list-style: none;">
15
+      % for new_parent_node in node_list:
16
+        <li>
17
+          <a href="${tg.url('/api/set_parent_node?node_id=%i&new_parent_id=%i'%(node_id, new_parent_node.node_id))}"><i class="fa fa-file-text-o"></i> ${new_parent_node.getTruncatedLabel(40-indentation*2)}
18
+          </a>
19
+          ${node_treeview_for_set_parent_menu(node_id, new_parent_node.getStaticChildList(), indentation+1)}
20
+        </li>
21
+      % endfor
22
+      </ul>
23
+    % endif
24
+  % endif
25
+</%def>
26
+
27
+<%def name="Toolbar(poNode, plNodeStatusList, plRootNodes, psDivId)">
28
+  <div id="${psDivId}">
29
+    <div class="btn-group">
30
+  % if poNode.parent_id!=None and poNode.parent_id!=0:
31
+      ${POD.EditButton('current-document-content-edit-button', True)}
32
+  % endif
33
+      <button class="btn btn-small"  data-toggle="dropdown" href="#"> 
34
+        <i class="fa  fa-signal"></i>
35
+        ${_("Change status")}
36
+      </button>
37
+      <a class="btn btn-small dropdown-toggle" data-toggle="dropdown" href="#">
38
+        <span class="caret"></span>
39
+      </a>
40
+      <ul class="dropdown-menu">
41
+      % for node_status in plNodeStatusList:
42
+        % if node_status.status_id==poNode.getStatus().status_id:
43
+        <li title="${h.getExplanationAboutStatus(node_status.status_id, current_node.getStatus().status_id)}">
44
+          <a class="${node_status.css}" href="#"  style="color: #999;">
45
+            <i class="${node_status.icon_id}"></i> ${node_status.label}
46
+          </a>
47
+        </li>
48
+        % else:
49
+        <li title="${h.getExplanationAboutStatus(node_status.status_id, current_node.getStatus().status_id)}">
50
+          <a class="${node_status.css}" href="${tg.url('/api/edit_status?node_id=%i&node_status=%s'%(current_node.node_id, node_status.status_id))}">
51
+            <i class="${node_status.icon_id}"></i> ${node_status.label}
52
+          </a>
53
+        </li>
54
+        % endif
55
+      % endfor
56
+      </ul>
57
+    </div>
58
+    <div class="btn-group">
59
+      <button class="btn btn-small btn-success"  data-toggle="dropdown" href="#">
60
+        <i class="fa fa-plus"></i> ${_('Add')}
61
+      </button>
62
+      <a class="btn btn-small dropdown-toggle" data-toggle="dropdown" href="#"><span class="caret"></span></a>
63
+      <ul class="dropdown-menu">
64
+      
65
+        <li>
66
+          <div class="btn-success strong" ><strong><i class="fa fa-magic"></i> Add New...</strong><br/></div>
67
+          <div class="pod-grey"><i>create a totally new item...</i></div>
68
+        </li>
69
+
70
+        <li><a><i class="fa fa-file-text-o"></i> Document</a></li>
71
+        <li><a><i class="fa fa-paperclip"></i> File</a></li>
72
+        <li><a><i class="fa fa-calendar"></i> Event</a></li>
73
+        <li><a><i class="fa fa-user"></i> Contact</a></li>
74
+        <li><a><i class="fa fa-comments-o"></i> Comment</a></li>
75
+
76
+        <li class="divider" role="presentation"></li>
77
+
78
+        <li>
79
+          <div class="btn-warning strong" ><strong><i class="fa fa-link"></i> Add Existing...</strong><br/></div>
80
+          <div class="pod-grey"><i>link to an existing item...</i></div>
81
+        </li>
82
+        <li><a><i class="fa fa-file-text-o"></i> Document</a></li>
83
+        <li><a><i class="fa fa-paperclip"></i> File</a></li>
84
+        <li><a><i class="fa fa-calendar"></i> Event</a></li>
85
+        <li><a><i class="fa fa-user"></i> Contact</a></li>
86
+        <li><a><i class="fa fa-comments-o"></i> Comment</a></li>
87
+
88
+      </ul>
89
+    </div>
90
+    <div class="btn-group ">
91
+      <a
92
+        class="btn btn-small btn-warning"
93
+        href="#"
94
+        data-toggle="dropdown"
95
+        title="${_('Move to')}"
96
+        ><i class="fa fa-arrows"></i></a>
97
+      <ul class="dropdown-menu">
98
+        <li >
99
+          <div class="btn-warning strong" ><strong><i class="fa fa-magic"></i> ${_("Move the document...")}</strong><br/></div>
100
+          <div class="pod-grey"><i>move the document to...</i></div>
101
+        </li>
102
+        ${node_treeview_for_set_parent_menu(poNode.node_id, plRootNodes)}
103
+      </ul>
104
+      <a
105
+        class="btn btn-small btn-danger"
106
+        href='${tg.url('/api/edit_status?node_id=%i&node_status=%s'%(poNode.node_id, 'deleted'))}'
107
+        id='current-document-force-delete-button' onclick="return confirm('${_('Delete current document?')}');"
108
+        title="${_('Delete')}"
109
+        ><i class="fa fa-trash-o"></i></a>
110
+    </div>
111
+  </div>
112
+</%def>
113
+
114
+<%def name="BreadCrumb(poNode)">
115
+  <ul class="breadcrumb span12">
116
+    <li>
117
+      <span class="divider"> / Documents /</span>
118
+    </li>
119
+    % for breadcrumb_node in poNode.getBreadCrumbNodes():
120
+    <li>
121
+      <a href="${tg.url('/document/%s'%(breadcrumb_node.node_id))}">${breadcrumb_node.getTruncatedLabel(30)}</a>
122
+      <span class="divider">/</span>
123
+    </li>
124
+    % endfor
125
+    <li class="active">${poNode.data_label}</li>
126
+  </ul>
127
+</%def>
128
+
129
+<%def name="EditForm(poNode)">
130
+  <form
131
+    style="display: none;"
132
+    id="current-document-content-edit-form"
133
+    method="post"
134
+    action="${tg.url('/api/edit_label_and_content')}"
135
+  >
136
+    <div>
137
+      ${POD.CancelButton('current-document-content-edit-cancel-button-top', True)}
138
+      ${POD.SaveButton('current-document-content-edit-save-button-top', True)}
139
+    </div>
140
+    <div style="padding: 0.5em 0 0 0">
141
+      <input type="hidden" name="node_id" value="${current_node.node_id}"/>
142
+      <input type="hidden" name="data_content" id="current_node_textarea" />
143
+      <input
144
+        type="text"
145
+        name="data_label"
146
+        value="${current_node.data_label}"
147
+        class="span4"
148
+        placeholder="${_('document title')}"
149
+      />
150
+    </div>
151
+    <div>
152
+      ${POD.RichTextEditor('current_node_textarea_wysiwyg', current_node.data_content)}
153
+    </div>
154
+    <div class="pull-right">
155
+      ${POD.CancelButton('current-document-content-edit-cancel-button', True)}
156
+      ${POD.SaveButton('current-document-content-edit-save-button', True)}
157
+    </div>
158
+  </form>
159
+</%def>
160
+
161
+<%def name="ShowContent(poNode, psKeywords)">
162
+  <div>
163
+  % if len(psKeywords)>0 and psKeywords!='':
164
+      ${poNode.getContentWithHighlightedKeywords(psKeywords.split(), poNode.getContentWithTags())|n}
165
+  % else:
166
+      ${poNode.getContentWithTags()|n}
167
+  % endif
168
+  </div>
169
+</%def>
170
+
171
+<%def name="ShowTitle(poNode, psKeywords, psId)">
172
+  <h3 id="${psId}" title="Document ${poNode.node_id}: ${poNode.data_label}">
173
+    ${poNode.data_label}
174
+    <sup class="label ${poNode.getStatus().css}" href="#">
175
+      ${poNode.getStatus().label}
176
+    </sup>
177
+  </h3>
178
+</%def>
179
+
180
+#######
181
+##
182
+## METADATA TAB FUNCTIONS
183
+##
184
+<%def name="MetadataTab(psAnchorName, psDataToggleName, psTitle, psFontAwesomeIconClass, plItems)">
185
+  <a
186
+    href="${psAnchorName}" 
187
+    data-toggle="${psDataToggleName}" 
188
+    title="${psTitle}"
189
+  >
190
+    <i class="pod-dark-grey fa ${psFontAwesomeIconClass}"></i>
191
+    ${POD.ItemNb(plItems)}
192
+  </a>
193
+</%def>
194
+

+ 122 - 136
pboard/pboard/templates/document.mak View File

1
 <%inherit file="local:templates.master"/>
1
 <%inherit file="local:templates.master"/>
2
 <%namespace name="POD" file="pboard.templates.pod"/>
2
 <%namespace name="POD" file="pboard.templates.pod"/>
3
+<%namespace name="DOC" file="pboard.templates.document-widgets"/>
3
 
4
 
4
 <%def name="title()">
5
 <%def name="title()">
5
 pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id} / ${current_node.getStatus().label}]
6
 pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id} / ${current_node.getStatus().label}]
6
 </%def>
7
 </%def>
7
 
8
 
8
-<%def name="node_treeview_for_set_parent_menu(node_id, node_list, indentation=-1)">
9
-  % if indentation==-1:
10
-    <li><a href="${tg.url('/api/set_parent_node?node_id=%i&new_parent_id=0'%(current_node.node_id))}">${_('Home')}</a>
11
-      ${node_treeview_for_set_parent_menu(node_id, node_list, 0)}
12
-    </li>
13
-  % else:
14
-    % if len(node_list)>0:
15
-      <ul>
16
-      % for new_parent_node in node_list:
17
-        <li>
18
-          <a href="${tg.url('/api/set_parent_node?node_id=%i&new_parent_id=%i'%(node_id, new_parent_node.node_id))}">${new_parent_node.getTruncatedLabel(40-indentation*2)}</a>
19
-          ${node_treeview_for_set_parent_menu(node_id, new_parent_node.getStaticChildList(), indentation+1)}
20
-        </li>
21
-      % endfor
22
-      </ul>
23
-    % endif
24
-  % endif
25
-</%def>
26
-
27
 <%def name="node_treeview(node_list, indentation=-1)">
9
 <%def name="node_treeview(node_list, indentation=-1)">
28
   % if indentation==-1:
10
   % if indentation==-1:
29
     <div id='pod-menu-item-0' class="pod-toolbar-parent" style="padding-left: 0.5em; position: relative;">
11
     <div id='pod-menu-item-0' class="pod-toolbar-parent" style="padding-left: 0.5em; position: relative;">
82
     % endif
64
     % endif
83
 </%def>
65
 </%def>
84
 
66
 
67
+#######
68
+##
69
+## HERE COMES THE BREADCRUMB
70
+##
71
+  <div class="row">
72
+    ${DOC.BreadCrumb(current_node)}
73
+  </div>
74
+
85
   <div class="row">
75
   <div class="row">
86
     <div id='application-left-panel' class="span3">
76
     <div id='application-left-panel' class="span3">
87
       <div>
77
       <div>
89
       </div>
79
       </div>
90
     </div>
80
     </div>
91
     <div id='application-main-panel' class="span9">
81
     <div id='application-main-panel' class="span9">
92
-    % if current_node.parent_id!=None and current_node.parent_id!=0:
93
-      <div class="btn-group">
94
-        <a class="btn " href="${tg.url('/document/%i'%current_node.parent_id)}" title="${_("Go to parent document")}"><i class="fa fa-hand-o-left"></i></a>
95
-      </div>
96
-    % endif
97
-      <div class="btn-group">
98
-        <button class="btn"  data-toggle="dropdown" href="#">${_("Change status")}</button>
99
-        <a class="btn dropdown-toggle" data-toggle="dropdown" href="#"><span class="caret"></span></a>
100
-        <ul class="dropdown-menu">
101
-        % for node_status in node_status_list:
102
-          % if node_status.status_id==current_node.getStatus().status_id:
103
-          <li title="${h.getExplanationAboutStatus(node_status.status_id, current_node.getStatus().status_id)}">
104
-            <a class="${node_status.css}" href="#"  style="color: #999;">
105
-              <i class="${node_status.icon_id}"></i> ${node_status.label}
106
-            </a>
107
-          </li>
108
-          % else:
109
-          <li title="${h.getExplanationAboutStatus(node_status.status_id, current_node.getStatus().status_id)}">
110
-            <a class="${node_status.css}" href="${tg.url('/api/edit_status?node_id=%i&node_status=%s'%(current_node.node_id, node_status.status_id))}">
111
-              <i class="${node_status.icon_id}"></i> ${node_status.label}
112
-            </a>
113
-          </li>
114
-          % endif
115
-        % endfor
116
-        </ul>
117
-      </div>
118
-      <div class="btn-group">
119
-        ${POD.EditButton('current-document-content-edit-button', True)}
120
-        <a class="btn" href="#" data-toggle="dropdown"><i class="icon-g-move"></i> ${_('Move to')} <span class="caret"></span></a>
121
-        <ul class="dropdown-menu">
122
-          ${node_treeview_for_set_parent_menu(current_node.node_id, root_node_list)}
123
-        </ul>
124
-
125
-        <a href='${tg.url('/api/edit_status?node_id=%i&node_status=%s'%(current_node.node_id, 'deleted'))}' id='current-document-force-delete-button' class="btn" onclick="return confirm('${_('Delete current document?')}');"><i class="fa fa-trash-o"></i> ${_('Delete')}</a>
126
-        
127
-      </div>
128
 
82
 
129
       <div class="row">
83
       <div class="row">
130
         <div id='application-document-panel' class="span5">
84
         <div id='application-document-panel' class="span5">
131
-          <p>
132
-            <div id='current-document-content' class="">
133
-              <h3 id="current-document-title">#${current_node.node_id} - ${current_node.data_label}
134
-                <span class="label ${current_node.getStatus().css}" href="#">${current_node.getStatus().label}</a>
135
-              
136
-              </h3>
137
-              ${current_node.getContentWithTags()|n}
138
-            </div>
139
-            <form style='display: none;' id="current-document-content-edit-form" method='post' action='${tg.url('/api/edit_label_and_content')}'>
140
-              <input type='hidden' name='node_id' value='${current_node.node_id}'/>
141
-              <input type="hidden" name='data_content' id="current_node_textarea" />
142
-              <input type='text' name='data_label' value='${current_node.data_label}' class="span4" placeholder="document title" />
143
-              ${POD.RichTextEditor('current_node_textarea_wysiwyg', current_node.data_content)}
144
-              ${POD.CancelButton('current-document-content-edit-cancel-button', True)}
145
-              ${POD.SaveButton('current-document-content-edit-save-button', True)}
146
-
147
-
148
-            </form>
149
-          </p>
85
+          <div id='current-document-content' class="">
86
+            ######
87
+            ##
88
+            ## CURRENT DOCUMENT TOOLBAR - START
89
+            ##
90
+            ## The Toolbar is a div with a specific id
91
+            ##
92
+            ${DOC.Toolbar(current_node, node_status_list, root_node_list, 'current-document-toobar')}
93
+            ${DOC.ShowTitle(current_node, keywords, 'current-document-title')}
94
+            ${DOC.ShowContent(current_node, keywords)}
95
+          </div>
96
+          ${DOC.EditForm(current_node)}
150
         </div>
97
         </div>
151
-        ## FIXME - D.A - 2013-11-07 - The following div should be span4 instead of span3 but some bug make it impossible
152
         <div id='application-metadata-panel' class="span4">
98
         <div id='application-metadata-panel' class="span4">
153
           <div class="tabbable">
99
           <div class="tabbable">
154
-            <ul class="nav nav-tabs">
155
-                ## FIXME - D.A. - 2013-11-07 - TO REMOVE OR TO REACTIVATE  <li class="active"><a href="#tags" data-toggle="tab" title="${_('Tags')}"><i class='icon-g-tags'></i></a></li>
156
-                <li class="active"><a href="#events" data-toggle="tab" title="History"><i class="pod-dark-grey fa fa-calendar"></i>${POD.ItemNb(current_node.getEvents())}</a></li>
157
-                <li><a href="#contacts" data-toggle="tab" title="Contacts"><i class="pod-dark-grey fa fa-user"></i>${POD.ItemNb(current_node.getContacts())}</a></li>
158
-                <li><a href="#comments" data-toggle="tab" title="Comments"><i class="pod-dark-grey fa fa-comments-o"></i>${POD.ItemNb(current_node.getComments())}</a></li>
159
-                <li><a href="#files" data-toggle="tab" title="Files"><i class="pod-dark-grey fa  fa-file-text-o"></i>${POD.ItemNb(current_node.getFiles())}</a></li>
100
+            <ul class="nav nav-tabs" style="margin-bottom: 0.5em;">
101
+                <li>${DOC.MetadataTab('#subdocuments', 'tab', _('Subdocuments'), 'fa-file-text-o', current_node.getChildren())}</li>
102
+                <li class="active">${DOC.MetadataTab('#events', 'tab', _('Calendar'), 'fa-calendar', current_node.getEvents())}</li>
103
+                <li>${DOC.MetadataTab('#contacts', 'tab', _('Address book'), 'fa-user', current_node.getContacts())}</li>
104
+                <li>${DOC.MetadataTab('#comments', 'tab', _('Comment thread'), 'fa-comments-o', current_node.getComments())}</li>
105
+                <li>${DOC.MetadataTab('#files', 'tab', _('Attachments'), 'fa-paperclip', current_node.getFiles())}</li>
106
+                <li class="pull-right">${DOC.MetadataTab('#accessmanagement', 'tab', _('Access Management'), 'fa-key', [])}</li>
160
             </ul>
107
             </ul>
161
             <div class="tab-content">
108
             <div class="tab-content">
162
                 ################################
109
                 ################################
163
                 ##
110
                 ##
164
-                ## PANEL SHOWING LIST OF TAGS
111
+                ## PANEL SHOWING LIST OF SUB DOCUMENTS
165
                 ##
112
                 ##
166
                 ################################
113
                 ################################
167
-                <!-- DEBUG - D.A. - 2013-11-07 - Not using tags for th moment
168
-                <div class="tab-pane" id="tags">
169
-                  <div class="well">
170
-                    <p>
171
-                      <i>
172
-                        ${_('Tags are automatically extracted from document content:')}
173
-                        <ul>
174
-                          <li>${_('<code>@visible_keyword</code> is a visible keyword generating a tag.')|n}</li>
175
-                          <li>
176
-                            ${_('<code>@invisible_keyword</code> is an <u>invisible</u> keyword generating a tag.')|n}</li>
177
-                        </ul>
178
-                      </i>
179
-                    </p>
180
-                    % for tag in current_node.getTagList():
181
-                      ${POD.Badge(tag)}
114
+                <!-- DEBUG - D.A. - 2013-11-07 - Not using tags for th moment -->
115
+                <div class="tab-pane" id="subdocuments">
116
+                  <p><strong>Sub-documents</strong></p> 
117
+                % if len(current_node.getChildren())<=0:
118
+                  <p class="pod-grey">
119
+                    ${_("There is currently no child documents.")}<br/>
120
+                  </p>
121
+                  <p>
122
+                    
123
+                    <a class="btn btn-success btn-small" href="${tg.url('/api/create_document?parent_id=%i'%current_node.node_id)}">
124
+                      <i class="fa fa-plus"></i> ${_("Add one")}
125
+                    </a>
126
+                  </p>
127
+                % else:
128
+                  <p>
129
+                    <a class="btn btn-success btn-small" href="${tg.url('/api/create_document?parent_id=%i'%current_node.node_id)}">
130
+                      <i class="fa fa-plus"></i> ${_("Add one")}
131
+                    </a>
132
+                  </p>
133
+
134
+                  <div>
135
+                    % for subnode in current_node.getChildren():
136
+                      <p style="list-style-type:none;">
137
+                        <i class="fa-fw ${subnode.getIconClass()}"></i>
138
+                          <a href="${tg.url('/document/%i'%subnode.node_id)}">
139
+                            ${subnode.data_label}
140
+                          </a>
141
+                      </p>
182
                     % endfor
142
                     % endfor
183
                   </div>
143
                   </div>
144
+                % endif
184
                 </div>
145
                 </div>
185
-                -->
146
+                
186
                 ################################
147
                 ################################
187
                 ##
148
                 ##
188
                 ## PANEL SHOWING LIST OF EVENTS
149
                 ## PANEL SHOWING LIST OF EVENTS
189
                 ##
150
                 ##
190
                 ################################
151
                 ################################
191
                 <div class="tab-pane active" id="events">
152
                 <div class="tab-pane active" id="events">
192
-                  ${POD.AddButton('current-document-add-event-button', True, _(' Add event'))}
153
+                  <p><strong>Calendar</strong></p> 
154
+                % if len(current_node.getEvents())<=0:
155
+                  <p class="pod-grey">${_("The calendar is empty.")}<br/></p>
156
+                  <p>${POD.AddButton('current-document-add-event-button', True, _(' Add first event'))}</p>
157
+                % else:
158
+                  <p>${POD.AddButton('current-document-add-event-button', True, _(' Add an event'))}</p>
159
+                % endif
160
+                
193
                   <form style='display: none;' id='current-document-add-event-form' action='${tg.url('/api/create_event')}' method='post' class="well">
161
                   <form style='display: none;' id='current-document-add-event-form' action='${tg.url('/api/create_event')}' method='post' class="well">
194
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
162
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
195
                     <fieldset>
163
                     <fieldset>
225
                     </fieldset>
193
                     </fieldset>
226
                   </form>
194
                   </form>
227
 
195
 
228
-                % if len(current_node.getEvents())<=0:
229
-                  <p><i>${_('No history for the moment.')}</i></p>
230
-                % else:
196
+                % if len(current_node.getEvents())>0:
231
                   <table class="table table-striped table-hover table-condensed">
197
                   <table class="table table-striped table-hover table-condensed">
232
                     <thead>
198
                     <thead>
233
                       <tr>
199
                       <tr>
261
                 ##
227
                 ##
262
                 ##############################
228
                 ##############################
263
                 <div class="tab-pane" id="contacts">
229
                 <div class="tab-pane" id="contacts">
264
-                
230
+                  <p><strong>${_('Address book')}</strong></p> 
231
+                % if len(current_node.getContacts())<=0:
232
+                  <p class="pod-grey">${_("The address book is empty.")}<br/></p>
233
+                  <p>${POD.AddButton('current-document-add-contact-button', True, _(' Add first contact'), True)}</p>
234
+                % else:
235
+                  <p>${POD.AddButton('current-document-add-contact-button', True, _(' Add a contact'))}</p>
236
+                % endif
237
+
265
                   <!-- ADD CONTACT FORM -->
238
                   <!-- ADD CONTACT FORM -->
266
-                  ${POD.AddButton('current-document-add-contact-button', True, _(' Add contact'))}
267
                   <form style='display: none;' id='current-document-add-contact-form' action='${tg.url('/api/create_contact')}' method='post' class="well">
239
                   <form style='display: none;' id='current-document-add-contact-form' action='${tg.url('/api/create_contact')}' method='post' class="well">
268
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
240
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
269
                     <fieldset>
241
                     <fieldset>
297
                       </div>
269
                       </div>
298
                     </div>
270
                     </div>
299
                   % endfor
271
                   % endfor
300
-
301
-
302
                 </div>
272
                 </div>
303
                 ################################
273
                 ################################
304
                 ##
274
                 ##
306
                 ##
276
                 ##
307
                 ################################
277
                 ################################
308
                 <div class="tab-pane" id="comments">
278
                 <div class="tab-pane" id="comments">
279
+                  <p><strong>${_('Comment thread')}</strong></p> 
280
+                % if len(current_node.getComments())<=0:
281
+                  <p class="pod-grey">${_("The comment thread is empty.")}<br/></p>
282
+                  <p>${POD.AddButton('current-document-add-comment-button', True, _('Add first comment'), True)}</p>
283
+                % else:
284
+                  <p>${POD.AddButton('current-document-add-comment-button', True, _('Add a comment'))}</p>
285
+                % endif
286
+
309
                   <!-- ADD COMMENT FORM -->
287
                   <!-- ADD COMMENT FORM -->
310
-                  ${POD.AddButton('current-document-add-comment-button', True, _(' Add comment'))}
311
                   <form style='display: none;' id='current-document-add-comment-form' action='${tg.url('/api/create_comment')}' method='post' class="well">
288
                   <form style='display: none;' id='current-document-add-comment-form' action='${tg.url('/api/create_comment')}' method='post' class="well">
312
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
289
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
313
                     <fieldset>
290
                     <fieldset>
327
                   </form>
304
                   </form>
328
 
305
 
329
                   <!-- LIST OF COMMENTS -->
306
                   <!-- LIST OF COMMENTS -->
330
-                % if len(current_node.getComments())<=0:
331
-                  <p><i>${_('No comments.')}</i></p>
332
-                % else:
307
+                % if len(current_node.getComments())>0:
333
                   <table class="table table-striped table-hover table-condensed">
308
                   <table class="table table-striped table-hover table-condensed">
334
                     % for comment in current_node.getComments():
309
                     % for comment in current_node.getComments():
335
                       <tr title="Last updated: ${comment.updated_at}">
310
                       <tr title="Last updated: ${comment.updated_at}">
354
                 ##
329
                 ##
355
                 ################################
330
                 ################################
356
                 <div class="tab-pane" id="files">
331
                 <div class="tab-pane" id="files">
332
+                  <p><strong>${_('Attachments')}</strong></p> 
333
+                % if len(current_node.getFiles())<=0:
334
+                  <p class="pod-grey">${_("There is currently no attachment.")}<br/></p>
335
+                  <p>${POD.AddButton('current-document-add-file-button', True, _(' Attach first file'))}</p>
336
+                % else:
337
+                  <p>${POD.AddButton('current-document-add-file-button', True, _(' Attach a file'))}</p>
338
+                % endif
339
+
357
                   <!-- ADD FILE FORM -->
340
                   <!-- ADD FILE FORM -->
358
-                  ${POD.AddButton('current-document-add-file-button', True, _(' Add file'))}
359
                   <form style='display: none;' id='current-document-add-file-form' enctype="multipart/form-data" action='${tg.url('/api/create_file')}' method='post' class="well">
341
                   <form style='display: none;' id='current-document-add-file-form' enctype="multipart/form-data" action='${tg.url('/api/create_file')}' method='post' class="well">
360
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
342
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
361
                     <fieldset>
343
                     <fieldset>
378
                   </form>
360
                   </form>
379
 
361
 
380
                   <!-- LIST OF FILES -->
362
                   <!-- LIST OF FILES -->
381
-                % if len(current_node.getFiles())<=0:
382
-                  <p><i>${_('No files.')}</i></p>
383
-                % else:
384
-                  <table class="table table-striped table-hover table-condensed">
363
+                  <div>
364
+                % if len(current_node.getFiles())>0:
385
                     % for current_file in current_node.getFiles():
365
                     % for current_file in current_node.getFiles():
386
-                      <tr title="Last updated: ${current_file.updated_at}">
387
-                        <td>
388
-                          <a href="${tg.url('/api/get_file_content/%s'%(current_file.node_id))}" title="${_("Download file")}">
389
-                            <i class="fa fa-2x fa-file-text-o"></i>
390
-                          </a>
391
-                          ## FIXME - SHOW THUMBNAIL WHEN IT WILL BE OK<img src="${tg.url('/api/get_file_content_thumbnail/%s'%(current_file.node_id))}" class="img-polaroid">
392
-                        </td>
393
-                        <td>
394
-                          <b>${current_file.data_label}</b>
395
-                          <a class="pull-right" href="${tg.url('/api/get_file_content/%s'%(current_file.node_id))}" title="${_("Download file")}">
396
-                            <i class="fa fa-download"></i>
397
-                          </a>
398
-                          <a class="pull-right" href="${tg.url('/document/%i'%current_file.node_id)}" title="${_("Edit title or comment")}"><i class="fa fa-edit"></i></a>
399
-
400
-                          <br/>
401
-                          <p>
402
-                            ${current_file.data_content|n}
403
-                          </p>
404
-                        </td>
405
-                      </tr>
366
+                      <p style="list-style-type:none; margin-bottom: 0.5em;">
367
+                        <i class="fa fa-paperclip"></i>
368
+                        <a
369
+                          href="${tg.url('/document/%i'%current_file.node_id)}"
370
+                          title="${_('View the attachment')}: ${current_file.data_label}"
371
+                        >
372
+                          ${current_file.getTruncatedLabel(50)}
373
+                        </a>
374
+                        <a
375
+                          class="pull-right"
376
+                          href="${tg.url('/api/get_file_content/%s'%(current_file.node_id))}"
377
+                          title="${_('View the attachment')}"
378
+                        >
379
+                          <i class="fa fa-download"></i>
380
+                        </a>
381
+                      </p>
406
                     % endfor
382
                     % endfor
407
-                  </table>
408
                 % endif
383
                 % endif
384
+                  </div>
385
+                </div>
386
+                
387
+                
388
+                ################################
389
+                ##
390
+                ## PANEL SHOWING ACCESS MANAGEMENT
391
+                ##
392
+                ################################
393
+                <div class="tab-pane" id="accessmanagement">
394
+                  blabla
409
                 </div>
395
                 </div>
410
               </div>
396
               </div>
411
             </div>
397
             </div>

+ 9 - 8
pboard/pboard/templates/master.mak View File

168
                 <li><a href="${tg.url('/debug/identity')}"><i class="fa fa-user-md"></i>  request.identity</a></li>
168
                 <li><a href="${tg.url('/debug/identity')}"><i class="fa fa-user-md"></i>  request.identity</a></li>
169
               </ul>
169
               </ul>
170
             </li>
170
             </li>
171
-            
171
+            <li>
172
+              <form class="navbar-search  form-search" action="${tg.url('/search')}">
173
+                <div class="input-append">
174
+                  <input name="keywords" type="text" class="span2 search-query" placeholder="Search" value="${context.get('search_string', '')}">
175
+                  <button title="${_('Search')}" class="btn" type="submit"><i class="fa fa-search"></i></button>
176
+                </div>
177
+              </form>
178
+            </li>
179
+
172
             
180
             
173
             
181
             
174
           % endif
182
           % endif
209
                     <p></p>
217
                     <p></p>
210
                  </ul>
218
                  </ul>
211
               </li>
219
               </li>
212
-              
213
             % endif
220
             % endif
214
           </ul>
221
           </ul>
215
 
222
 
216
-          #####################
217
-          ## FIXME - D.A. - 2013-11-07 - Make search available
218
-          ## 
219
-          ## <form class="navbar-search pull-right" action="">
220
-          ##   <input type="text" class="search-query span2" placeholder="Search">
221
-          ## </form>
222
         </div><!-- /.nav-collapse -->
223
         </div><!-- /.nav-collapse -->
223
       </div><!-- /.container -->
224
       </div><!-- /.container -->
224
     </div><!-- /.navbar-inner -->
225
     </div><!-- /.navbar-inner -->

+ 83 - 30
pboard/pboard/templates/pod.mak View File

1
+<%def name="IconCssClass(psNodeType)" >
2
+  % if psNodeType=='data':
3
+    fa fa-file-text-o
4
+  % elif  psNodeType=='folder':
5
+    fa fa-folder-open
6
+  % elif  psNodeType=='node':
7
+    fa fa-file-text-o
8
+  % elif  psNodeType=='file':
9
+    fa fa-paperclip
10
+  % elif  psNodeType=='event':
11
+    fa fa-calendar
12
+  % elif  psNodeType=='contact':
13
+    fa fa-user
14
+  % elif  psNodeType=='comment':
15
+    fa fa-comments-o
16
+  % endif
17
+</%def>
18
+
19
+<%def name="DocumentTypeLabel(psNodeType)" ><%
20
+  labels = dict()
21
+  labels['data'] = 'document'
22
+  labels['folder'] = 'folder'
23
+  labels['node'] = 'node'
24
+  labels['file'] = 'file'
25
+  labels['event'] = 'event'
26
+  labels['contact'] = 'contact'
27
+  labels['comment'] = 'comment'
28
+  return labels[psNodeType]
29
+%></%def>
30
+
31
+<%def name="DocumentUrl(piNodeId, psHighlight)" >${tg.url('/document/%i?highlight=%s'%(piNodeId, psHighlight))}</%def>
32
+<%def name="DocumentUrlWithAnchor(piNodeId, psHighlight, psAnchor)" >${tg.url('/document/%i?highlight=%s#%s'%(piNodeId, psHighlight, psAnchor))}</%def>
33
+
1
 <%def name="Button(piId, pbWithLabel, psButtonCssClass, psButtonTitle, psButtonIcon, psButtonLabel)" >
34
 <%def name="Button(piId, pbWithLabel, psButtonCssClass, psButtonTitle, psButtonIcon, psButtonLabel)" >
2
   <button id='${piId}' type="button" class="${psButtonCssClass}" title="${psButtonTitle}"><i class="${psButtonIcon}"></i>${'' if (pbWithLabel==False) else ' %s'%(psButtonLabel)}</button>
35
   <button id='${piId}' type="button" class="${psButtonCssClass}" title="${psButtonTitle}"><i class="${psButtonIcon}"></i>${'' if (pbWithLabel==False) else ' %s'%(psButtonLabel)}</button>
3
 </%def>
36
 </%def>
4
 
37
 
5
 <%def name="SaveButton(piId, pbWithLabel=False)" >
38
 <%def name="SaveButton(piId, pbWithLabel=False)" >
6
-  ${Button(piId, pbWithLabel, 'btn btn-success', _('Save'), ' icon-g-ok-2 icon-g-white', _('Save'))}
39
+  ${Button(piId, pbWithLabel, 'btn btn-small btn-success', _('Save'), ' icon-g-ok-2 icon-g-white', _('Save'))}
7
 </%def>
40
 </%def>
8
 <%def name="EditButton(piId, pbWithLabel=False)" >
41
 <%def name="EditButton(piId, pbWithLabel=False)" >
9
-  ${Button(piId, pbWithLabel, 'btn', _('Edit'), 'icon-g-edit', _('Edit'))}
42
+  ${Button(piId, pbWithLabel, 'btn btn-small', _('Edit'), 'fa fa-edit', _('Edit'))}
10
 </%def>
43
 </%def>
11
 <%def name='CancelButton(piId, pbWithLabel=False)'>
44
 <%def name='CancelButton(piId, pbWithLabel=False)'>
12
-  ${Button(piId, pbWithLabel, 'btn ', _('Cancel'), 'icon-g-ban', _('Cancel'))}
45
+  ${Button(piId, pbWithLabel, 'btn btn-small', _('Cancel'), 'icon-g-ban', _('Cancel'))}
13
 </%def>
46
 </%def>
14
-<%def name='AddButton(piId, pbWithLabel=False, psLabel=None)'>
15
-  ${Button(piId, pbWithLabel, 'btn', psLabel or _('New'), 'icon-g-circle-plus', psLabel or _('New'))}
47
+<%def name='AddButton(piId, pbWithLabel=False, psLabel=None, pbIsCallToAction=True)'>
48
+% if pbIsCallToAction:
49
+  ${Button(piId, pbWithLabel, 'btn btn-small btn-success', psLabel or _('New'), 'fa fa-plus', psLabel or _('New'))}
50
+% else:
51
+  ${Button(piId, pbWithLabel, 'btn btn-small', psLabel or _('New'), 'fa fa-plus', psLabel or _('New'))}
52
+% endif
16
 </%def>
53
 </%def>
17
 <%def name='Badge(psLabel, psCssClass="")'>
54
 <%def name='Badge(psLabel, psCssClass="")'>
18
   <span class='badge ${psCssClass}'>${psLabel}</span>
55
   <span class='badge ${psCssClass}'>${psLabel}</span>
41
 <%def name='RichTextEditorToolbar(psRichTextEditorNodeId, psMenuOptions="styles|boldanditalic|lists|justifiers|links|images|undoredo|fullscreen")'>
78
 <%def name='RichTextEditorToolbar(psRichTextEditorNodeId, psMenuOptions="styles|boldanditalic|lists|justifiers|links|images|undoredo|fullscreen")'>
42
       <div class="btn-toolbar" data-role="${psRichTextEditorNodeId}-toolbar" data-target="${psRichTextEditorNodeId}">
79
       <div class="btn-toolbar" data-role="${psRichTextEditorNodeId}-toolbar" data-target="${psRichTextEditorNodeId}">
43
       % if psMenuOptions.find('styles')>=0:
80
       % if psMenuOptions.find('styles')>=0:
81
+      
44
         <div class="btn-group">
82
         <div class="btn-group">
45
-          <a class="btn" data-edit="formatBlock p"   title="Normal paragraph">§</h1></a></li>
46
-          <a class="btn" data-edit="formatBlock pre" title="Fixed width (code)">C</h1></a>
47
-          <a class="btn" data-edit="formatBlock h1"  title="Title - level 1">h1</a>
48
-          <a class="btn" data-edit="formatBlock h2"  title="Title - level 2">h2</a>
49
-          <a class="btn" data-edit="formatBlock h3"  title="Title - level 3">h3</a>
50
-          <a class="btn" data-edit="formatBlock h4"  title="Title - level 4">h4</a>
51
-          <a class="btn" data-edit="formatBlock h5"  title="Title - level 5">h5</a>
52
-          <a class="btn" data-edit="formatBlock h6"  title="Title - level 6">h6</a>
83
+          <a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
84
+            <i class="fa fa-font"></i>
85
+            <span class="caret"></span>
86
+          </a>
87
+          <ul class="dropdown-menu">
88
+          <!-- dropdown menu links -->
89
+            <li><a data-edit="formatBlock p"   title="Normal paragraph"><p style="margin: 0">text body</p></a></li>
90
+            <li><a data-edit="formatBlock pre" title="Fixed width (code)"><pre style="margin: 0">quote</pre></a></li>
91
+            <li><a data-edit="formatBlock h1"  title="Title - level 1"><h1 style="margin: 0">heading 1</h1></a></li>
92
+            <li><a data-edit="formatBlock h2"  title="Title - level 2"><h2 style="margin: 0">heading 2</h2></a></li>
93
+            <li><a data-edit="formatBlock h3"  title="Title - level 3"><h3 style="margin: 0">heading 3</h3></a></li>
94
+            <li><a data-edit="formatBlock h4"  title="Title - level 4"><h4 style="margin: 0">heading 4</h4></a></li>
95
+            <li><a data-edit="formatBlock h5"  title="Title - level 5"><h5 style="margin: 0">heading 5</h5></a></li>
96
+            <li><a data-edit="formatBlock h6"  title="Title - level 6"><h6 style="margin: 0">heading 6</h6></a></li>
97
+          </ul>
53
         </div>
98
         </div>
54
       % endif
99
       % endif
55
       % if psMenuOptions.find('boldanditalic')>=0:
100
       % if psMenuOptions.find('boldanditalic')>=0:
76
           <a class="btn" data-edit="justifyfull" title="Justify (Ctrl/Cmd+J)"><i class="fa fa-align-justify"></i></a>
121
           <a class="btn" data-edit="justifyfull" title="Justify (Ctrl/Cmd+J)"><i class="fa fa-align-justify"></i></a>
77
         </div>
122
         </div>
78
       % endif
123
       % endif
79
-      % if psMenuOptions.find('links')>=0:
80
-        <div class="btn-group">
81
-          <a class="btn dropdown-toggle" data-toggle="dropdown" title="Hyperlink"><i class="fa fa-link"></i></a>
82
-          <div class="dropdown-menu input-append">
83
-            <input class="span2" placeholder="URL" type="text" data-edit="createLink"/>
84
-            <button class="btn" type="button">Add</button>
85
-          </div>
86
-          <a class="btn" data-edit="unlink" title="Remove Hyperlink"><i class="fa fa-cut"></i></a>
87
-        </div>
88
-      % endif
89
-      % if psMenuOptions.find('images')>=0:
90
-        <div class="btn-group">
91
-          <a class="btn" title="Insert picture (or just drag & drop)" id="pictureBtn"><i class="fa fa-picture-o"></i></a>
92
-          <input type="file" data-role="magic-overlay" data-target="#pictureBtn" data-edit="insertImage" />
93
-        </div>
94
-      % endif
124
+#######
125
+##
126
+## LINK MENU ; NOT WORKING FOR NOW (links are auto-generated at render time)
127
+##
128
+##      % if psMenuOptions.find('links')>=0:
129
+##        <div class="btn-group">
130
+##          <a class="btn dropdown-toggle" data-toggle="dropdown" title="Hyperlink"><i class="fa fa-link"></i></a>
131
+##          <div class="dropdown-menu input-append">
132
+##            <input class="span2" placeholder="URL" type="text" data-edit="createLink"/>
133
+##            <button class="btn" type="button">Add</button>
134
+##          </div>
135
+##          <a class="btn" data-edit="unlink" title="Remove Hyperlink"><i class="fa fa-cut"></i></a>
136
+##        </div>
137
+##      % endif
138
+#######
139
+##
140
+## IMAGES MENU ; NOT WORKING FOR NOW
141
+##
142
+##      % if psMenuOptions.find('images')>=0:
143
+##        <div class="btn-group">
144
+##          <a class="btn" title="Insert picture (or just drag & drop)" id="pictureBtn"><i class="fa fa-picture-o"></i></a>
145
+##          <input type="file" data-role="magic-overlay" data-target="#pictureBtn" data-edit="insertImage" />
146
+##        </div>
147
+##      % endif
95
       % if psMenuOptions.find('undoredo')>=0:
148
       % if psMenuOptions.find('undoredo')>=0:
96
         <div class="btn-group">
149
         <div class="btn-group">
97
           <a class="btn" data-edit="undo" title="Undo (Ctrl/Cmd+Z)"><i class="fa fa-undo"></i></a>
150
           <a class="btn" data-edit="undo" title="Undo (Ctrl/Cmd+Z)"><i class="fa fa-undo"></i></a>
122
 
175
 
123
 <%def name='RichTextEditor(psRichTextEditorNodeId, psRichTextEditorContent="", psMenuOptions="styles|boldanditalic|lists|justifiers|links|images|undoredo|fullscreen")'>
176
 <%def name='RichTextEditor(psRichTextEditorNodeId, psRichTextEditorContent="", psMenuOptions="styles|boldanditalic|lists|justifiers|links|images|undoredo|fullscreen")'>
124
   <div id="${psRichTextEditorNodeId}-widget" class="rich-text-editor-widget">
177
   <div id="${psRichTextEditorNodeId}-widget" class="rich-text-editor-widget">
178
+    ${RichTextEditorToolbar(psRichTextEditorNodeId, psMenuOptions)}
125
     <div id="${psRichTextEditorNodeId}-widget-inner" class="rich-text-editor-widget-inner">
179
     <div id="${psRichTextEditorNodeId}-widget-inner" class="rich-text-editor-widget-inner">
126
       <div id="${psRichTextEditorNodeId}-alert-container"></div>
180
       <div id="${psRichTextEditorNodeId}-alert-container"></div>
127
-      ${RichTextEditorToolbar(psRichTextEditorNodeId, psMenuOptions)}
128
       <div id="${psRichTextEditorNodeId}" class="pod-rich-text-zone pod-input-like-shadow">
181
       <div id="${psRichTextEditorNodeId}" class="pod-rich-text-zone pod-input-like-shadow">
129
         ${psRichTextEditorContent|n}
182
         ${psRichTextEditorContent|n}
130
       </div>
183
       </div>

+ 134 - 0
pboard/pboard/templates/search.mak View File

1
+<%inherit file="local:templates.master"/>
2
+<%namespace name="POD" file="pboard.templates.pod"/>
3
+
4
+<%def name="title()">
5
+pod :: your dashboard
6
+</%def>
7
+
8
+<div class="row">
9
+  <div class="span6">
10
+    <form class="form-search" action="${tg.url('/search')}">
11
+      <div class="input-append">
12
+        <input name="keywords" type="text" class="span2 search-query" placeholder="Search" value="${context.get('search_string', '')}">
13
+        <button title="${_('Search again')}" class="btn" type="submit"><i class="fa fa-search"></i></button>
14
+      </div>
15
+    </form>
16
+  </div>
17
+</div>
18
+<div class="row">
19
+  <div class="span6">
20
+    <p>
21
+      <i class="pod-blue fa fa-search"></i>
22
+      ${_("Results")}
23
+      <span class="badge badge-info">${len(found_nodes)}  result(s)</span> 
24
+      % for keyword in search_string.split():
25
+        <span class="label">${keyword}</span>
26
+      % endfor
27
+    </p>
28
+  </div>
29
+</div>
30
+<div class="row">
31
+  <div class="span6">
32
+#######
33
+## SHOW RESULT FILTERING TOOLBAR
34
+    <span>Filter results:</span>
35
+    <div class="btn-group search-result-toogle-buttons" data-toggle="buttons-checkbox">
36
+      % for data_type in ('file', 'event', 'data', 'contact', 'comment'):
37
+        <button
38
+            id="search-result-toogle-button-${data_type}"
39
+            class="btn search-result-dynamic-toogle-button"
40
+            title="Show/hide ${POD.DocumentTypeLabel(data_type)} results"
41
+        >
42
+          <i class=" ${POD.IconCssClass(data_type)}"></i>
43
+          <sup style="color: #5BB75B;"><i class="fa fa-check"></i></sup>
44
+        </button>
45
+      % endfor
46
+    </div>
47
+  </div>
48
+</div>
49
+<script>
50
+
51
+$('.search-result-toogle-buttons > button.search-result-dynamic-toogle-button').addClass('active');
52
+
53
+
54
+
55
+$('#search-result-toogle-button-file').click(function () {
56
+  $('.search-result-file').toggle();
57
+  $('#search-result-toogle-button-file > sup').toggle();
58
+});
59
+$('#search-result-toogle-button-event').click(function () {
60
+  $('.search-result-event').toggle();
61
+  $('#search-result-toogle-button-event > sup').toggle();
62
+});
63
+$('#search-result-toogle-button-data').click(function () {
64
+  $('.search-result-data').toggle();
65
+  $('#search-result-toogle-button-data > sup').toggle();
66
+});
67
+$('#search-result-toogle-button-contact').click(function () {
68
+  $('.search-result-contact').toggle();
69
+  $('#search-result-toogle-button-contact > sup').toggle();
70
+});
71
+$('#search-result-toogle-button-comment').click(function () {
72
+  $('.search-result-comment').toggle();
73
+  $('#search-result-toogle-button-comment > sup').toggle();
74
+});
75
+</script>
76
+
77
+% if found_nodes==None or len(found_nodes)<=0:
78
+#######
79
+## No result view
80
+<div class="row">
81
+  <p class="alert">
82
+    <i class="fa  fa-exclamation-triangle"></i>
83
+    ${_("No data found for keywords:")} <i>${search_string}</i>
84
+  </p>
85
+</div>
86
+% else:
87
+#######
88
+## Standard result view
89
+##  <hr/>
90
+
91
+<div class="row">
92
+  <div class="span12">
93
+  % for result_id, node in enumerate(found_nodes):
94
+  
95
+    <div class="row">
96
+      <div class="span5 search-result-item search-result-${node.node_type}">
97
+
98
+        <h5 title="${node.data_label}">
99
+    % if node.node_type=='data' or node.parent_id==None:
100
+          <a href="${POD.DocumentUrl(node.node_id, search_string)}">
101
+    % else:
102
+          <a href="${POD.DocumentUrlWithAnchor(node.parent_id, search_string, 'tab-%ss'%node.node_type)}">
103
+    % endif
104
+            <i class="${node.getIconClass()}"></i>
105
+            ${node.data_label}
106
+            <span class="label ${node.getStatus().css} pull-right" title="${node.getStatus().label}">
107
+              <i class="${node.getStatus().icon}"></i>
108
+              
109
+              
110
+            <span>
111
+          </a>
112
+        </h5>
113
+## Now show the breakcrumb in a google-like manner
114
+        <div>
115
+    % for parent_node in node.getBreadCrumbNodes():
116
+          <a style="color: #468847;" href="${POD.DocumentUrl(parent_node.node_id, search_string)}" title="${parent_node.data_label}">${parent_node.getTruncatedLabel(30)}</a>
117
+          <span style="color: #468847;" >/</span>
118
+    % endfor
119
+
120
+            <a style="color: #468847;" href="${POD.DocumentUrl(node.node_id, search_string)}" title="${node.data_label}">
121
+              ${node.getTruncatedLabel(30)}
122
+            </a>
123
+        </div>
124
+        <div class="row">
125
+          <p class="span5">${node.getContentWithHighlightedKeywords(search_string.split(), node.getTruncatedContentAsText(200))|n}</p>
126
+        </div>
127
+##      <hr/>
128
+      </div>
129
+    </div>
130
+  % endfor
131
+  </div>
132
+% endif
133
+</div>
134
+