Selaa lähdekoodia

add search feature + improved document interface

Damien Accorsi 10 vuotta sitten
vanhempi
commit
050b692cda

+ 2 - 0
bin/setup.sh Näytä tiedosto

61
 echo "-> pillow"
61
 echo "-> pillow"
62
 pip install psycopg2
62
 pip install psycopg2
63
 pip install pillow
63
 pip install pillow
64
+pip install beautifulsoup4
65
+
64
 echo
66
 echo
65
 echo
67
 echo
66
 
68
 

+ 3 - 3
doc/database/pod-create-database-and-user.sh Näytä tiedosto

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 Näytä tiedosto

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

+ 2 - 6
pboard/pboard.egg-info/SOURCES.txt Näytä tiedosto

54
 pboard/public/img/turbogears_logo.png
54
 pboard/public/img/turbogears_logo.png
55
 pboard/public/img/turbogears_logo_big.png
55
 pboard/public/img/turbogears_logo_big.png
56
 pboard/public/img/under_the_hood_blue.png
56
 pboard/public/img/under_the_hood_blue.png
57
+pboard/public/javascript/pod.js
57
 pboard/public/javascript/external/bootstrap-datetimepicker.min.js
58
 pboard/public/javascript/external/bootstrap-datetimepicker.min.js
58
 pboard/public/javascript/external/bootstrap-wysiwyg.js
59
 pboard/public/javascript/external/bootstrap-wysiwyg.js
59
 pboard/public/javascript/external/bootstrap.js
60
 pboard/public/javascript/external/bootstrap.js
90
 pboard/public/javascript/external/google-code-prettify/prettify.js
91
 pboard/public/javascript/external/google-code-prettify/prettify.js
91
 pboard/public/javascript/external/google-code-prettify/run_prettify.js
92
 pboard/public/javascript/external/google-code-prettify/run_prettify.js
92
 pboard/templates/__init__.py
93
 pboard/templates/__init__.py
93
-pboard/templates/__init__.pyc
94
+pboard/templates/about.mak
94
 pboard/templates/dashboard.mak
95
 pboard/templates/dashboard.mak
95
 pboard/templates/document.mak
96
 pboard/templates/document.mak
96
-pboard/templates/document.mak~
97
 pboard/templates/error.mak
97
 pboard/templates/error.mak
98
 pboard/templates/index.mak
98
 pboard/templates/index.mak
99
-pboard/templates/index.mak~
100
 pboard/templates/login.mak
99
 pboard/templates/login.mak
101
-pboard/templates/master.html
102
 pboard/templates/master.mak
100
 pboard/templates/master.mak
103
 pboard/templates/pod.mak
101
 pboard/templates/pod.mak
104
-pboard/templates/pod.mak~
105
 pboard/templates/simple_mako.mak
102
 pboard/templates/simple_mako.mak
106
 pboard/templates/debug/__init__.py
103
 pboard/templates/debug/__init__.py
107
-pboard/templates/debug/__init__.pyc
108
 pboard/templates/debug/environ.mak
104
 pboard/templates/debug/environ.mak
109
 pboard/templates/debug/iconset.mak
105
 pboard/templates/debug/iconset.mak
110
 pboard/templates/debug/identity.mak
106
 pboard/templates/debug/identity.mak

+ 28 - 11
pboard/pboard/controllers/root.py Näytä tiedosto

106
     @expose('pboard.templates.dashboard')
106
     @expose('pboard.templates.dashboard')
107
     @require(predicates.in_group('user', msg=l_('Please login to access this page')))
107
     @require(predicates.in_group('user', msg=l_('Please login to access this page')))
108
     def dashboard(self):
108
     def dashboard(self):
109
-      loCurrentUser   = pld.PODStaticController.getCurrentUser()
110
-      loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
109
+        loCurrentUser   = pld.PODStaticController.getCurrentUser()
110
+        loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
111
 
111
 
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)
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)
116
 
116
 
117
 
117
 
118
     @expose('pboard.templates.document')
118
     @expose('pboard.templates.document')
119
     @require(predicates.in_group('user', msg=l_('Please login to access this page')))
119
     @require(predicates.in_group('user', msg=l_('Please login to access this page')))
120
-    def document(self, node=0, came_from=lurl('/')):
120
+    def document(self, node=0, came_from=lurl('/'), highlight=''):
121
         """show the user dashboard"""
121
         """show the user dashboard"""
122
         loCurrentUser   = pld.PODStaticController.getCurrentUser()
122
         loCurrentUser   = pld.PODStaticController.getCurrentUser()
123
         loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
123
         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()
125
         # 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())
126
         loRootNodeList = loApiController.buildTreeListForMenu(pbmd.PBNodeStatus.getVisibleIdsList())
127
         liNodeId         = int(node)
127
         liNodeId         = int(node)
128
-        
128
+
129
         loCurrentNode    = None
129
         loCurrentNode    = None
130
         loNodeStatusList = None
130
         loNodeStatusList = None
131
         try:
131
         try:
133
           loCurrentNode    = loApiController.getNode(liNodeId)
133
           loCurrentNode    = loApiController.getNode(liNodeId)
134
         except Exception as e:
134
         except Exception as e:
135
           flash(_('Document not found'), 'error')
135
           flash(_('Document not found'), 'error')
136
-        
136
+
137
         # FIXME - D.A - 2013-11-07 - Currently, the code build a new item if no item found for current user
137
         # 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
138
         # the correct behavior should be to redirect to setup page
139
         if loCurrentNode is not None and "%s"%loCurrentNode.node_id!=node:
139
         if loCurrentNode is not None and "%s"%loCurrentNode.node_id!=node:
140
           redirect(tg.url('/document/%i'%loCurrentNode.node_id))
140
           redirect(tg.url('/document/%i'%loCurrentNode.node_id))
141
-          
141
+
142
         if loCurrentNode is None:
142
         if loCurrentNode is None:
143
           loCurrentNode = loApiController.getNode(0) # try to get an item
143
           loCurrentNode = loApiController.getNode(0) # try to get an item
144
           if loCurrentNode is not None:
144
           if loCurrentNode is not None:
150
             pm.DBSession.flush()
150
             pm.DBSession.flush()
151
             redirect(tg.url('/document/%i'%loCurrentNode.node_id))
151
             redirect(tg.url('/document/%i'%loCurrentNode.node_id))
152
 
152
 
153
-        return dict(root_node_list=loRootNodeList, current_node=loCurrentNode, node_status_list = loNodeStatusList)
153
+        return dict(
154
+            root_node_list=loRootNodeList,
155
+            current_node=loCurrentNode,
156
+            node_status_list = loNodeStatusList,
157
+            keywords = highlight
158
+        )
159
+
160
+    @expose('pboard.templates.search')
161
+    @require(predicates.in_group('user', msg=l_('Please login to access this page')))
162
+    def search(self, keywords=''):
163
+        loCurrentUser   = pld.PODStaticController.getCurrentUser()
164
+        loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
165
+
166
+        loFoundNodes = loApiController.searchNodesByText(keywords.split())
167
+
168
+        return dict(search_string=keywords, found_nodes=loFoundNodes)
169
+
170
+
154
 
171
 

+ 23 - 0
pboard/pboard/lib/dbapi.py Näytä tiedosto

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 Näytä tiedosto

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 Näytä tiedosto

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 Näytä tiedosto

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();

+ 243 - 109
pboard/pboard/templates/document.mak Näytä tiedosto

7
 
7
 
8
 <%def name="node_treeview_for_set_parent_menu(node_id, node_list, indentation=-1)">
8
 <%def name="node_treeview_for_set_parent_menu(node_id, node_list, indentation=-1)">
9
   % if 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>
10
+    <li>
11
+      <a href="${tg.url('/api/set_parent_node?node_id=%i&new_parent_id=0'%(current_node.node_id))}">
12
+        <i class="fa fa-file-text-o"></i> ${_('Home')}
13
+      </a>
11
       ${node_treeview_for_set_parent_menu(node_id, node_list, 0)}
14
       ${node_treeview_for_set_parent_menu(node_id, node_list, 0)}
12
     </li>
15
     </li>
13
   % else:
16
   % else:
14
     % if len(node_list)>0:
17
     % if len(node_list)>0:
15
-      <ul>
18
+      <ul style="list-style: none;">
16
       % for new_parent_node in node_list:
19
       % for new_parent_node in node_list:
17
         <li>
20
         <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>
21
+          <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)}
22
+          </a>
19
           ${node_treeview_for_set_parent_menu(node_id, new_parent_node.getStaticChildList(), indentation+1)}
23
           ${node_treeview_for_set_parent_menu(node_id, new_parent_node.getStaticChildList(), indentation+1)}
20
         </li>
24
         </li>
21
       % endfor
25
       % endfor
82
     % endif
86
     % endif
83
 </%def>
87
 </%def>
84
 
88
 
89
+#######
90
+##
91
+## HERE COMES THE BREADCRUMB
92
+##
93
+  <div class="row">
94
+<ul class="breadcrumb span12">
95
+  <li><span class="divider"> / Documents /</span></li>
96
+  % for breadcrumb_node in current_node.getBreadCrumbNodes():
97
+    <li><a href="${tg.url('/document/%s'%(breadcrumb_node.node_id))}">${breadcrumb_node.getTruncatedLabel(30)}</a> <span class="divider">/</span></li>
98
+  % endfor
99
+  <li class="active">${current_node.data_label}</li>
100
+</ul>
101
+  </div>
102
+
85
   <div class="row">
103
   <div class="row">
86
     <div id='application-left-panel' class="span3">
104
     <div id='application-left-panel' class="span3">
87
       <div>
105
       <div>
89
       </div>
107
       </div>
90
     </div>
108
     </div>
91
     <div id='application-main-panel' class="span9">
109
     <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
 
110
 
129
       <div class="row">
111
       <div class="row">
130
         <div id='application-document-panel' class="span5">
112
         <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}
113
+          <div id='current-document-content' class="">
114
+######
115
+##
116
+## CURRENT DOCUMENT TOOLBAR - START
117
+##
118
+            <div id="current-document-toobar">
119
+              <div class="btn-group">
120
+          % if current_node.parent_id!=None and current_node.parent_id!=0:
121
+                ${POD.EditButton('current-document-content-edit-button', True)}
122
+          % endif
123
+      ##        </div>
124
+      ##        <div class="btn-group">
125
+                <button class="btn btn-small"  data-toggle="dropdown" href="#"> 
126
+                  <i class="fa  fa-signal"></i>
127
+                  ${_("Change status")}
128
+                </button>
129
+                <a class="btn btn-small dropdown-toggle" data-toggle="dropdown" href="#">
130
+                  <span class="caret"></span>
131
+                </a>
132
+                <ul class="dropdown-menu">
133
+                % for node_status in node_status_list:
134
+                  % if node_status.status_id==current_node.getStatus().status_id:
135
+                  <li title="${h.getExplanationAboutStatus(node_status.status_id, current_node.getStatus().status_id)}">
136
+                    <a class="${node_status.css}" href="#"  style="color: #999;">
137
+                      <i class="${node_status.icon_id}"></i> ${node_status.label}
138
+                    </a>
139
+                  </li>
140
+                  % else:
141
+                  <li title="${h.getExplanationAboutStatus(node_status.status_id, current_node.getStatus().status_id)}">
142
+                    <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))}">
143
+                      <i class="${node_status.icon_id}"></i> ${node_status.label}
144
+                    </a>
145
+                  </li>
146
+                  % endif
147
+                % endfor
148
+                </ul>
149
+              </div>
150
+              <div class="btn-group">
151
+                <button class="btn btn-small btn-success"  data-toggle="dropdown" href="#">
152
+                  <i class="fa fa-plus"></i> ${_('Add')}
153
+                </button>
154
+                <a class="btn btn-small dropdown-toggle" data-toggle="dropdown" href="#"><span class="caret"></span></a>
155
+                <ul class="dropdown-menu">
156
+                
157
+                  <li>
158
+                    <div class="btn-success strong" ><strong><i class="fa fa-magic"></i> Add New...</strong><br/></div>
159
+                    <div class="pod-grey"><i>create a totally new item...</i></div>
160
+                  </li>
161
+
162
+                  <li><a><i class="fa fa-file-text-o"></i> Document</a></li>
163
+                  <li><a><i class="fa fa-paperclip"></i> File</a></li>
164
+                  <li><a><i class="fa fa-calendar"></i> Event</a></li>
165
+                  <li><a><i class="fa fa-user"></i> Contact</a></li>
166
+                  <li><a><i class="fa fa-comments-o"></i> Comment</a></li>
167
+
168
+                  <li class="divider" role="presentation"></li>
169
+
170
+                  <li>
171
+                    <div class="btn-warning strong" ><strong><i class="fa fa-link"></i> Add Existing...</strong><br/></div>
172
+                    <div class="pod-grey"><i>link to an existing item...</i></div>
173
+                  </li>
174
+                  <li><a><i class="fa fa-file-text-o"></i> Document</a></li>
175
+                  <li><a><i class="fa fa-paperclip"></i> File</a></li>
176
+                  <li><a><i class="fa fa-calendar"></i> Event</a></li>
177
+                  <li><a><i class="fa fa-user"></i> Contact</a></li>
178
+                  <li><a><i class="fa fa-comments-o"></i> Comment</a></li>
179
+
180
+                </ul>
181
+              </div>
182
+              <div class="btn-group ">
183
+                <a
184
+                  class="btn btn-small btn-warning"
185
+                  href="#"
186
+                  data-toggle="dropdown"
187
+                  title="${_('Move to')}"
188
+                  ><i class="fa fa-arrows"></i></a>
189
+                <ul class="dropdown-menu">
190
+                  <li >
191
+                    <div class="btn-warning strong" ><strong><i class="fa fa-magic"></i> ${_("Move the document...")}</strong><br/></div>
192
+                    <div class="pod-grey"><i>move the document to...</i></div>
193
+                  </li>
194
+                  ${node_treeview_for_set_parent_menu(current_node.node_id, root_node_list)}
195
+                </ul>
196
+                <a
197
+                  class="btn btn-small btn-danger"
198
+                  href='${tg.url('/api/edit_status?node_id=%i&node_status=%s'%(current_node.node_id, 'deleted'))}'
199
+                  id='current-document-force-delete-button' onclick="return confirm('${_('Delete current document?')}');"
200
+                  title="${_('Delete')}"
201
+                  ><i class="fa fa-trash-o"></i></a>
202
+              </div>
203
+            </div>
204
+##
205
+## CURRENT DOCUMENT TOOLBAR - END
206
+##
207
+######
208
+
209
+######
210
+##
211
+## CURRENT DOCUMENT CONTENT - START
212
+##
213
+            <h3 id="current-document-title">#${current_node.node_id} - ${current_node.data_label}
214
+              <span class="label ${current_node.getStatus().css}" href="#">${current_node.getStatus().label}</a>
215
+            </h3>
216
+            % if len(keywords)>0 and keywords!='':
217
+                ${current_node.getContentWithHighlightedKeywords(keywords.split(), current_node.getContentWithTags())|n}
218
+            % else:
219
+                ${current_node.getContentWithTags()|n}
220
+            % endif
221
+          </div>
222
+          <form style='display: none;' id="current-document-content-edit-form" method='post' action='${tg.url('/api/edit_label_and_content')}'>
223
+            <div>
224
+              ${POD.CancelButton('current-document-content-edit-cancel-button-top', True)}
225
+              ${POD.SaveButton('current-document-content-edit-save-button-top', True)}
138
             </div>
226
             </div>
139
-            <form style='display: none;' id="current-document-content-edit-form" method='post' action='${tg.url('/api/edit_label_and_content')}'>
227
+            <div style="padding: 0.5em 0 0 0">
140
               <input type='hidden' name='node_id' value='${current_node.node_id}'/>
228
               <input type='hidden' name='node_id' value='${current_node.node_id}'/>
141
               <input type="hidden" name='data_content' id="current_node_textarea" />
229
               <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" />
230
               <input type='text' name='data_label' value='${current_node.data_label}' class="span4" placeholder="document title" />
231
+            </div>
232
+            <div>
143
               ${POD.RichTextEditor('current_node_textarea_wysiwyg', current_node.data_content)}
233
               ${POD.RichTextEditor('current_node_textarea_wysiwyg', current_node.data_content)}
234
+            </div>
235
+            <div class="pull-right">
144
               ${POD.CancelButton('current-document-content-edit-cancel-button', True)}
236
               ${POD.CancelButton('current-document-content-edit-cancel-button', True)}
145
               ${POD.SaveButton('current-document-content-edit-save-button', True)}
237
               ${POD.SaveButton('current-document-content-edit-save-button', True)}
146
-
147
-
148
-            </form>
149
-          </p>
238
+            </div>
239
+          </form>
150
         </div>
240
         </div>
151
         ## FIXME - D.A - 2013-11-07 - The following div should be span4 instead of span3 but some bug make it impossible
241
         ## 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">
242
         <div id='application-metadata-panel' class="span4">
153
           <div class="tabbable">
243
           <div class="tabbable">
154
             <ul class="nav nav-tabs">
244
             <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>
245
+                <li><a href="#subdocuments" data-toggle="tab" title="${_('Subdocuments')}"><i class='pod-dark-grey fa fa-file-text-o'></i>
246
+                
247
+                ${POD.ItemNb(current_node.getChildren())}</a></li>
248
+                
249
+                <li class="active"><a href="#events" data-toggle="tab" title="${_('Calendar')}"><i class="pod-dark-grey fa fa-calendar"></i>${POD.ItemNb(current_node.getEvents())}</a></li>
250
+                <li><a href="#contacts" data-toggle="tab" title="${_('Address book')}"><i class="pod-dark-grey fa fa-user"></i>${POD.ItemNb(current_node.getContacts())}</a></li>
251
+                <li><a href="#comments" data-toggle="tab" title="${_('Comment thread')}"><i class="pod-dark-grey fa fa-comments-o"></i>${POD.ItemNb(current_node.getComments())}</a></li>
252
+                <li><a href="#files" data-toggle="tab" title="${_('Attachments')}"><i class="pod-dark-grey fa  fa-paperclip"></i>${POD.ItemNb(current_node.getFiles())}</a></li>
253
+                
254
+                <li class="pull-right"><a href="#accessmanagement" data-toggle="tab" title="${_('Access Management')}"><i class="pod-dark-grey fa fa-key"></i>${POD.ItemNb(current_node.getFiles())}</a></li>
255
+                 
160
             </ul>
256
             </ul>
161
             <div class="tab-content">
257
             <div class="tab-content">
162
                 ################################
258
                 ################################
163
                 ##
259
                 ##
164
-                ## PANEL SHOWING LIST OF TAGS
260
+                ## PANEL SHOWING LIST OF SUB DOCUMENTS
165
                 ##
261
                 ##
166
                 ################################
262
                 ################################
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)}
263
+                <!-- DEBUG - D.A. - 2013-11-07 - Not using tags for th moment -->
264
+                <div class="tab-pane" id="subdocuments">
265
+                % if len(current_node.getChildren())<=0:
266
+                  <p class="pod-grey">
267
+                    ${_("There is currently no child documents.")}<br/>
268
+                  </p>
269
+                  <p>
270
+                    <a class="btn btn-success btn-small" href="${tg.url('/api/create_document?parent_id=%i'%current_node.node_id)}">
271
+                      <i class="fa fa-plus"></i> ${_("Add one")}
272
+                    </a>
273
+                  </p>
274
+                % else:
275
+                  <p>
276
+                    <a class="btn btn-success btn-small" href="${tg.url('/api/create_document?parent_id=%i'%current_node.node_id)}">
277
+                      <i class="fa fa-plus"></i> ${_("Add new document")}
278
+                    </a>
279
+                  </p>
280
+
281
+                  <div>
282
+                    % for subnode in current_node.getChildren():
283
+                      <p style="list-style-type:none;">
284
+                        <i class="fa-fw ${subnode.getIconClass()}"></i>
285
+                          <a href="${tg.url('/document/%i'%subnode.node_id)}">
286
+                            ${subnode.data_label}
287
+                          </a>
288
+                      </p>
182
                     % endfor
289
                     % endfor
183
                   </div>
290
                   </div>
291
+                % endif
184
                 </div>
292
                 </div>
185
-                -->
293
+                
186
                 ################################
294
                 ################################
187
                 ##
295
                 ##
188
                 ## PANEL SHOWING LIST OF EVENTS
296
                 ## PANEL SHOWING LIST OF EVENTS
189
                 ##
297
                 ##
190
                 ################################
298
                 ################################
191
                 <div class="tab-pane active" id="events">
299
                 <div class="tab-pane active" id="events">
192
-                  ${POD.AddButton('current-document-add-event-button', True, _(' Add event'))}
300
+                % if len(current_node.getEvents())<=0:
301
+                  <p class="pod-grey">${_("The calendar is empty.")}<br/></p>
302
+                  <p>${POD.AddButton('current-document-add-event-button', True, _(' Add first event'))}</p>
303
+                % else:
304
+                  <p>${POD.AddButton('current-document-add-event-button', True, _(' Add an event'))}</p>
305
+                % endif
306
+                
193
                   <form style='display: none;' id='current-document-add-event-form' action='${tg.url('/api/create_event')}' method='post' class="well">
307
                   <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}'/>
308
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
195
                     <fieldset>
309
                     <fieldset>
225
                     </fieldset>
339
                     </fieldset>
226
                   </form>
340
                   </form>
227
 
341
 
228
-                % if len(current_node.getEvents())<=0:
229
-                  <p><i>${_('No history for the moment.')}</i></p>
230
-                % else:
342
+                % if len(current_node.getEvents())>0:
231
                   <table class="table table-striped table-hover table-condensed">
343
                   <table class="table table-striped table-hover table-condensed">
232
                     <thead>
344
                     <thead>
233
                       <tr>
345
                       <tr>
261
                 ##
373
                 ##
262
                 ##############################
374
                 ##############################
263
                 <div class="tab-pane" id="contacts">
375
                 <div class="tab-pane" id="contacts">
264
-                
376
+                % if len(current_node.getContacts())<=0:
377
+                  <p class="pod-grey">${_("The address book is empty.")}<br/></p>
378
+                  <p>${POD.AddButton('current-document-add-contact-button', True, _(' Add first contact'), True)}</p>
379
+                % else:
380
+                  <p>${POD.AddButton('current-document-add-contact-button', True, _(' Add a contact'))}</p>
381
+                % endif
382
+
265
                   <!-- ADD CONTACT FORM -->
383
                   <!-- 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">
384
                   <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}'/>
385
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
269
                     <fieldset>
386
                     <fieldset>
306
                 ##
423
                 ##
307
                 ################################
424
                 ################################
308
                 <div class="tab-pane" id="comments">
425
                 <div class="tab-pane" id="comments">
426
+                % if len(current_node.getComments())<=0:
427
+                  <p class="pod-grey">${_("The comment thread is empty.")}<br/></p>
428
+                  <p>${POD.AddButton('current-document-add-comment-button', True, _('Add first comment'), True)}</p>
429
+                % else:
430
+                  <p>${POD.AddButton('current-document-add-comment-button', True, _('Add a comment'))}</p>
431
+                % endif
432
+
309
                   <!-- ADD COMMENT FORM -->
433
                   <!-- 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">
434
                   <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}'/>
435
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
313
                     <fieldset>
436
                     <fieldset>
327
                   </form>
450
                   </form>
328
 
451
 
329
                   <!-- LIST OF COMMENTS -->
452
                   <!-- LIST OF COMMENTS -->
330
-                % if len(current_node.getComments())<=0:
331
-                  <p><i>${_('No comments.')}</i></p>
332
-                % else:
453
+                % if len(current_node.getComments())>0:
333
                   <table class="table table-striped table-hover table-condensed">
454
                   <table class="table table-striped table-hover table-condensed">
334
                     % for comment in current_node.getComments():
455
                     % for comment in current_node.getComments():
335
                       <tr title="Last updated: ${comment.updated_at}">
456
                       <tr title="Last updated: ${comment.updated_at}">
354
                 ##
475
                 ##
355
                 ################################
476
                 ################################
356
                 <div class="tab-pane" id="files">
477
                 <div class="tab-pane" id="files">
478
+                % if len(current_node.getFiles())<=0:
479
+                  <p class="pod-grey">${_("There is currently no attachment.")}<br/></p>
480
+                  <p>${POD.AddButton('current-document-add-file-button', True, _(' Attach first file'))}</p>
481
+                % else:
482
+                  <p>${POD.AddButton('current-document-add-file-button', True, _(' Attach a file'))}</p>
483
+                % endif
484
+
357
                   <!-- ADD FILE FORM -->
485
                   <!-- 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">
486
                   <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}'/>
487
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
361
                     <fieldset>
488
                     <fieldset>
378
                   </form>
505
                   </form>
379
 
506
 
380
                   <!-- LIST OF FILES -->
507
                   <!-- 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">
508
+                  <div>
509
+                % if len(current_node.getFiles())>0:
385
                     % for current_file in current_node.getFiles():
510
                     % 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>
511
+                      <p style="list-style-type:none; margin-bottom: 0.5em;">
512
+                        <i class="fa fa-paperclip"></i>
513
+                        <a
514
+                          href="${tg.url('/document/%i'%current_file.node_id)}"
515
+                          title="${_('View the attachment')}: ${current_file.data_label}"
516
+                        >
517
+                          ${current_file.getTruncatedLabel(50)}
518
+                        </a>
519
+                        <a
520
+                          class="pull-right"
521
+                          href="${tg.url('/api/get_file_content/%s'%(current_file.node_id))}"
522
+                          title="${_('View the attachment')}"
523
+                        >
524
+                          <i class="fa fa-download"></i>
525
+                        </a>
526
+                      </p>
406
                     % endfor
527
                     % endfor
407
-                  </table>
408
                 % endif
528
                 % endif
529
+                  </div>
409
                 </div>
530
                 </div>
531
+                
532
+                
533
+                ################################
534
+                ##
535
+                ## PANEL SHOWING ACCESS MANAGEMENT
536
+                ##
537
+                ################################
538
+                <div class="tab-pane" id="accessmanagement">
539
+                  blabla
540
+                </div>
541
+                
542
+                
543
+                
410
               </div>
544
               </div>
411
             </div>
545
             </div>
412
           </div>
546
           </div>

+ 6 - 6
pboard/pboard/templates/master.mak Näytä tiedosto

212
               
212
               
213
             % endif
213
             % endif
214
           </ul>
214
           </ul>
215
+          <form class="navbar-search pull-right form-search" action="${tg.url('/search')}">
216
+            <div class="input-append">
217
+              <input name="keywords" type="text" class="span2 search-query" placeholder="Search" value="${context.get('search_string', '')}">
218
+              <button title="${_('Search')}" class="btn" type="submit"><i class="fa fa-search"></i></button>
219
+            </div>
220
+          </form>
215
 
221
 
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 -->
222
         </div><!-- /.nav-collapse -->
223
       </div><!-- /.container -->
223
       </div><!-- /.container -->
224
     </div><!-- /.navbar-inner -->
224
     </div><!-- /.navbar-inner -->

+ 83 - 30
pboard/pboard/templates/pod.mak Näytä tiedosto

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 Näytä tiedosto

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
+