浏览代码

improve performance + add file upload basic features

damien 11 年前
父节点
当前提交
cf3b6e55eb

+ 58 - 0
pboard/pboard/controllers/api.py 查看文件

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 """Sample controller with all its actions protected."""
2
 """Sample controller with all its actions protected."""
3
 from datetime import datetime
3
 from datetime import datetime
4
+
5
+import cStringIO as csio
6
+import Image as pil
7
+
8
+import tg
4
 from tg import expose, flash, require, url, lurl, request, redirect, tmpl_context
9
 from tg import expose, flash, require, url, lurl, request, redirect, tmpl_context
5
 from tg.i18n import ugettext as _, lazy_ugettext as l_
10
 from tg.i18n import ugettext as _, lazy_ugettext as l_
6
 from tg import predicates
11
 from tg import predicates
54
       redirect(lurl('/document/%i'%(loNewNode.parent_id)))
59
       redirect(lurl('/document/%i'%(loNewNode.parent_id)))
55
 
60
 
56
     @expose()
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
     def set_parent_node(self, node_id, new_parent_id, **kw):
115
     def set_parent_node(self, node_id, new_parent_id, **kw):
58
       loNewNode = pld.getNode(node_id)
116
       loNewNode = pld.getNode(node_id)
59
       if new_parent_id!='':
117
       if new_parent_id!='':

+ 26 - 1
pboard/pboard/controllers/root.py 查看文件

122
     def document(self, node=0, came_from=lurl('/')):
122
     def document(self, node=0, came_from=lurl('/')):
123
         """show the user dashboard"""
123
         """show the user dashboard"""
124
         import pboard.model.data as pbmd
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
         liNodeId         = max(int(node), 1) # show node #1 if no selected node
130
         liNodeId         = max(int(node), 1) # show node #1 if no selected node
127
         loCurrentNode    = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.node_id==liNodeId).one()
131
         loCurrentNode    = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.node_id==liNodeId).one()
132
+        print "===> CCC"
128
         loNodeStatusList = pbmd.PBNodeStatus.getList()
133
         loNodeStatusList = pbmd.PBNodeStatus.getList()
134
+        print "===> DDD"
129
         return dict(root_node_list=loRootNodeList, current_node=loCurrentNode, node_status_list = loNodeStatusList)
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 查看文件

9
 from sqlalchemy import Table, ForeignKey, Column
9
 from sqlalchemy import Table, ForeignKey, Column
10
 from sqlalchemy.types import Unicode, Integer, DateTime, Text
10
 from sqlalchemy.types import Unicode, Integer, DateTime, Text
11
 from sqlalchemy.orm import relation, synonym
11
 from sqlalchemy.orm import relation, synonym
12
+from sqlalchemy.orm import joinedload_all
12
 
13
 
13
 from pboard.model import DeclarativeBase, metadata, DBSession
14
 from pboard.model import DeclarativeBase, metadata, DBSession
14
 from pboard.model import data as pbmd
15
 from pboard.model import data as pbmd
16
+import pboard.model as pbm
15
 
17
 
16
 def createNode():
18
 def createNode():
17
   loNode = pbmd.PBNode()
19
   loNode = pbmd.PBNode()
19
   return loNode
21
   return loNode
20
 
22
 
21
 def getNode(liNodeId):
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
 def getParentNode(loNode):
26
 def getParentNode(loNode):
25
   return DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.node_id==loNode.parent_id).one()
27
   return DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.node_id==loNode.parent_id).one()
36
     liNewWeight = liNewWeight + 1
38
     liNewWeight = liNewWeight + 1
37
     loNode.node_order = liNewWeight
39
     loNode.node_order = liNewWeight
38
   # DBSession.save()
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
 def moveNodeUpper(loNode):
46
 def moveNodeUpper(loNode):
42
   # FIXME - manage errors and logging
47
   # FIXME - manage errors and logging
81
 def deleteNode(loNode):
86
 def deleteNode(loNode):
82
   DBSession.delete(loNode)
87
   DBSession.delete(loNode)
83
   return
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 查看文件

17
 __all__ = ['User', 'Group', 'Permission']
17
 __all__ = ['User', 'Group', 'Permission']
18
 
18
 
19
 from sqlalchemy import Table, ForeignKey, Column, Sequence
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
 import tg
25
 import tg
24
 from pboard.model import DeclarativeBase, metadata, DBSession
26
 from pboard.model import DeclarativeBase, metadata, DBSession
25
 
27
 
26
 # This is the association table for the many-to-many relationship between
28
 # This is the association table for the many-to-many relationship between
27
 # groups and permissions.
29
 # groups and permissions.
28
-pb_node_table = Table('pb_nodes', metadata,
30
+"""pb_node_table = Table('pb_nodes', metadata,
29
     Column('node_id', Integer, Sequence('pb_nodes__node_id__sequence'), primary_key=True),
31
     Column('node_id', Integer, Sequence('pb_nodes__node_id__sequence'), primary_key=True),
30
     Column('parent_id', Integer, ForeignKey('pb_nodes.node_id'), nullable=True, default=None),
32
     Column('parent_id', Integer, ForeignKey('pb_nodes.node_id'), nullable=True, default=None),
31
     Column('node_order', Integer, nullable=True, default=1),
33
     Column('node_order', Integer, nullable=True, default=1),
39
     Column('data_content', Text(), unique=False, nullable=False, default=u''),
41
     Column('data_content', Text(), unique=False, nullable=False, default=u''),
40
     Column('data_datetime', DateTime, unique=False, nullable=False),
42
     Column('data_datetime', DateTime, unique=False, nullable=False),
41
     Column('data_reminder_datetime', DateTime, unique=False, nullable=True),
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
 - node_type
51
 - node_type
45
 
52
 
46
 - node_created_at
53
 - node_created_at
136
   Comment = 'comment'
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
   def getChildrenOfType(self, plNodeTypeList, plSortingCriteria):
191
   def getChildrenOfType(self, plNodeTypeList, plSortingCriteria):
142
     """return all children nodes of type 'data' or 'node' or 'folder'"""
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
   def getChildNb(self):
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
   def getChildren(self):
212
   def getChildren(self):
151
     """return all children nodes of type 'data' or 'node' or 'folder'"""
213
     """return all children nodes of type 'data' or 'node' or 'folder'"""
156
     return self.getChildrenOfType([PBNodeType.Contact], PBNode.data_label.asc())
218
     return self.getChildrenOfType([PBNodeType.Contact], PBNode.data_label.asc())
157
 
219
 
158
   def getEvents(self):
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
   def getIconClass(self):
229
   def getIconClass(self):
163
     laIconClass = dict()
230
     laIconClass = dict()
164
     laIconClass['node']   = 'icon-g-folder-open'
231
     laIconClass['node']   = 'icon-g-folder-open'
170
     laIconClass['contact'] = 'icon-user'
237
     laIconClass['contact'] = 'icon-user'
171
     laIconClass['comment'] = 'icon-comment'
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
       return laIconClass['folder']
241
       return laIconClass['folder']
175
     else:
242
     else:
176
       return laIconClass[self.node_type]
243
       return laIconClass[self.node_type]
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 查看文件

13
       % for new_parent_node in node_list:
13
       % for new_parent_node in node_list:
14
         <li>
14
         <li>
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>
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
         </li>
17
         </li>
18
       % endfor
18
       % endfor
19
       </ul>
19
       </ul>
36
   % else:
36
   % else:
37
     % if len(node_list)>0:
37
     % if len(node_list)>0:
38
       % for node in node_list:
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
           <a href="${tg.url('/document/%s'%(node.node_id))}" title="${node.data_label}">
41
           <a href="${tg.url('/document/%s'%(node.node_id))}" title="${node.data_label}">
41
             % if node.getStatus().status_family=='closed' or node.getStatus().status_family=='invisible':
42
             % if node.getStatus().status_family=='closed' or node.getStatus().status_family=='invisible':
42
               <strike>
43
               <strike>
43
             % endif
44
             % endif
44
-            <i class='${node.getIconClass()}'></i> ${node.getTruncatedLabel(32-0.8*(indentation+1))}
45
+                ${node.getTruncatedLabel(32-0.8*(indentation+1))}
45
             % if node.getStatus().status_family=='closed' or node.getStatus().status_family=='invisible':
46
             % if node.getStatus().status_family=='closed' or node.getStatus().status_family=='invisible':
46
               </strike>
47
               </strike>
47
             % endif
48
             % endif
55
              <i class='${node.getStatus().icon}'></i>
56
              <i class='${node.getStatus().icon}'></i>
56
           </div>
57
           </div>
57
         </div>
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
       % endfor
60
       % endfor
60
     % endif
61
     % endif
61
   % endif
62
   % endif
109
         ${POD.EditButton('current-document-content-edit-button', True)}
110
         ${POD.EditButton('current-document-content-edit-button', True)}
110
         <a class="btn" href="#" data-toggle="dropdown"><i class="icon-g-move"></i> ${_('Move to')} <span class="caret"></span></a>
111
         <a class="btn" href="#" data-toggle="dropdown"><i class="icon-g-move"></i> ${_('Move to')} <span class="caret"></span></a>
111
         <ul class="dropdown-menu">
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
         </ul>
115
         </ul>
114
 
116
 
115
 
117
 
145
     <div class="span4">
147
     <div class="span4">
146
       <div class="tabbable">
148
       <div class="tabbable">
147
         <ul class="nav nav-tabs">
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
             <li><a href="#comments" data-toggle="tab" title="Comments"><i class="icon-g-comments"></i> </a></li>
153
             <li><a href="#comments" data-toggle="tab" title="Comments"><i class="icon-g-comments"></i> </a></li>
154
             <li><a href="#files" data-toggle="tab" title="Files"><i class="icon-g-attach"></i> </a></li>
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
         </ul>
155
         </ul>
157
         <div class="tab-content">
156
         <div class="tab-content">
157
+            ################################
158
+            ##
159
+            ## PANEL SHOWING LIST OF TAGS
160
+            ##
161
+            ################################
158
             <div class="tab-pane" id="tags">
162
             <div class="tab-pane" id="tags">
159
               <div class="well">
163
               <div class="well">
160
                 <p>
164
                 <p>
172
                 % endfor
176
                 % endfor
173
               </div>
177
               </div>
174
             </div>
178
             </div>
179
+            ################################
180
+            ##
181
+            ## PANEL SHOWING LIST OF EVENTS
182
+            ##
183
+            ################################
175
             <div class="tab-pane active" id="events">
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
             % if len(current_node.getEvents())<=0:
220
             % if len(current_node.getEvents())<=0:
213
               <p><i>${_('No history for the moment.')}</i></p>
221
               <p><i>${_('No history for the moment.')}</i></p>
235
               </table>
243
               </table>
236
             % endif
244
             % endif
237
             </div>
245
             </div>
246
+            ##############################
247
+            ## 
248
+            ## PANEL SHOWING LIST OF CONTACTS
249
+            ##
250
+            ##############################
238
             <div class="tab-pane" id="contacts">
251
             <div class="tab-pane" id="contacts">
239
             
252
             
240
               <!-- ADD CONTACT FORM -->
253
               <!-- ADD CONTACT FORM -->
242
               <form style='display: none;' id='current-document-add-contact-form' action='${tg.url('/api/create_contact')}' method='post' class="well">
255
               <form style='display: none;' id='current-document-add-contact-form' action='${tg.url('/api/create_contact')}' method='post' class="well">
243
                 <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
256
                 <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
244
                 <fieldset>
257
                 <fieldset>
245
-                  <legend>Add an event</legend>
258
+                  <legend>${_('Add a contact')}</legend>
246
                   <label>
259
                   <label>
247
                     <input type="text" name='data_label' placeholder="Title"/>
260
                     <input type="text" name='data_label' placeholder="Title"/>
248
                   </label>
261
                   </label>
266
                   </div>
279
                   </div>
267
                 </div>
280
                 </div>
268
               % endfor
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
             </div>
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
         </div>
379
         </div>
273
       </div>
380
       </div>
274
     </div>
381
     </div>

+ 104 - 7
pboard/pboard/templates/master.mak 查看文件

122
 
122
 
123
             <script>
123
             <script>
124
               $(document).ready(function() {
124
               $(document).ready(function() {
125
-
126
-                
127
                 $('#current_node_textarea').wysihtml5({
125
                 $('#current_node_textarea').wysihtml5({
128
                   "font-styles": true, //Font styling, e.g. h1, h2, etc. Default true
126
                   "font-styles": true, //Font styling, e.g. h1, h2, etc. Default true
129
                   "emphasis": true, //Italics, bold, etc. Default true
127
                   "emphasis": true, //Italics, bold, etc. Default true
166
                 $('#add_contact_data_content_textarea').addClass("span3");
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
                 /* Edit title form */
196
                 /* Edit title form */
170
                 $("#current-document-title-edit-form" ).css("display", "none");
197
                 $("#current-document-title-edit-form" ).css("display", "none");
171
                 $("#current-document-title" ).dblclick(function() {
198
                 $("#current-document-title" ).dblclick(function() {
231
                   $('#current-document-add-contact-form').submit();
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
                 $(function() {
290
                 $(function() {
241
                   $('.datetime-picker-input-div').datetimepicker({
291
                   $('.datetime-picker-input-div').datetimepicker({
242
                     language: 'fr-FR',
292
                     language: 'fr-FR',
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
             </script>
346
             </script>
250
 </body>
347
 </body>
251
 
348