Browse Source

improve performance + add file upload basic features

damien 11 years ago
parent
commit
cf3b6e55eb

+ 58 - 0
pboard/pboard/controllers/api.py View File

@@ -1,6 +1,11 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 """Sample controller with all its actions protected."""
3 3
 from datetime import datetime
4
+
5
+import cStringIO as csio
6
+import Image as pil
7
+
8
+import tg
4 9
 from tg import expose, flash, require, url, lurl, request, redirect, tmpl_context
5 10
 from tg.i18n import ugettext as _, lazy_ugettext as l_
6 11
 from tg import predicates
@@ -54,6 +59,59 @@ class PODApiController(BaseController):
54 59
       redirect(lurl('/document/%i'%(loNewNode.parent_id)))
55 60
 
56 61
     @expose()
62
+    def create_comment(self, parent_id=None, data_label=u'', data_content=u'', **kw):
63
+
64
+      loNewNode = pld.createNode()
65
+      loNewNode.parent_id     = int(parent_id)
66
+      loNewNode.node_type     = pmd.PBNodeType.Comment
67
+      loNewNode.data_label    = data_label
68
+      loNewNode.data_content  = data_content
69
+
70
+      pm.DBSession.flush()
71
+      redirect(lurl('/document/%i'%(loNewNode.parent_id)))
72
+
73
+    @expose()
74
+    def create_file(self, parent_id=None, data_label=u'', data_content=u'', data_file=None, **kw):
75
+
76
+      loNewNode = pld.createNode()
77
+      loNewNode.parent_id     = int(parent_id)
78
+      loNewNode.node_type     = pmd.PBNodeType.File
79
+      loNewNode.data_label    = data_label
80
+      loNewNode.data_content  = data_content
81
+
82
+      loNewNode.data_file_name      = data_file.filename
83
+      loNewNode.data_file_mime_type = data_file.type
84
+      loNewNode.data_file_content   = data_file.file.read()
85
+
86
+      pm.DBSession.flush()
87
+      redirect(lurl('/document/%i'%(loNewNode.parent_id)))
88
+
89
+    @expose()
90
+    def get_file_content(self, node_id=None, **kw):
91
+      if node_id==None:
92
+        return
93
+      else:
94
+        loFile = pld.getNode(node_id)
95
+        tg.response.headers['Content-type'] = str(loFile.data_file_mime_type)
96
+        return loFile.data_file_content
97
+
98
+    @expose()
99
+    def get_file_content_thumbnail(self, node_id=None, **kw):
100
+      if node_id==None:
101
+        return
102
+      else:
103
+        loFile = pld.getNode(node_id)
104
+        
105
+        loJpegBytes = csio.StringIO(loFile.data_file_content)
106
+        loImage     = pil.open(loJpegBytes)
107
+        loImage.thumbnail([140,140], pil.ANTIALIAS)
108
+        
109
+        loResultBuffer = StringIO()
110
+        loImage.save(loResultBuffer,"JPEG")
111
+        tg.response.headers['Content-type'] = str(loFile.data_file_mime_type)
112
+        return loResultBuffer.getvalue()
113
+
114
+    @expose()
57 115
     def set_parent_node(self, node_id, new_parent_id, **kw):
58 116
       loNewNode = pld.getNode(node_id)
59 117
       if new_parent_id!='':

+ 26 - 1
pboard/pboard/controllers/root.py View File

@@ -122,11 +122,36 @@ class RootController(BaseController):
122 122
     def document(self, node=0, came_from=lurl('/')):
123 123
         """show the user dashboard"""
124 124
         import pboard.model.data as pbmd
125
-        loRootNodeList = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.parent_id==None).order_by(pbmd.PBNode.node_order).all()
125
+        
126
+        # loRootNodeList   = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.parent_id==None).order_by(pbmd.PBNode.node_order).all()
127
+        print "===> AAA"
128
+        loRootNodeList = pld.buildTreeListForMenu()
129
+        print "===> BBB"
126 130
         liNodeId         = max(int(node), 1) # show node #1 if no selected node
127 131
         loCurrentNode    = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.node_id==liNodeId).one()
132
+        print "===> CCC"
128 133
         loNodeStatusList = pbmd.PBNodeStatus.getList()
134
+        print "===> DDD"
129 135
         return dict(root_node_list=loRootNodeList, current_node=loCurrentNode, node_status_list = loNodeStatusList)
130 136
 
131 137
 
138
+    @expose()
139
+    def fill_treepath(self):
140
+        """show the user dashboard"""
141
+        import pboard.model.data as pbmd
142
+        
143
+        loRootNodeList   = pbm.DBSession.query(pbmd.PBNode).order_by(pbmd.PBNode.parent_id).all()
144
+        for loNode in loRootNodeList:
145
+          if loNode.parent_id==None:
146
+            loNode.node_depth = 0
147
+            loNode.parent_tree_path = '/'
148
+          else:
149
+            loNode.node_depth = loNode._oParent.node_depth+1
150
+            loNode.parent_tree_path = '%s%i/'%(loNode._oParent.parent_tree_path,loNode.parent_id)
151
+        
152
+        pbm.DBSession.flush()
153
+        
154
+        return 
155
+
156
+
132 157
 

+ 25 - 2
pboard/pboard/lib/dbapi.py View File

@@ -9,9 +9,11 @@ __all__ = ['User', 'Group', 'Permission']
9 9
 from sqlalchemy import Table, ForeignKey, Column
10 10
 from sqlalchemy.types import Unicode, Integer, DateTime, Text
11 11
 from sqlalchemy.orm import relation, synonym
12
+from sqlalchemy.orm import joinedload_all
12 13
 
13 14
 from pboard.model import DeclarativeBase, metadata, DBSession
14 15
 from pboard.model import data as pbmd
16
+import pboard.model as pbm
15 17
 
16 18
 def createNode():
17 19
   loNode = pbmd.PBNode()
@@ -19,7 +21,7 @@ def createNode():
19 21
   return loNode
20 22
 
21 23
 def getNode(liNodeId):
22
-  return DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.node_id==liNodeId).one()
24
+  return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.node_id==liNodeId).one()
23 25
 
24 26
 def getParentNode(loNode):
25 27
   return DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.node_id==loNode.parent_id).one()
@@ -36,7 +38,10 @@ def resetNodeOrderOfSiblingNodes(loSiblingNodes):
36 38
     liNewWeight = liNewWeight + 1
37 39
     loNode.node_order = liNewWeight
38 40
   # DBSession.save()
39
-  
41
+
42
+def getNodeFileContent(liNodeId):
43
+  return DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.node_id==liNodeId).one().data_file_content
44
+
40 45
 
41 46
 def moveNodeUpper(loNode):
42 47
   # FIXME - manage errors and logging
@@ -81,3 +86,21 @@ def moveNodeLower(loNode):
81 86
 def deleteNode(loNode):
82 87
   DBSession.delete(loNode)
83 88
   return
89
+
90
+def buildTreeListForMenu():
91
+  loNodeList = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.node_type==pbmd.PBNodeType.Data).order_by(pbmd.PBNode.parent_tree_path).order_by(pbmd.PBNode.node_order).all()
92
+  loTreeList = []
93
+  loTmpDict = {}
94
+  for loNode in loNodeList:
95
+    loTmpDict[loNode.node_id] = loNode
96
+
97
+    if loNode.parent_id==None:
98
+      loTreeList.append(loNode)
99
+    else:
100
+      # append the node to the parent list
101
+      loTmpDict[loNode.parent_id].appendStaticChild(loNode)
102
+
103
+  print "=================="
104
+  print loTmpDict[101].getStaticChildList()
105
+  return loTreeList
106
+

+ 101 - 12
pboard/pboard/model/data.py View File

@@ -17,15 +17,17 @@ from hashlib import sha256
17 17
 __all__ = ['User', 'Group', 'Permission']
18 18
 
19 19
 from sqlalchemy import Table, ForeignKey, Column, Sequence
20
-from sqlalchemy.types import Unicode, Integer, DateTime, Text
21
-from sqlalchemy.orm import relation, synonym
20
+from sqlalchemy.types import Unicode, Integer, DateTime, Text, LargeBinary
21
+from sqlalchemy.orm import relation, synonym, relationship
22
+from sqlalchemy.orm import backref
23
+from sqlalchemy import orm as sqlao
22 24
 
23 25
 import tg
24 26
 from pboard.model import DeclarativeBase, metadata, DBSession
25 27
 
26 28
 # This is the association table for the many-to-many relationship between
27 29
 # groups and permissions.
28
-pb_node_table = Table('pb_nodes', metadata,
30
+"""pb_node_table = Table('pb_nodes', metadata,
29 31
     Column('node_id', Integer, Sequence('pb_nodes__node_id__sequence'), primary_key=True),
30 32
     Column('parent_id', Integer, ForeignKey('pb_nodes.node_id'), nullable=True, default=None),
31 33
     Column('node_order', Integer, nullable=True, default=1),
@@ -39,8 +41,13 @@ pb_node_table = Table('pb_nodes', metadata,
39 41
     Column('data_content', Text(), unique=False, nullable=False, default=u''),
40 42
     Column('data_datetime', DateTime, unique=False, nullable=False),
41 43
     Column('data_reminder_datetime', DateTime, unique=False, nullable=True),
44
+    
45
+    Column('data_file_name', Unicode(255), unique=False, nullable=False, default=u''),
46
+    Column('data_file_mime_type', Unicode(255), unique=False, nullable=False, default=u''),
47
+    Column('data_file_content', LargeBinary(), unique=False, nullable=False, default=None),
42 48
 )
43 49
 """
50
+"""
44 51
 - node_type
45 52
 
46 53
 - node_created_at
@@ -136,16 +143,71 @@ class PBNodeType(object):
136 143
   Comment = 'comment'
137 144
 
138 145
 
139
-class PBNode(object):
146
+class PBNode(DeclarativeBase):
147
+
148
+  def __init__(self):
149
+    self._lStaticChildList = []
150
+
151
+  @sqlao.reconstructor
152
+  def init_on_load(self):
153
+    self._lStaticChildList = []
154
+
155
+  def appendStaticChild(self, loNode):
156
+    print "%s has child %s" % (self.node_id, loNode.node_id)
157
+    self._lStaticChildList.append(loNode)
158
+
159
+  def getStaticChildList(self):
160
+    return self._lStaticChildList
161
+
162
+  def getStaticChildNb(self):
163
+    return len(self._lStaticChildList)
164
+
165
+  __tablename__ = 'pb_nodes'
166
+  node_id          = Column(Integer, Sequence('pb_nodes__node_id__sequence'), primary_key=True)
167
+  parent_id        = Column(Integer, ForeignKey('pb_nodes.node_id'), nullable=True, default=None)
168
+  node_depth       = Column(Integer, unique=False, nullable=False, default=0)
169
+  parent_tree_path = Column(Unicode(255), unique=False, nullable=False, default=u'data')
170
+
171
+  node_order  = Column(Integer, nullable=True, default=1)
172
+  node_type   = Column(Unicode(16), unique=False, nullable=False, default=u'data')
173
+  node_status = Column(Unicode(16), unique=False, nullable=False, default=u'new')
174
+
175
+  created_at = Column(DateTime, unique=False, nullable=False)
176
+  updated_at = Column(DateTime, unique=False, nullable=False)
177
+
178
+  data_label   = Column(Unicode(1024), unique=False, nullable=False, default=u'')
179
+  data_content = Column(Text(),        unique=False, nullable=False, default=u'')
180
+  
181
+  data_datetime          = Column(DateTime, unique=False, nullable=False)
182
+  data_reminder_datetime = Column(DateTime, unique=False, nullable=True)
183
+  
184
+  data_file_name      = Column(Unicode(255),  unique=False, nullable=False, default=u'')
185
+  data_file_mime_type = Column(Unicode(255),  unique=False, nullable=False, default=u'')
186
+  data_file_content   = Column(LargeBinary(), unique=False, nullable=False, default=None)
187
+
188
+
189
+  _oParent = relationship('PBNode', remote_side=[node_id], backref='_lAllChildren')
140 190
 
141 191
   def getChildrenOfType(self, plNodeTypeList, plSortingCriteria):
142 192
     """return all children nodes of type 'data' or 'node' or 'folder'"""
143
-    return DBSession.query(PBNode).filter(PBNode.parent_id==self.node_id).filter(PBNode.node_type.in_(plNodeTypeList)).order_by(plSortingCriteria).all()
193
+    llChildren = []
194
+    for child in self._lAllChildren:
195
+      if child.node_type in plNodeTypeList:
196
+        llChildren.append(child)
197
+    return llChildren
198
+    # return DBSession.query(PBNode).filter(PBNode.parent_id==self.node_id).filter(PBNode.node_type.in_(plNodeTypeList)).order_by(plSortingCriteria).all()
144 199
   
200
+  def getChildNbOfType(self, plNodeTypeList):
201
+    """return all children nodes of type 'data' or 'node' or 'folder'"""
202
+    liChildNb = 0
203
+    for child in self._lAllChildren:
204
+      if child.node_type in plNodeTypeList:
205
+        liChildNb = liChildNb+1
206
+    return liChildNb
207
+    # return DBSession.query(PBNode).filter(PBNode.parent_id==self.node_id).filter(PBNode.node_type.in_(plNodeTypeList)).order_by(plSortingCriteria).all()
145 208
   
146 209
   def getChildNb(self):
147
-    liChildNb = DBSession.query(PBNode).filter(PBNode.parent_id==self.node_id).filter(PBNode.node_type==PBNodeType.Data).count()
148
-    return liChildNb
210
+    return self.getChildNbOfType([PBNodeType.Data])
149 211
 
150 212
   def getChildren(self):
151 213
     """return all children nodes of type 'data' or 'node' or 'folder'"""
@@ -156,9 +218,14 @@ class PBNode(object):
156 218
     return self.getChildrenOfType([PBNodeType.Contact], PBNode.data_label.asc())
157 219
 
158 220
   def getEvents(self):
159
-    return DBSession.query(PBNode).filter(PBNode.parent_id==self.node_id).filter(PBNode.node_type==PBNodeType.Event).order_by(PBNode.data_datetime.desc()).all()
160
-    return []
221
+    return self.getChildrenOfType([PBNodeType.Event], PBNode.data_datetime.desc())
161 222
     
223
+  def getFiles(self):
224
+    return self.getChildrenOfType([PBNodeType.File], PBNode.data_datetime.desc())
225
+
226
+  def getComments(self):
227
+    return self.getChildrenOfType([PBNodeType.Comment], PBNode.data_datetime.desc())
228
+
162 229
   def getIconClass(self):
163 230
     laIconClass = dict()
164 231
     laIconClass['node']   = 'icon-g-folder-open'
@@ -170,7 +237,7 @@ class PBNode(object):
170 237
     laIconClass['contact'] = 'icon-user'
171 238
     laIconClass['comment'] = 'icon-comment'
172 239
 
173
-    if self.node_type==PBNodeType.Data and self.getChildNb()>0:
240
+    if self.node_type==PBNodeType.Data and self.getStaticChildNb()>0:
174 241
       return laIconClass['folder']
175 242
     else:
176 243
       return laIconClass[self.node_type]
@@ -222,6 +289,28 @@ class PBNode(object):
222 289
 
223 290
 
224 291
 
225
-from sqlalchemy.orm import mapper
226
-mapper(PBNode, pb_node_table)
292
+"""from sqlalchemy.orm import mapper
293
+mapper(
294
+  PBNode,
295
+  pb_node_table,
296
+  properties = {'_lAllChildren' : relationship(PBNode, backref=backref('_oParent', remote_side=PBNode.parent_id)) }
297
+)"""
298
+
299
+
300
+
301
+"""    children = relationship('TreeNode',
302
+
303
+                        # cascade deletions
304
+                        cascade="all",
305
+
306
+                        # many to one + adjacency list - remote_side
307
+                        # is required to reference the 'remote' 
308
+                        # column in the join condition.
309
+                        backref=backref("parent", remote_side='TreeNode.id'),
310
+
311
+                        # children will be represented as a dictionary
312
+                        # on the "name" attribute.
313
+                        collection_class=attribute_mapped_collection('name'),
314
+                    ) 
315
+"""
227 316
 

+ 154 - 47
pboard/pboard/templates/document.mak View File

@@ -13,7 +13,7 @@
13 13
       % for new_parent_node in node_list:
14 14
         <li>
15 15
           <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>
16
-          ${node_treeview_for_set_parent_menu(node_id, new_parent_node.getChildren(), indentation+1)}
16
+          ${node_treeview_for_set_parent_menu(node_id, new_parent_node.getStaticChildList(), indentation+1)}
17 17
         </li>
18 18
       % endfor
19 19
       </ul>
@@ -36,12 +36,13 @@
36 36
   % else:
37 37
     % if len(node_list)>0:
38 38
       % for node in node_list:
39
-        <div class="pod-toolbar-parent ${'pod-status-active' if node.node_id==current_node.node_id else ''}" style="padding-left: ${(indentation+2)*0.5}em; position: relative;">
39
+        <div id='pod-menu-item-${node.node_id}' class="pod-toolbar-parent ${'pod-status-active' if node.node_id==current_node.node_id else ''}" style="padding-left: ${(indentation+2)*0.5}em; position: relative;">
40
+          <a class="toggle-child-menu-items"><i class='${node.getIconClass()}'></i></a>
40 41
           <a href="${tg.url('/document/%s'%(node.node_id))}" title="${node.data_label}">
41 42
             % if node.getStatus().status_family=='closed' or node.getStatus().status_family=='invisible':
42 43
               <strike>
43 44
             % endif
44
-            <i class='${node.getIconClass()}'></i> ${node.getTruncatedLabel(32-0.8*(indentation+1))}
45
+                ${node.getTruncatedLabel(32-0.8*(indentation+1))}
45 46
             % if node.getStatus().status_family=='closed' or node.getStatus().status_family=='invisible':
46 47
               </strike>
47 48
             % endif
@@ -55,7 +56,7 @@
55 56
              <i class='${node.getStatus().icon}'></i>
56 57
           </div>
57 58
         </div>
58
-        ${node_treeview(node.getChildren(), indentation+1)}
59
+        <div id="pod-menu-item-${node.node_id}-children">${node_treeview(node.getStaticChildList(), indentation+1)}</div>
59 60
       % endfor
60 61
     % endif
61 62
   % endif
@@ -109,7 +110,8 @@ POD :: ${current_node.getTruncatedLabel(40)} [${current_node.getStatus().label}]
109 110
         ${POD.EditButton('current-document-content-edit-button', True)}
110 111
         <a class="btn" href="#" data-toggle="dropdown"><i class="icon-g-move"></i> ${_('Move to')} <span class="caret"></span></a>
111 112
         <ul class="dropdown-menu">
112
-          ${node_treeview_for_set_parent_menu(current_node.node_id, root_node_list)}
113
+          AGAGA
114
+          {node_treeview_for_set_parent_menu(current_node.node_id, root_node_list)}
113 115
         </ul>
114 116
 
115 117
 
@@ -145,16 +147,18 @@ POD :: ${current_node.getTruncatedLabel(40)} [${current_node.getStatus().label}]
145 147
     <div class="span4">
146 148
       <div class="tabbable">
147 149
         <ul class="nav nav-tabs">
148
-            <li class=""><a href="#tags" data-toggle="tab" title="${_('Tags')}"><i class='icon-g-tags'></i></a></li>
149
-            <li class="active">
150
-              <a href="#events" data-toggle="tab" title="History"><i class="icon-g-history"></i></a>
151
-            </li>
152
-            <li><a href="#contacts" data-toggle="tab" title="Contacts"><i class="icon-g-phone""></i> </a></li>
150
+            <li class="active"><a href="#tags" data-toggle="tab" title="${_('Tags')}"><i class='icon-g-tags'></i></a></li>
151
+            <li><a href="#events" data-toggle="tab" title="History"><i class="icon-g-history"></i></a></li>
152
+            <li><a href="#contacts" data-toggle="tab" title="Contacts"><i class="icon-g-user""></i> </a></li>
153 153
             <li><a href="#comments" data-toggle="tab" title="Comments"><i class="icon-g-comments"></i> </a></li>
154 154
             <li><a href="#files" data-toggle="tab" title="Files"><i class="icon-g-attach"></i> </a></li>
155
-            <li><a href="#contacts" data-toggle="tab" title="Users"><i class="icon-g-user""></i> </a></li>
156 155
         </ul>
157 156
         <div class="tab-content">
157
+            ################################
158
+            ##
159
+            ## PANEL SHOWING LIST OF TAGS
160
+            ##
161
+            ################################
158 162
             <div class="tab-pane" id="tags">
159 163
               <div class="well">
160 164
                 <p>
@@ -172,42 +176,46 @@ POD :: ${current_node.getTruncatedLabel(40)} [${current_node.getStatus().label}]
172 176
                 % endfor
173 177
               </div>
174 178
             </div>
179
+            ################################
180
+            ##
181
+            ## PANEL SHOWING LIST OF EVENTS
182
+            ##
183
+            ################################
175 184
             <div class="tab-pane active" id="events">
176
-
177
-${POD.AddButton('current-document-add-event-button', True, _(' Add event'))}
178
-<form style='display: none;' id='current-document-add-event-form' action='${tg.url('/api/create_event')}' method='post' class="well">
179
-  <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
180
-  <fieldset>
181
-    <legend>Add an event</legend>
182
-    <label>
183
-      <input type="text" name='data_label' placeholder="Event"/>
184
-    </label>
185
-    <label>
186
-      <div class="datetime-picker-input-div input-append date">
187
-        <input name='data_datetime' data-format="dd/MM/yyyy hh:mm" type="text" placeholder="date and time"/>
188
-        <span class="add-on"><i data-time-icon="icon-g-clock" data-date-icon="icon-g-calendar"></i></span>
189
-      </div>
190
-    </label>
191
-    <label>
192
-      <div>
193
-        <textarea id="add_event_data_content_textarea" name='data_content' spellcheck="false" wrap="off" autofocus placeholder="${_('detail...')}"></textarea>
194
-      </div>
195
-    </label>
196
-    <label class="checkbox">
197
-      <input disabled name='add_reminder' type="checkbox"> add a reminder
198
-    </label>
199
-    <label>
200
-      <div class="datetime-picker-input-div input-append date">
201
-        <input disabled name='data_reminder_datetime' data-format="dd/MM/yyyy hh:mm" type="text" placeholder="date and time"/>
202
-        <span class="add-on"><i data-time-icon="icon-g-clock" data-date-icon="icon-g-calendar"></i></span>
203
-      </div>
204
-    </label>
185
+              ${POD.AddButton('current-document-add-event-button', True, _(' Add event'))}
186
+              <form style='display: none;' id='current-document-add-event-form' action='${tg.url('/api/create_event')}' method='post' class="well">
187
+                <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
188
+                <fieldset>
189
+                  <legend>Add an event</legend>
190
+                  <label>
191
+                    <input type="text" name='data_label' placeholder="Event"/>
192
+                  </label>
193
+                  <label>
194
+                    <div class="datetime-picker-input-div input-append date">
195
+                      <input name='data_datetime' data-format="dd/MM/yyyy hh:mm" type="text" placeholder="date and time"/>
196
+                      <span class="add-on"><i data-time-icon="icon-g-clock" data-date-icon="icon-g-calendar"></i></span>
197
+                    </div>
198
+                  </label>
199
+                  <label>
200
+                    <div>
201
+                      <textarea id="add_event_data_content_textarea" name='data_content' spellcheck="false" wrap="off" autofocus placeholder="${_('detail...')}"></textarea>
202
+                    </div>
203
+                  </label>
204
+                  <label class="checkbox">
205
+                    <input disabled name='add_reminder' type="checkbox"> add a reminder
206
+                  </label>
207
+                  <label>
208
+                    <div class="datetime-picker-input-div input-append date">
209
+                      <input disabled name='data_reminder_datetime' data-format="dd/MM/yyyy hh:mm" type="text" placeholder="date and time"/>
210
+                      <span class="add-on"><i data-time-icon="icon-g-clock" data-date-icon="icon-g-calendar"></i></span>
211
+                    </div>
212
+                  </label>
205 213
 
206 214
 
207
-    ${POD.CancelButton('current-document-add-event-cancel-button', True)}
208
-    ${POD.SaveButton('current-document-add-event-save-button', True)}
209
-  </fieldset>
210
-</form>
215
+                  ${POD.CancelButton('current-document-add-event-cancel-button', True)}
216
+                  ${POD.SaveButton('current-document-add-event-save-button', True)}
217
+                </fieldset>
218
+              </form>
211 219
 
212 220
             % if len(current_node.getEvents())<=0:
213 221
               <p><i>${_('No history for the moment.')}</i></p>
@@ -235,6 +243,11 @@ ${POD.AddButton('current-document-add-event-button', True, _(' Add event'))}
235 243
               </table>
236 244
             % endif
237 245
             </div>
246
+            ##############################
247
+            ## 
248
+            ## PANEL SHOWING LIST OF CONTACTS
249
+            ##
250
+            ##############################
238 251
             <div class="tab-pane" id="contacts">
239 252
             
240 253
               <!-- ADD CONTACT FORM -->
@@ -242,7 +255,7 @@ ${POD.AddButton('current-document-add-event-button', True, _(' Add event'))}
242 255
               <form style='display: none;' id='current-document-add-contact-form' action='${tg.url('/api/create_contact')}' method='post' class="well">
243 256
                 <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
244 257
                 <fieldset>
245
-                  <legend>Add an event</legend>
258
+                  <legend>${_('Add a contact')}</legend>
246 259
                   <label>
247 260
                     <input type="text" name='data_label' placeholder="Title"/>
248 261
                   </label>
@@ -266,9 +279,103 @@ ${POD.AddButton('current-document-add-event-button', True, _(' Add event'))}
266 279
                   </div>
267 280
                 </div>
268 281
               % endfor
282
+
283
+
284
+            </div>
285
+            ################################
286
+            ##
287
+            ## PANEL SHOWING LIST OF COMMENTS
288
+            ##
289
+            ################################
290
+            <div class="tab-pane" id="comments">
291
+              <!-- ADD COMMENT FORM -->
292
+              ${POD.AddButton('current-document-add-comment-button', True, _(' Add comment'))}
293
+              <form style='display: none;' id='current-document-add-comment-form' action='${tg.url('/api/create_comment')}' method='post' class="well">
294
+                <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
295
+                <fieldset>
296
+                  <legend>${_('Add a comment')}</legend>
297
+                  <label>
298
+                    <input type="text" name='data_label' placeholder="Title"/>
299
+                  </label>
300
+                  <label>
301
+                    <div>
302
+                      <textarea id="add_comment_data_content_textarea" name='data_content' spellcheck="false" wrap="off" autofocus placeholder="${_('comment...')}"></textarea>
303
+                    </div>
304
+                  </label>
305
+                  ${POD.CancelButton('current-document-add-comment-cancel-button', True)}
306
+                  ${POD.SaveButton('current-document-add-comment-save-button', True)}
307
+                </fieldset>
308
+              </form>
309
+
310
+              <!-- LIST OF COMMENTS -->
311
+            % if len(current_node.getComments())<=0:
312
+              <p><i>${_('No comments.')}</i></p>
313
+            % else:
314
+              <table class="table table-striped table-hover table-condensed">
315
+                % for comment in current_node.getComments():
316
+                  <tr title="Last updated: ${event.updated_at}">
317
+                    <td>
318
+                      <b>${comment.data_label}</b><br/>
319
+                      <i>commented by comment.author the ${comment.getFormattedDate(comment.updated_at)} at ${comment.getFormattedTime(comment.updated_at)}</i></br>
320
+                      <p>
321
+                        ${comment.data_content|n}
322
+                      </p>
323
+                    </td>
324
+                  </tr>
325
+                % endfor
326
+              </table>
327
+            % endif
328
+            </div>
329
+            ################################
330
+            ##
331
+            ## PANEL SHOWING LIST OF FILES
332
+            ##
333
+            ################################
334
+            <div class="tab-pane" id="files">
335
+              <!-- ADD FILE FORM -->
336
+              ${POD.AddButton('current-document-add-file-button', True, _(' Add file'))}
337
+              <form style='display: none;' id='current-document-add-file-form' enctype="multipart/form-data" action='${tg.url('/api/create_file')}' method='post' class="well">
338
+                <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
339
+                <fieldset>
340
+                  <legend>${_('Add a file')}</legend>
341
+                  <label>
342
+                    <input type="text" name='data_label' placeholder="Title"/>
343
+                  </label>
344
+                  <label>
345
+                    <input type="file" name='data_file' placeholder="choose a file..."/>
346
+                  </label>
347
+                  <label>
348
+                    <div>
349
+                      <textarea id="add_file_data_content_textarea" name='data_content' spellcheck="false" wrap="off" autofocus placeholder="${_('comment...')}"></textarea>
350
+                    </div>
351
+                  </label>
352
+                  ${POD.CancelButton('current-document-add-file-cancel-button', True)}
353
+                  ${POD.SaveButton('current-document-add-file-save-button', True)}
354
+                </fieldset>
355
+              </form>
356
+
357
+              <!-- LIST OF FILES -->
358
+            % if len(current_node.getFiles())<=0:
359
+              <p><i>${_('No files.')}</i></p>
360
+            % else:
361
+              <table class="table table-striped table-hover table-condensed">
362
+                % for current_file in current_node.getFiles():
363
+                  <tr title="Last updated: ${event.updated_at}">
364
+                    <td>
365
+                      <img src="${tg.url('/api/get_file_content_thumbnail/%s'%(current_file.node_id))}" class="img-polaroid">
366
+                    </td>
367
+                    <td>
368
+                      <b>${current_file.data_label}</b><br/>
369
+                      <i>commented by comment.author the ${current_file.getFormattedDate(comment.updated_at)} at ${current_file.getFormattedTime(comment.updated_at)}</i></br>
370
+                      <p>
371
+                        ${current_file.data_content|n}
372
+                      </p>
373
+                    </td>
374
+                  </tr>
375
+                % endfor
376
+              </table>
377
+            % endif
269 378
             </div>
270
-            <div class="tab-pane" id="comments">${current_node.data_content|n}</div>
271
-            <div class="tab-pane" id="files">Files</div>
272 379
         </div>
273 380
       </div>
274 381
     </div>

+ 104 - 7
pboard/pboard/templates/master.mak View File

@@ -122,8 +122,6 @@ tr:Hover td div.pod-toolbar {
122 122
 
123 123
             <script>
124 124
               $(document).ready(function() {
125
-
126
-                
127 125
                 $('#current_node_textarea').wysihtml5({
128 126
                   "font-styles": true, //Font styling, e.g. h1, h2, etc. Default true
129 127
                   "emphasis": true, //Italics, bold, etc. Default true
@@ -166,6 +164,35 @@ tr:Hover td div.pod-toolbar {
166 164
                 $('#add_contact_data_content_textarea').addClass("span3");
167 165
 
168 166
 
167
+                /* ADD COMMENT FORM */
168
+                $('#add_comment_data_content_textarea').wysihtml5({
169
+                  "font-styles": false, //Font styling, e.g. h1, h2, etc. Default true
170
+                  "emphasis": true, //Italics, bold, etc. Default true
171
+                  "lists": false, //(Un)ordered lists, e.g. Bullets, Numbers. Default true
172
+                  "html": true, //Button which allows you to edit the generated HTML. Default false
173
+                  "link": true, //Button to insert a link. Default true
174
+                  "image": true, //Button to insert an image. Default true,
175
+                  // "color": true //Button to change color of font  
176
+                });
177
+                $('#add_comment_data_content_textarea').css('margin-bottom', '0');
178
+                $('#add_comment_data_content_textarea').css("height", "4em");
179
+                $('#add_comment_data_content_textarea').addClass("span3");
180
+
181
+                /* ADD FILE FORM */
182
+                $('#add_file_data_content_textarea').wysihtml5({
183
+                  "font-styles": false, //Font styling, e.g. h1, h2, etc. Default true
184
+                  "emphasis": true, //Italics, bold, etc. Default true
185
+                  "lists": false, //(Un)ordered lists, e.g. Bullets, Numbers. Default true
186
+                  "html": true, //Button which allows you to edit the generated HTML. Default false
187
+                  "link": true, //Button to insert a link. Default true
188
+                  "image": true, //Button to insert an image. Default true,
189
+                  // "color": true //Button to change color of font  
190
+                });
191
+                $('#add_file_data_content_textarea').css('margin-bottom', '0');
192
+                $('#add_file_data_content_textarea').css("height", "4em");
193
+                $('#add_file_data_content_textarea').addClass("span3");
194
+
195
+
169 196
                 /* Edit title form */
170 197
                 $("#current-document-title-edit-form" ).css("display", "none");
171 198
                 $("#current-document-title" ).dblclick(function() {
@@ -231,12 +258,35 @@ tr:Hover td div.pod-toolbar {
231 258
                   $('#current-document-add-contact-form').submit();
232 259
                 });
233 260
 
261
+                /* Add comment form hide/show behavior */
262
+                $("#current-document-add-comment-button" ).click(function() {
263
+                  $("#current-document-add-comment-form" ).css("display", "block");
264
+                  $("#current-document-add-comment-button" ).css("display", "none");
265
+                });
266
+                $('#current-document-add-comment-cancel-button').on('click', function(e){
267
+                  $("#current-document-add-comment-form" ).css("display", "none");
268
+                  $("#current-document-add-comment-button" ).css("display", "block");
269
+                });
270
+                $('#current-document-add-comment-save-button').on('click', function(e){
271
+                  e.preventDefault(); // We don't want this to act as a link so cancel the link action
272
+                  $('#current-document-add-comment-form').submit();
273
+                });
234 274
 
235
-
236
-/*                $('.date-picker-input').datepicker({
237
-                  format: 'mm-dd-yyyy'
275
+                /* Add file form hide/show behavior */
276
+                $("#current-document-add-file-button" ).click(function() {
277
+                  $("#current-document-add-file-form" ).css("display", "block");
278
+                  $("#current-document-add-file-button" ).css("display", "none");
238 279
                 });
239
-*/
280
+                $('#current-document-add-file-cancel-button').on('click', function(e){
281
+                  $("#current-document-add-file-form" ).css("display", "none");
282
+                  $("#current-document-add-file-button" ).css("display", "block");
283
+                });
284
+                $('#current-document-add-file-save-button').on('click', function(e){
285
+                  e.preventDefault(); // We don't want this to act as a link so cancel the link action
286
+                  $('#current-document-add-file-form').submit();
287
+                });
288
+
289
+
240 290
                 $(function() {
241 291
                   $('.datetime-picker-input-div').datetimepicker({
242 292
                     language: 'fr-FR',
@@ -244,8 +294,55 @@ tr:Hover td div.pod-toolbar {
244 294
                   });
245 295
                 });
246 296
 
297
+/*
298
+                // Allow to go directly to required tab on load
299
+                // Javascript to enable link to tab
300
+                var url = document.location.toString();
301
+                if (url.match('#')) {
302
+                  $('.nav-tabs a[href=#'+url.split('#')[1]+']').tab('show') ;
303
+                } 
304
+
305
+                // Change hash for page-reload
306
+                $('.nav-tabs a').on('shown', function (e) {
307
+                  window.location.hash = e.target.hash;
308
+                })
309
+*/
310
+                #################################
311
+                ##
312
+                ## The following JS code allow t
313
+                ##
314
+                ##
315
+                // Javascript to enable link to tab
316
+                var hash = document.location.hash;
317
+                var prefix = "tab-";
318
+                if (hash) {
319
+                    $('.nav-tabs a[href='+hash.replace(prefix,"")+']').tab('show');
320
+                } 
321
+
322
+                // Change hash for page-reload
323
+                $('.nav-tabs a').on('shown', function (e) {
324
+                    window.location.hash = e.target.hash.replace("#", "#" + prefix);
325
+                });
326
+
327
+                $('a.toggle-child-menu-items').on('click', function (e) {
328
+                  parent_id    = $(this).parent().attr('id');
329
+                  child        = $('#'+parent_id+'-children');
330
+                  togglebutton = $(this).children('i:first')
331
+                  if(child.css('display')=='none'){
332
+                    child.css("display", "block");
333
+                    togglebutton.removeClass('icon-g-folder-plus');
334
+                    togglebutton.attr('class', 'icon-g-folder-open');
335
+                    console.log("class is: "+togglebutton.attr('class'));
336
+                  } else {
337
+                    child.css("display", "none");
338
+                    togglebutton.removeClass('icon-g-folder-open');
339
+                    togglebutton.addClass('icon-g-folder-plus');
340
+                    console.log("class is: "+togglebutton.attr('class'));
341
+                  }
342
+                  
343
+                });
247 344
               });
248
-              
345
+
249 346
             </script>
250 347
 </body>
251 348