Browse Source

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

sferot 11 years ago
parent
commit
bf99066079

+ 2 - 2
.gitignore View File

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

+ 16 - 8
bin/setup.sh View File

@@ -45,23 +45,31 @@ echo
45 45
 echo
46 46
 echo
47 47
 echo "-------------------------"
48
-echo "- setup project"
48
+echo "- install dependencies"
49 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 60
 echo
53 61
 echo
54 62
 
55 63
 echo
56 64
 echo
57 65
 echo "-------------------------"
58
-echo "- install dependencies"
66
+echo "- setup project"
59 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 70
 echo
65 71
 echo
66 72
 
73
+
74
+
67 75
 cd ${OLD_PATH}

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

@@ -1,7 +1,7 @@
1 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 6
 # DB_HOST='127.0.0.1'
7 7
 # DB_PORT='5432'

+ 1 - 1
pboard/development.ini View File

@@ -60,7 +60,7 @@ beaker.session.validate_key = 3283411b-1904-4554-b0e1-883863b53080
60 60
 # in development
61 61
 
62 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 64
 #echo shouldn't be used together with the logging module.
65 65
 sqlalchemy.echo = false
66 66
 sqlalchemy.echo_pool = false

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

@@ -1,23 +1,18 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 """Main Controller"""
3
+import pboard
3 4
 
4 5
 import tg
5 6
 from tg import expose, flash, require, url, lurl, request, redirect, tmpl_context
6 7
 from tg.i18n import ugettext as _, lazy_ugettext as l_
7 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 13
 from pboard.lib.base import BaseController
17 14
 from pboard.controllers.error import ErrorController
18 15
 
19
-import pboard.model as pbm
20
-import pboard.controllers as pbc
21 16
 from pboard.lib import dbapi as pld
22 17
 from pboard.controllers import api as pbca
23 18
 from pboard.controllers import debug as pbcd
@@ -43,10 +38,12 @@ class RootController(BaseController):
43 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 48
     api   = pbca.PODApiController()
52 49
     debug = pbcd.DebugController()
@@ -106,18 +103,18 @@ class RootController(BaseController):
106 103
     @expose('pboard.templates.dashboard')
107 104
     @require(predicates.in_group('user', msg=l_('Please login to access this page')))
108 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 115
     @expose('pboard.templates.document')
119 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 118
         """show the user dashboard"""
122 119
         loCurrentUser   = pld.PODStaticController.getCurrentUser()
123 120
         loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
@@ -125,7 +122,7 @@ class RootController(BaseController):
125 122
         # loRootNodeList   = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.parent_id==None).order_by(pbmd.PBNode.node_order).all()
126 123
         loRootNodeList = loApiController.buildTreeListForMenu(pbmd.PBNodeStatus.getVisibleIdsList())
127 124
         liNodeId         = int(node)
128
-        
125
+
129 126
         loCurrentNode    = None
130 127
         loNodeStatusList = None
131 128
         try:
@@ -133,12 +130,12 @@ class RootController(BaseController):
133 130
           loCurrentNode    = loApiController.getNode(liNodeId)
134 131
         except Exception as e:
135 132
           flash(_('Document not found'), 'error')
136
-        
133
+
137 134
         # FIXME - D.A - 2013-11-07 - Currently, the code build a new item if no item found for current user
138 135
         # the correct behavior should be to redirect to setup page
139 136
         if loCurrentNode is not None and "%s"%loCurrentNode.node_id!=node:
140 137
           redirect(tg.url('/document/%i'%loCurrentNode.node_id))
141
-          
138
+
142 139
         if loCurrentNode is None:
143 140
           loCurrentNode = loApiController.getNode(0) # try to get an item
144 141
           if loCurrentNode is not None:
@@ -150,5 +147,22 @@ class RootController(BaseController):
150 147
             pm.DBSession.flush()
151 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,6 +9,7 @@ from sqlalchemy import Table, ForeignKey, Column
9 9
 from sqlalchemy.types import Unicode, Integer, DateTime, Text
10 10
 from sqlalchemy.orm import relation, synonym
11 11
 from sqlalchemy.orm import joinedload_all
12
+import sqlalchemy as sqla
12 13
 
13 14
 from pboard.model import DeclarativeBase, metadata, DBSession
14 15
 from pboard.model import data as pbmd
@@ -90,6 +91,28 @@ class PODUserFilteredApiController(object):
90 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 116
   def getNodesByStatus(self, psNodeStatus, piMaxNodeNb=5):
94 117
     liOwnerIdList = self._getUserIdListForFiltering()
95 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,6 +6,7 @@ import datetime as datetimeroot
6 6
 from datetime import datetime
7 7
 from hashlib import sha256
8 8
 
9
+import bs4
9 10
 from sqlalchemy import Table, ForeignKey, Column, Sequence
10 11
 from sqlalchemy.types import Unicode, Integer, DateTime, Text, LargeBinary
11 12
 from sqlalchemy.orm import relation, synonym, relationship
@@ -263,21 +264,44 @@ class PBNode(DeclarativeBase):
263 264
     return self.getChildrenOfType([PBNodeType.Comment], PBNode.getSortingKeyBasedOnDataDatetime, True)
264 265
 
265 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 293
     laIconClass = dict()
267 294
     laIconClass['node']   = 'fa fa-folder-open'
268 295
     laIconClass['folder'] = 'fa fa-folder-open'
269 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 299
     laIconClass['event']  = 'fa fa-calendar'
273 300
     laIconClass['contact'] = 'fa fa-user'
274 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 305
   def getUserFriendlyNodeType(self):
282 306
     laNodeTypesLng = dict()
283 307
     laNodeTypesLng['node']   = 'Document' # FIXME - D.A. - 2013-11-14 - Make text translatable
@@ -304,7 +328,7 @@ class PBNode(DeclarativeBase):
304 328
   def getFormattedTime(self, poDateTime, psDateTimeFormat = '%H:%M'):
305 329
     return poDateTime.strftime(psDateTimeFormat)
306 330
 
307
-  def getStatus(self):
331
+  def getStatus(self) -> PBNodeStatusItem:
308 332
     loStatus = PBNodeStatus.getStatusItem(self.node_status)
309 333
     if loStatus.status_id!='automatic':
310 334
       return loStatus
@@ -328,6 +352,17 @@ class PBNode(DeclarativeBase):
328 352
       lsTruncatedLabel = self.data_label
329 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 366
   def getTagList(self):
332 367
     loPattern = re.compile('(^|\s|@)@(\w+)')
333 368
     loResults = re.findall(loPattern, self.data_content)

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

@@ -122,8 +122,10 @@ body { padding-top: 60px; }
122 122
   position:fixed;
123 123
   left:0;
124 124
   top:0;
125
-  z-index:0 !important;
125
+  z-index:2001 !important;
126 126
   background-color:white;
127
+
128
+  padding: 0.5em 0.5em 0.5em 0.5em;
127 129
   
128 130
   filter: alpha(opacity=90); /* internet explorer */
129 131
   -khtml-opacity: 0.9;      /* khtml, old safari */
@@ -132,9 +134,9 @@ body { padding-top: 60px; }
132 134
 }
133 135
 
134 136
 .full-size-overlay-inner {
135
-  margin: 3.5em 0.5em 0.5em 0.5em;
137
+  margin: 0.5em 1em 0.5em 0em;
136 138
   overflow: auto;
137
-  max-height: 85%;
139
+  max-height: 90%;
138 140
 }
139 141
 
140 142
 
@@ -181,3 +183,10 @@ tr:Hover td div.pod-toolbar {
181 183
 }
182 184
 
183 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,6 +6,7 @@
6 6
       $('.pod-toggle-full-screen-button > i').removeClass('fa-compress')
7 7
       $('.pod-toggle-full-screen-button > i').addClass('fa-expand')
8 8
     } else {
9
+      // Toggle from normal to fullscreen
9 10
       $(outerWidgetId).addClass('full-size-overlay');
10 11
       $(innerWidgetId).addClass('full-size-overlay-inner');
11 12
       $('.pod-toggle-full-screen-button > i').removeClass('fa-expand')
@@ -94,16 +95,18 @@
94 95
     $("#current-document-content-edit-button" ).click(function() {
95 96
       $("#current-document-content" ).css("display", "none");
96 97
       $("#current-document-content-edit-form" ).css("display", "block");
98
+      $("#current-document-toobar").css("display", "none");
97 99
     });
98 100
     $("#current-document-content" ).dblclick(function() {
99 101
       $("#current-document-content" ).css("display", "none");
100 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 105
       $("#current-document-content" ).css("display", "block");
104 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 110
       // We don't want this to act as a link so cancel the link action
108 111
       e.preventDefault();
109 112
       $('#current_node_textarea_wysiwyg').cleanHtml();

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

@@ -0,0 +1,194 @@
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,29 +1,11 @@
1 1
 <%inherit file="local:templates.master"/>
2 2
 <%namespace name="POD" file="pboard.templates.pod"/>
3
+<%namespace name="DOC" file="pboard.templates.document-widgets"/>
3 4
 
4 5
 <%def name="title()">
5 6
 pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id} / ${current_node.getStatus().label}]
6 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 9
 <%def name="node_treeview(node_list, indentation=-1)">
28 10
   % if indentation==-1:
29 11
     <div id='pod-menu-item-0' class="pod-toolbar-parent" style="padding-left: 0.5em; position: relative;">
@@ -82,6 +64,14 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
82 64
     % endif
83 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 75
   <div class="row">
86 76
     <div id='application-left-panel' class="span3">
87 77
       <div>
@@ -89,107 +79,85 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
89 79
       </div>
90 80
     </div>
91 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 83
       <div class="row">
130 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 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 98
         <div id='application-metadata-panel' class="span4">
153 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 107
             </ul>
161 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 142
                     % endfor
183 143
                   </div>
144
+                % endif
184 145
                 </div>
185
-                -->
146
+                
186 147
                 ################################
187 148
                 ##
188 149
                 ## PANEL SHOWING LIST OF EVENTS
189 150
                 ##
190 151
                 ################################
191 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 161
                   <form style='display: none;' id='current-document-add-event-form' action='${tg.url('/api/create_event')}' method='post' class="well">
194 162
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
195 163
                     <fieldset>
@@ -225,9 +193,7 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
225 193
                     </fieldset>
226 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 197
                   <table class="table table-striped table-hover table-condensed">
232 198
                     <thead>
233 199
                       <tr>
@@ -261,9 +227,15 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
261 227
                 ##
262 228
                 ##############################
263 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 238
                   <!-- ADD CONTACT FORM -->
266
-                  ${POD.AddButton('current-document-add-contact-button', True, _(' Add contact'))}
267 239
                   <form style='display: none;' id='current-document-add-contact-form' action='${tg.url('/api/create_contact')}' method='post' class="well">
268 240
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
269 241
                     <fieldset>
@@ -297,8 +269,6 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
297 269
                       </div>
298 270
                     </div>
299 271
                   % endfor
300
-
301
-
302 272
                 </div>
303 273
                 ################################
304 274
                 ##
@@ -306,8 +276,15 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
306 276
                 ##
307 277
                 ################################
308 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 287
                   <!-- ADD COMMENT FORM -->
310
-                  ${POD.AddButton('current-document-add-comment-button', True, _(' Add comment'))}
311 288
                   <form style='display: none;' id='current-document-add-comment-form' action='${tg.url('/api/create_comment')}' method='post' class="well">
312 289
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
313 290
                     <fieldset>
@@ -327,9 +304,7 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
327 304
                   </form>
328 305
 
329 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 308
                   <table class="table table-striped table-hover table-condensed">
334 309
                     % for comment in current_node.getComments():
335 310
                       <tr title="Last updated: ${comment.updated_at}">
@@ -354,8 +329,15 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
354 329
                 ##
355 330
                 ################################
356 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 340
                   <!-- ADD FILE FORM -->
358
-                  ${POD.AddButton('current-document-add-file-button', True, _(' Add file'))}
359 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 342
                     <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
361 343
                     <fieldset>
@@ -378,34 +360,38 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
378 360
                   </form>
379 361
 
380 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 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 382
                     % endfor
407
-                  </table>
408 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 395
                 </div>
410 396
               </div>
411 397
             </div>

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

@@ -168,7 +168,15 @@
168 168
                 <li><a href="${tg.url('/debug/identity')}"><i class="fa fa-user-md"></i>  request.identity</a></li>
169 169
               </ul>
170 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 182
           % endif
@@ -209,16 +217,9 @@
209 217
                     <p></p>
210 218
                  </ul>
211 219
               </li>
212
-              
213 220
             % endif
214 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 223
         </div><!-- /.nav-collapse -->
223 224
       </div><!-- /.container -->
224 225
     </div><!-- /.navbar-inner -->

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

@@ -1,18 +1,55 @@
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 34
 <%def name="Button(piId, pbWithLabel, psButtonCssClass, psButtonTitle, psButtonIcon, psButtonLabel)" >
2 35
   <button id='${piId}' type="button" class="${psButtonCssClass}" title="${psButtonTitle}"><i class="${psButtonIcon}"></i>${'' if (pbWithLabel==False) else ' %s'%(psButtonLabel)}</button>
3 36
 </%def>
4 37
 
5 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 40
 </%def>
8 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 43
 </%def>
11 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 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 53
 </%def>
17 54
 <%def name='Badge(psLabel, psCssClass="")'>
18 55
   <span class='badge ${psCssClass}'>${psLabel}</span>
@@ -41,15 +78,23 @@
41 78
 <%def name='RichTextEditorToolbar(psRichTextEditorNodeId, psMenuOptions="styles|boldanditalic|lists|justifiers|links|images|undoredo|fullscreen")'>
42 79
       <div class="btn-toolbar" data-role="${psRichTextEditorNodeId}-toolbar" data-target="${psRichTextEditorNodeId}">
43 80
       % if psMenuOptions.find('styles')>=0:
81
+      
44 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 98
         </div>
54 99
       % endif
55 100
       % if psMenuOptions.find('boldanditalic')>=0:
@@ -76,22 +121,30 @@
76 121
           <a class="btn" data-edit="justifyfull" title="Justify (Ctrl/Cmd+J)"><i class="fa fa-align-justify"></i></a>
77 122
         </div>
78 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 148
       % if psMenuOptions.find('undoredo')>=0:
96 149
         <div class="btn-group">
97 150
           <a class="btn" data-edit="undo" title="Undo (Ctrl/Cmd+Z)"><i class="fa fa-undo"></i></a>
@@ -122,9 +175,9 @@
122 175
 
123 176
 <%def name='RichTextEditor(psRichTextEditorNodeId, psRichTextEditorContent="", psMenuOptions="styles|boldanditalic|lists|justifiers|links|images|undoredo|fullscreen")'>
124 177
   <div id="${psRichTextEditorNodeId}-widget" class="rich-text-editor-widget">
178
+    ${RichTextEditorToolbar(psRichTextEditorNodeId, psMenuOptions)}
125 179
     <div id="${psRichTextEditorNodeId}-widget-inner" class="rich-text-editor-widget-inner">
126 180
       <div id="${psRichTextEditorNodeId}-alert-container"></div>
127
-      ${RichTextEditorToolbar(psRichTextEditorNodeId, psMenuOptions)}
128 181
       <div id="${psRichTextEditorNodeId}" class="pod-rich-text-zone pod-input-like-shadow">
129 182
         ${psRichTextEditorContent|n}
130 183
       </div>

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

@@ -0,0 +1,134 @@
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
+