Browse Source

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

sferot 10 years ago
parent
commit
f9d574446c

+ 21 - 0
doc/database/pod-upgrade-0.3.0_to_0.4.0.sql View File

@@ -11,4 +11,25 @@ CREATE OR REPLACE VIEW pod_nodes AS
11 11
     FROM pod_nodes_history
12 12
     ORDER BY node_id, updated_at DESC;
13 13
 
14
+CREATE OR REPLACE RULE pod_insert_new_node AS ON INSERT
15
+TO pod_nodes
16
+DO INSTEAD INSERT INTO pod_nodes_history (node_id, parent_id, node_order, node_type, created_at, updated_at, 
17
+       data_label, data_content, data_datetime, node_status, data_reminder_datetime, 
18
+       data_file_name, data_file_content, data_file_mime_type, parent_tree_path, 
19
+       node_depth, owner_id, version_id, is_shared, is_public, public_url_key) VALUES (nextval('pod_nodes__node_id__sequence'), NEW.parent_id, NEW.node_order, NEW.node_type, NEW.created_at, NEW.updated_at, NEW.data_label, NEW.data_content, NEW.data_datetime, NEW.node_status, NEW.data_reminder_datetime, NEW.data_file_name, NEW.data_file_content, NEW.data_file_mime_type, NEW.parent_tree_path, NEW.node_depth, NEW.owner_id, nextval('pod_nodes_version_id_sequence'), NEW.is_shared, NEW.is_public, NEW.public_url_key)
20
+RETURNING node_id, parent_id, node_order, node_type, created_at, updated_at, 
21
+       data_label, data_content, data_datetime, node_status, data_reminder_datetime, 
22
+       data_file_name, data_file_content, data_file_mime_type, parent_tree_path, 
23
+       node_depth, owner_id, is_shared, is_public, public_url_key;
14 24
 
25
+CREATE OR REPLACE FUNCTION pod_update_node() RETURNS trigger AS $$
26
+BEGIN
27
+INSERT INTO pod_nodes_history (node_id, parent_id, node_order, node_type, created_at, updated_at, 
28
+       data_label, data_content, data_datetime, node_status, data_reminder_datetime, 
29
+       data_file_name, data_file_content, data_file_mime_type, parent_tree_path, 
30
+       node_depth, owner_id, version_id, is_shared, is_public, public_url_key) VALUES (NEW.node_id, NEW.parent_id, NEW.node_order, NEW.node_type, NEW.created_at, NEW.updated_at, NEW.data_label, NEW.data_content, NEW.data_datetime, NEW.node_status, NEW.data_reminder_datetime, NEW.data_file_name, NEW.data_file_content, NEW.data_file_mime_type, NEW.parent_tree_path, NEW.node_depth, NEW.owner_id, nextval('pod_nodes_version_id_sequence'), NEW.is_shared, NEW.is_public, NEW.public_url_key);
31
+return new;
32
+END;
33
+$$ LANGUAGE plpgsql;
34
+
35
+CREATE TRIGGER pod_update_node_tg INSTEAD OF UPDATE ON pod_nodes FOR EACH ROW EXECUTE PROCEDURE pod_update_node();

+ 1 - 1
pboard/pboard/config/app_cfg.py View File

@@ -22,7 +22,7 @@ from pboard.lib import app_globals, helpers
22 22
 base_config = AppConfig()
23 23
 base_config.renderers = []
24 24
 base_config.use_toscawidgets = False
25
-base_config.use_toscawidgets2 = False
25
+base_config.use_toscawidgets2 = True
26 26
 
27 27
 base_config.package = pboard
28 28
 

+ 30 - 8
pboard/pboard/controllers/api.py View File

@@ -93,15 +93,17 @@ class PODApiController(BaseController):
93 93
       redirect(lurl('/document/%i'%(loNewNode.parent_id)))
94 94
 
95 95
     @expose()
96
-    def create_comment(self, parent_id=None, data_label='', data_content='', **kw):
96
+    def create_comment(self, parent_id=None, data_label='', data_content='', is_shared='', **kw):
97 97
       loCurrentUser   = pld.PODStaticController.getCurrentUser()
98 98
       loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
99
-      
99
+
100 100
       loNewNode = loApiController.createNode()
101 101
       loNewNode.parent_id     = int(parent_id)
102 102
       loNewNode.node_type     = pmd.PBNodeType.Comment
103 103
       loNewNode.data_label    = data_label
104 104
       loNewNode.data_content  = data_content
105
+      if is_shared=='on':
106
+        loNewNode.is_shared = True
105 107
 
106 108
       pm.DBSession.flush()
107 109
       redirect(lurl('/document/%i'%(loNewNode.parent_id)))
@@ -193,16 +195,25 @@ class PODApiController(BaseController):
193 195
       redirect(lurl('/document/%s'%(node_id)))
194 196
 
195 197
     @expose()
196
-    def create_document(self, parent_id=None):
198
+    def create_document(self, parent_id=None, data_label='', data_content=''):
197 199
       loCurrentUser   = pld.PODStaticController.getCurrentUser()
198 200
       loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
199
-      
201
+
202
+      lsNodeName = 'Document with no name...'
203
+      if int(parent_id)!=0:
204
+        loParent = loApiController.getNode(parent_id)
205
+        lsNodeName = 'Subdocument of (%s)' % loParent.data_label
206
+
200 207
       loNewNode = loApiController.createDummyNode()
201
-      loNewNode.data_label   = 'New document'
208
+      loNewNode.data_label   = lsNodeName
202 209
       loNewNode.data_content = 'insert content...'
203
-      if int(parent_id)==0:
204
-        loNewNode.parent_id = None
205
-      else:
210
+
211
+      if data_label!='':
212
+        loNewNode.data_label = data_label
213
+      if data_content!='':
214
+        loNewNode.data_content = data_content
215
+
216
+      if int(parent_id)!=0:
206 217
         loNewNode.parent_id = parent_id
207 218
 
208 219
       pm.DBSession.flush()
@@ -258,3 +269,14 @@ class PODApiController(BaseController):
258 269
       flash(_('Documents re-indexed'), 'info')
259 270
       redirect(lurl('/document/%s'%(back_to_node_id)))
260 271
 
272
+    @expose()
273
+    def toggle_share_status(self, node_id):
274
+      loCurrentUser   = pld.PODStaticController.getCurrentUser()
275
+      loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
276
+      loNode = loApiController.getNode(node_id)
277
+      if loNode.owner_id==loCurrentUser.user_id:
278
+        loNode.is_shared = not loNode.is_shared
279
+      # FIXME - DA. - 2014-05-06
280
+      # - if root node, then exception
281
+      # - this redirect is done in order to be adapted to comment share status toggle
282
+      redirect(lurl('/document/%s#tab-comments'%(loNode._oParent.node_id)))

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

@@ -119,12 +119,12 @@ class RootController(BaseController):
119 119
         loCurrentUser   = pld.PODStaticController.getCurrentUser()
120 120
         loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
121 121
 
122
-        # loRootNodeList   = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.parent_id==None).order_by(pbmd.PBNode.node_order).all()
123 122
         loRootNodeList = loApiController.buildTreeListForMenu(pbmd.PBNodeStatus.getVisibleIdsList())
124 123
         liNodeId         = int(node)
125 124
 
126 125
         loCurrentNode    = None
127 126
         loNodeStatusList = None
127
+
128 128
         try:
129 129
           loNodeStatusList = pbmd.PBNodeStatus.getChoosableList()
130 130
           loCurrentNode    = loApiController.getNode(liNodeId)
@@ -133,19 +133,6 @@ class RootController(BaseController):
133 133
 
134 134
         # FIXME - D.A - 2013-11-07 - Currently, the code build a new item if no item found for current user
135 135
         # the correct behavior should be to redirect to setup page
136
-        if loCurrentNode is not None and "%s"%loCurrentNode.node_id!=node:
137
-          redirect(tg.url('/document/%i'%loCurrentNode.node_id))
138
-
139
-        if loCurrentNode is None:
140
-          loCurrentNode = loApiController.getNode(0) # try to get an item
141
-          if loCurrentNode is not None:
142
-            flash(_('Document not found. Randomly showing item #%i')%(loCurrentNode.node_id), 'warning')
143
-            redirect(tg.url('/document/%i'%loCurrentNode.node_id))
144
-          else:
145
-            flash(_('Your first document has been automatically created'), 'info')
146
-            loCurrentNode = loApiController.createDummyNode()
147
-            pm.DBSession.flush()
148
-            redirect(tg.url('/document/%i'%loCurrentNode.node_id))
149 136
 
150 137
         return dict(
151 138
             root_node_list=loRootNodeList,

+ 4 - 5
pboard/pboard/lib/dbapi.py View File

@@ -70,17 +70,16 @@ class PODUserFilteredApiController(object):
70 70
 
71 71
   def createDummyNode(self):
72 72
     loNewNode = self.createNode()
73
-    loNewNode.data_label   = 'New document'
74
-    loNewNode.data_content = 'insert content...'
73
+    loNewNode.data_label   = ''
74
+    loNewNode.data_content = ''
75 75
     return loNewNode
76 76
 
77 77
 
78 78
   def getNode(self, liNodeId):
79 79
     liOwnerIdList = self._getUserIdListForFiltering()
80
-    if liNodeId==0:
81
-      return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).first()
82
-    else:
80
+    if liNodeId!=0:
83 81
       return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.node_id==liNodeId).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).one()
82
+    return None
84 83
 
85 84
 
86 85
   def getLastModifiedNodes(self, piMaxNodeNb):

+ 41 - 0
pboard/pboard/lib/helpers.py View File

@@ -39,3 +39,44 @@ def getExplanationAboutStatus(psStatusId, psCurrentStatusId):
39 39
       return _("Close the item if you want not to see it anymore. The data won't be deleted")
40 40
     if psStatusId=='deleted':
41 41
       return _("This status tells that the item has been deleted.")
42
+
43
+class ID(object):
44
+  """ Helper class that will manage html items ids that need to be shared"""
45
+
46
+  @classmethod
47
+  def AddDocumentModalForm(cls, poNode=None):
48
+    if poNode:
49
+      return 'add-document-modal-form-%d'%poNode.node_id
50
+    else:
51
+      return 'add-document-modal-form'
52
+
53
+  @classmethod
54
+  def AddContactModalForm(cls, poNode=None):
55
+    if poNode:
56
+      return 'add-contact-modal-form-%d'%poNode.node_id
57
+    else:
58
+      return 'add-contact-modal-form'
59
+
60
+  @classmethod
61
+  def AddFileModalForm(cls, poNode=None):
62
+    if poNode:
63
+      return 'add-file-modal-form-%d'%poNode.node_id
64
+    else:
65
+      return 'add-file-modal-form'
66
+
67
+  @classmethod
68
+  def AddEventModalForm(cls, poNode=None):
69
+    if poNode:
70
+      return 'add-event-modal-form-%d'%poNode.node_id
71
+    else:
72
+      return 'add-event-modal-form'
73
+    ## Original id is 'current-document-add-event-form'
74
+
75
+  @classmethod
76
+  def AddCommentInlineForm(cls):
77
+    return 'current-document-add-comment-form'
78
+
79
+class ICON(object):
80
+  Shared = '<i class="fa fa-group"></i>'
81
+  Private = '<i class="fa fa-key"></i>'
82
+

+ 45 - 36
pboard/pboard/model/data.py View File

@@ -9,6 +9,7 @@ from hashlib import sha256
9 9
 import bs4
10 10
 from sqlalchemy import Table, ForeignKey, Column, Sequence
11 11
 from sqlalchemy.types import Unicode, Integer, DateTime, Text, LargeBinary
12
+import sqlalchemy.types as sqlat
12 13
 from sqlalchemy.orm import relation, synonym, relationship
13 14
 from sqlalchemy.orm import backref
14 15
 import sqlalchemy.orm as sqlao
@@ -18,6 +19,7 @@ from tg.i18n import ugettext as _, lazy_ugettext as l_
18 19
 
19 20
 import tg
20 21
 from pboard.model import DeclarativeBase, metadata, DBSession
22
+from pboard.model import auth as pma
21 23
 
22 24
 # This is the association table for the many-to-many relationship between
23 25
 # groups and permissions.
@@ -143,8 +145,7 @@ class PBNodeStatus(object):
143 145
       PBNodeStatus.StatusList['closed'],
144 146
       PBNodeStatus.StatusList['deleted']
145 147
     ]
146
-    
147
-    PBNodeStatus.StatusList.values()
148
+
148 149
     
149 150
   @classmethod
150 151
   def getStatusItem(cls, psStatusId):
@@ -182,6 +183,7 @@ class PBNode(DeclarativeBase):
182 183
     return len(self._lStaticChildList)
183 184
 
184 185
   __tablename__ = 'pod_nodes'
186
+
185 187
   node_id          = Column(Integer, Sequence('pod_nodes__node_id__sequence'), primary_key=True)
186 188
   parent_id        = Column(Integer, ForeignKey('pod_nodes.node_id'), nullable=True, default=None)
187 189
   node_depth       = Column(Integer, unique=False, nullable=False, default=0)
@@ -195,6 +197,21 @@ class PBNode(DeclarativeBase):
195 197
   created_at = Column(DateTime, unique=False, nullable=False)
196 198
   updated_at = Column(DateTime, unique=False, nullable=False)
197 199
 
200
+  """
201
+    if 1, the document is available for other users logged into pod.
202
+    default is 0 (private document)
203
+  """
204
+  is_shared = Column(sqlat.Boolean, unique=False, nullable=False, default=False)
205
+  """
206
+    if 1, the document is available through a public - but obfuscated, url
207
+    default is 0 (document not publicly available)
208
+  """
209
+  is_public = Column(sqlat.Boolean, unique=False, nullable=False, default=False)
210
+  """
211
+    here is the hash allowing to get the document publicly
212
+  """
213
+  public_url_key = Column(Unicode(1024), unique=False, nullable=False, default='')
214
+
198 215
   data_label   = Column(Unicode(1024), unique=False, nullable=False, default='')
199 216
   data_content = Column(Text(),        unique=False, nullable=False, default='')
200 217
   
@@ -208,6 +225,7 @@ class PBNode(DeclarativeBase):
208 225
 
209 226
   _oParent = relationship('PBNode', remote_side=[node_id], backref='_lAllChildren')
210 227
   rights = relation('Rights', secondary=group_node_table, backref='nodes')
228
+  _oOwner = relationship('User', remote_side=[pma.User.user_id], backref='_lAllNodes')
211 229
 
212 230
   def getChildrenOfType(self, plNodeTypeList, poKeySortingMethod=None, pbDoReverseSorting=False):
213 231
     """return all children nodes of type 'data' or 'node' or 'folder'"""
@@ -231,9 +249,15 @@ class PBNode(DeclarativeBase):
231 249
   def getChildNb(self):
232 250
     return self.getChildNbOfType([PBNodeType.Data])
233 251
 
234
-  def getChildren(self):
252
+  def getChildren(self, pbIncludeDeleted=False):
235 253
     """return all children nodes of type 'data' or 'node' or 'folder'"""
236
-    return self.getChildrenOfType([PBNodeType.Node, PBNodeType.Folder, PBNodeType.Data])
254
+    # return self.getChildrenOfType([PBNodeType.Node, PBNodeType.Folder, PBNodeType.Data])
255
+    items = self.getChildrenOfType([PBNodeType.Node, PBNodeType.Folder, PBNodeType.Data])
256
+    items2 = list()
257
+    for item in items:
258
+      if pbIncludeDeleted==True or item.node_status!='deleted':
259
+        items2.append(item)
260
+    return items2
237 261
 
238 262
   def getContacts(self):
239 263
     """return all children nodes of type 'data' or 'node' or 'folder'"""
@@ -344,13 +368,23 @@ class PBNode(DeclarativeBase):
344 368
           break
345 369
       return PBNodeStatus.getStatusItem(lsRealStatusId)
346 370
 
347
-  def getTruncatedLabel(self, piCharNb):
348
-    lsTruncatedLabel = ''
371
+  def getTruncatedLabel(self, piCharNb: int):
372
+    """
373
+    return a truncated version of the data_label property.
374
+    if piCharNb is not > 0, then the full data_label is returned
375
+    note: if the node is a file and the data_label is empty, the file name is returned
376
+    """
377
+    lsTruncatedLabel = self.data_label
378
+
379
+    # 2014-05-06 - D.A. - HACK
380
+    # if the node is a file and label empty, then use the filename as data_label
381
+    if self.node_type==PBNodeType.File and lsTruncatedLabel=='':
382
+      lsTruncatedLabel = self.data_file_name
383
+
349 384
     liMaxLength = int(piCharNb)
350
-    if len(self.data_label)>liMaxLength:
351
-      lsTruncatedLabel = self.data_label[0:liMaxLength-1]+'…'
352
-    else:
353
-      lsTruncatedLabel = self.data_label
385
+    if liMaxLength>0 and len(lsTruncatedLabel)>liMaxLength:
386
+      lsTruncatedLabel = lsTruncatedLabel[0:liMaxLength-1]+'…'
387
+
354 388
     return lsTruncatedLabel
355 389
 
356 390
   def getTruncatedContentAsText(self, piCharNb):
@@ -389,31 +423,6 @@ class PBNode(DeclarativeBase):
389 423
 
390 424
 
391 425
 
392
-"""from sqlalchemy.orm import mapper
393
-mapper(
394
-  PBNode,
395
-  pod_node_table,
396
-  properties = {'_lAllChildren' : relationship(PBNode, backref=backref('_oParent', remote_side=PBNode.parent_id)) }
397
-)"""
398
-
399
-
400
-
401
-"""    children = relationship('TreeNode',
402
-
403
-                        # cascade deletions
404
-                        cascade="all",
405
-
406
-                        # many to one + adjacency list - remote_side
407
-                        # is required to reference the 'remote' 
408
-                        # column in the join condition.
409
-                        backref=backref("parent", remote_side='TreeNode.id'),
410
-
411
-                        # children will be represented as a dictionary
412
-                        # on the "name" attribute.
413
-                        collection_class=attribute_mapped_collection('name'),
414
-                    ) 
415
-"""
416
-
417 426
 # This is the association table for the many-to-many relationship between groups and nodes
418 427
 group_node_table = Table('pod_group_node', metadata,
419 428
         Column('group_id', Integer, ForeignKey('pod_group.group_id',
@@ -421,4 +430,4 @@ group_node_table = Table('pod_group_node', metadata,
421 430
         Column('node_id', Integer, ForeignKey('pod_nodes.node_id',
422 431
             onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
423 432
         Column('rights', Integer)
424
-)
433
+)

+ 6 - 4
pboard/pboard/public/css/style.css View File

@@ -127,10 +127,10 @@ body { padding-top: 60px; }
127 127
 
128 128
   padding: 0.5em 0.5em 0.5em 0.5em;
129 129
   
130
-  filter: alpha(opacity=90); /* internet explorer */
131
-  -khtml-opacity: 0.9;      /* khtml, old safari */
132
-  -moz-opacity: 0.9;       /* mozilla, netscape */
133
-  opacity: 0.9;           /* fx, safari, opera */
130
+  filter: alpha(opacity=95); /* internet explorer */
131
+  -khtml-opacity: 0.95;      /* khtml, old safari */
132
+  -moz-opacity: 0.95;       /* mozilla, netscape */
133
+  opacity: 0.95;           /* fx, safari, opera */
134 134
 }
135 135
 
136 136
 .full-size-overlay-inner {
@@ -190,3 +190,5 @@ div.search-result-item > h5 {
190 190
   margin-bottom: 0;
191 191
 }
192 192
 
193
+ul.nav-tabs li.active > a { background-color: #efefef; }
194
+div.tab-pane > h4 { background-color: #efefef; padding: 0.5em; margin: 0 0 0.5em 0;}

+ 12 - 148
pboard/pboard/public/javascript/pod.js View File

@@ -1,3 +1,13 @@
1
+  function generateStringId(charNb = 32, allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") {
2
+    var text = "";
3
+
4
+    for( var i=0; i < charNb; i++ ) {
5
+      text += allowedChars.charAt(Math.floor(Math.random() * allowedChars.length));
6
+    }
7
+
8
+    return text;
9
+  }
10
+
1 11
   function toggleFullScreen(outerWidgetId, innerWidgetId) {
2 12
     if($(outerWidgetId).hasClass('full-size-overlay')) {
3 13
       // Toggle from fullscreen to "normal"
@@ -31,6 +41,7 @@
31 41
       $('#voiceBtn').hide();
32 42
     }
33 43
   };
44
+  
34 45
   function showErrorAlert (reason, detail) {
35 46
     var msg='';
36 47
     if (reason==='unsupported-file-type') { msg = "Unsupported format " +detail; }
@@ -43,53 +54,6 @@
43 54
 
44 55
   $(document).ready(function() {
45 56
 
46
-    $('#create_document_save_button').on('click', function(e){
47
-      // We don't want this to act as a link so cancel the link action
48
-      e.preventDefault();
49
-
50
-      // Find form and submit it
51
-      $('#create_document_form').submit();
52
-    });
53
-
54
-// ## FIXME                $('#current_node_textarea').wysihtml5({
55
-// ## FIXME                  "font-styles": true, //Font styling, e.g. h1, h2, etc. Default true
56
-// ## FIXME                  "emphasis": true, //Italics, bold, etc. Default true
57
-// ## FIXME                  "lists": true, //(Un)ordered lists, e.g. Bullets, Numbers. Default true
58
-// ## FIXME                  "html": true, //Button which allows you to edit the generated HTML. Default false
59
-// ## FIXME                  "link": true, //Button to insert a link. Default true
60
-// ## FIXME                  "image": true, //Button to insert an image. Default true,
61
-// ## FIXME                  // "color": true //Button to change color of font  
62
-// ## FIXME                });
63
-// ## FIXME                $('#current_node_textarea').css('margin-bottom', '0');
64
-// ## FIXME                $('#current_node_textarea').css("min-height", "12em");
65
-// ## FIXME                $('#current_node_textarea').addClass("span5");
66
-
67
-// ###################
68
-// ##
69
-// ## HERE
70
-// ##
71
-// ###################
72
-
73
-// ##
74
-// ## RE-IMPLEMENT THIS SOON !!!
75
-// ##
76
-// ##                /* Edit title form */
77
-// ##                $("#current-document-title-edit-form" ).css("display", "none");
78
-// ##                $("#current-document-title" ).dblclick(function() {
79
-// ##                  $("#current-document-title" ).css("display", "none");
80
-// ##                  $("#current-document-title-edit-form" ).css("display", "block");
81
-// ##                });
82
-// ##                $("#current-document-title-edit-cancel-button" ).click(function() {
83
-// ##                  $("#current-document-title" ).css("display", "block");
84
-// ##                  $("#current-document-title-edit-form" ).css("display", "none");
85
-// ##                });
86
-// ##                $('#current-document-title-save-cancel-button').on('click', function(e){
87
-// ##                  // We don't want this to act as a link so cancel the link action
88
-// ##                  e.preventDefault();
89
-// ##                  $('#current-document-title-edit-form').submit();
90
-// ##                });
91
-
92
-
93 57
     /* EDIT CONTENT FORM */
94 58
     $("#current-document-content-edit-form" ).css("display", "none");
95 59
     $("#current-document-content-edit-button" ).click(function() {
@@ -97,10 +61,7 @@
97 61
       $("#current-document-content-edit-form" ).css("display", "block");
98 62
       $("#current-document-toobar").css("display", "none");
99 63
     });
100
-    $("#current-document-content" ).dblclick(function() {
101
-      $("#current-document-content" ).css("display", "none");
102
-      $("#current-document-content-edit-form" ).css("display", "block");
103
-    });
64
+
104 65
     $("#current-document-content-edit-cancel-button, #current-document-content-edit-cancel-button-top" ).click(function() {
105 66
       $("#current-document-content" ).css("display", "block");
106 67
       $("#current-document-content-edit-form" ).css("display", "none");
@@ -114,90 +75,6 @@
114 75
       $('#current-document-content-edit-form').submit();
115 76
     });
116 77
 
117
-    /* ADD EVENT => FORM */
118
-    $('#add_event_data_content_textarea').wysiwyg();
119
-    $('#add_event_data_content_textarea').css('margin-bottom', '0');
120
-    $('#add_event_data_content_textarea').css("height", "4em");
121
-    $('#add_event_data_content_textarea').addClass("span3");
122
-    /* ADD EVENT => SHOW/HIDE/SUBMIT BUTTONS */
123
-    $("#current-document-add-event-button" ).click(function() {
124
-      $("#current-document-add-event-form" ).css("display", "block");
125
-      $("#current-document-add-event-button" ).css("display", "none");
126
-    });
127
-    $('#current-document-add-event-cancel-button').on('click', function(e){
128
-      $("#current-document-add-event-form" ).css("display", "none");
129
-      $("#current-document-add-event-button" ).css("display", "block");
130
-    });
131
-    $('#current-document-add-event-save-button').on('click', function(e){
132
-      e.preventDefault(); // We don't want this to act as a link so cancel the link action
133
-      $('#add_event_data_content_textarea_wysiwyg').cleanHtml();
134
-      $('#add_event_data_content_textarea').val($('#add_event_data_content_textarea_wysiwyg').html());
135
-      $('#current-document-add-event-form').submit();
136
-    });
137
-
138
-    /* ADD CONTACT => FORM */
139
-    $('#add_contact_data_content_textarea').wysiwyg();
140
-    $('#add_contact_data_content_textarea').css('margin-bottom', '0');
141
-    $('#add_contact_data_content_textarea').css("height", "4em");
142
-    $('#add_contact_data_content_textarea').addClass("span3");
143
-    /* ADD CONTACT => SHOW/HIDE/SUBMIT BUTTONS */
144
-    $("#current-document-add-contact-button" ).click(function() {
145
-      $("#current-document-add-contact-form" ).css("display", "block");
146
-      $("#current-document-add-contact-button" ).css("display", "none");
147
-    });
148
-    $('#current-document-add-contact-cancel-button').on('click', function(e){
149
-      $("#current-document-add-contact-form" ).css("display", "none");
150
-      $("#current-document-add-contact-button" ).css("display", "block");
151
-    });
152
-    $('#current-document-add-contact-save-button').on('click', function(e){
153
-      e.preventDefault(); // We don't want this to act as a link so cancel the link action
154
-      $('#add_contact_data_content_textarea_wysiwyg').cleanHtml();
155
-      $('#add_contact_data_content_textarea').val($('#add_contact_data_content_textarea_wysiwyg').html());
156
-      $('#current-document-add-contact-form').submit();
157
-    });
158
-
159
-
160
-    /* ADD COMMENT => FORM */
161
-    $('#add_comment_data_content_textarea').wysiwyg();
162
-    $('#add_comment_data_content_textarea').css('margin-bottom', '0');
163
-    $('#add_comment_data_content_textarea').css("height", "4em");
164
-    $('#add_comment_data_content_textarea').addClass("span3");
165
-    /* ADD COMMENT => SHOW/HIDE/SUBMIT BUTTONS */
166
-    $("#current-document-add-comment-button" ).click(function() {
167
-      $("#current-document-add-comment-form" ).css("display", "block");
168
-      $("#current-document-add-comment-button" ).css("display", "none");
169
-    });
170
-    $('#current-document-add-comment-cancel-button').on('click', function(e){
171
-      $("#current-document-add-comment-form" ).css("display", "none");
172
-      $("#current-document-add-comment-button" ).css("display", "block");
173
-    });
174
-    $('#current-document-add-comment-save-button').on('click', function(e){
175
-      e.preventDefault(); // We don't want this to act as a link so cancel the link action
176
-      $('#add_comment_data_content_textarea_wysiwyg').cleanHtml();
177
-      $('#add_comment_data_content_textarea').val($('#add_comment_data_content_textarea_wysiwyg').html());
178
-      $('#current-document-add-comment-form').submit();
179
-    });
180
-
181
-    /* ADD FILE => FORM */
182
-    $('#add_file_data_content_textarea').wysiwyg();
183
-    $('#add_file_data_content_textarea').css('margin-bottom', '0');
184
-    $('#add_file_data_content_textarea').css("height", "4em");
185
-    $('#add_file_data_content_textarea').addClass("span3");
186
-    /* ADD FILE => SHOW/HIDE/SUBMIT BUTTONS */
187
-    $("#current-document-add-file-button" ).click(function() {
188
-      $("#current-document-add-file-form" ).css("display", "block");
189
-      $("#current-document-add-file-button" ).css("display", "none");
190
-    });
191
-    $('#current-document-add-file-cancel-button').on('click', function(e){
192
-      $("#current-document-add-file-form" ).css("display", "none");
193
-      $("#current-document-add-file-button" ).css("display", "block");
194
-    });
195
-    $('#current-document-add-file-save-button').on('click', function(e){
196
-      e.preventDefault(); // We don't want this to act as a link so cancel the link action
197
-      $('#add_file_data_content_textarea_wysiwyg').cleanHtml();
198
-      $('#add_file_data_content_textarea').val($('#add_file_data_content_textarea_wysiwyg').html());
199
-      $('#current-document-add-file-form').submit();
200
-    });
201 78
 
202 79
     $(function() {
203 80
       $('.datetime-picker-input-div').datetimepicker({
@@ -206,19 +83,6 @@
206 83
       });
207 84
     });
208 85
 
209
-/*
210
-    // Allow to go directly to required tab on load
211
-    // Javascript to enable link to tab
212
-    var url = document.location.toString();
213
-    if (url.match('#')) {
214
-      $('.nav-tabs a[href=#'+url.split('#')[1]+']').tab('show') ;
215
-    } 
216
-
217
-    // Change hash for page-reload
218
-    $('.nav-tabs a').on('shown', function (e) {
219
-      window.location.hash = e.target.hash;
220
-    })
221
-*/
222 86
     // #################################
223 87
     // ##
224 88
     // ## The following JS code allow t

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

@@ -0,0 +1,483 @@
1
+<%inherit file="local:templates.master"/>
2
+<%namespace name="POD" file="pboard.templates.pod"/>
3
+<%namespace name="DOC" file="pboard.templates.document-widgets"/>
4
+
5
+<%def name="AccessManagementTab(poNode)">
6
+  ######
7
+  ##
8
+  ## THIS WIDGET IS INTENDED TO BE USED ONE TIME ONLY IN A PAGE
9
+  ##
10
+  <h4>${_('Share options')}</h4> 
11
+  <p>
12
+    This document is
13
+    % if poNode.is_shared==False:
14
+      <span class="label label-info">
15
+        <i class="fa fa-user"></i>
16
+        ${_('private')}
17
+      </span>
18
+    % else:
19
+      <span class="label label-info">
20
+        <i class="fa fa-group"></i>
21
+        ${_('collaborative')}
22
+      </span>
23
+    % endif
24
+  </p>
25
+  <p>
26
+    % if poNode.is_shared==True or poNode.is_shared==False:
27
+      ${_('People working on it are:')}
28
+######
29
+##
30
+## FIXME - SHOW LIST OF GROUPS ALLOWED TO WORK ON THE DOCUMENT
31
+##
32
+    <table class="table table-striped table-hover table-condensed">
33
+      <thead>
34
+        <tr>
35
+          <th><i class="fa fa-group"></i> ${_('Groups')}</th>
36
+          <th></th>
37
+        </tr>
38
+      </thead>
39
+      <tr>
40
+        <td>Recherche et Développement</td>
41
+        <td>
42
+          <span class="label label-success" title="${_('Read access')}">R</span>
43
+          <span class="label label-warning" title="${_('Write access')}">W</span>
44
+        </td>
45
+      </tr>
46
+      <thead>
47
+        <tr>
48
+          <th><i class="fa fa-user"></i> ${_('Users')}</th>
49
+          <th></th>
50
+        </tr>
51
+      </thead>
52
+      <tr>
53
+        <td>Damien Accorsi</td>
54
+        <td>
55
+          <span class="label label-success">R</span>
56
+        </td>
57
+      </tr>
58
+      <tr>
59
+        <td>Sylvain Ferot</td>
60
+        <td>
61
+          <span class="label label-success">R</span>
62
+          <span class="label label-warning">W</span>
63
+        </td>
64
+      </tr>
65
+    </table>
66
+    
67
+    % endif
68
+  <p>
69
+
70
+######
71
+##
72
+## 2014-05-06 - D.A. We do not share documents on internet yet.
73
+##
74
+##  <p>
75
+##    % if poNode.is_public==False:
76
+##      ${_('This document is not shared on internet')|n}
77
+##    % else:
78
+##      ${_('This document is <span class="label label-warning"><i class="fa fa-globe"></i><span>shared</span></span> on internet')|n}.
79
+##      ${_('The associated url is:')} <a href="FIXME">${poNode.public_url_key}</a>
80
+##    % endif
81
+##  </p>
82
+  <!-- Button to trigger modal -->
83
+  <a href="#edit-document-share-properties" role="button" class="btn btn-success" data-toggle="modal">
84
+    <i class="fa fa-edit"></i>
85
+    ${_('Edit share options')}
86
+  </a>
87
+     
88
+  <!-- Modal -->
89
+  <div
90
+    id="edit-document-share-properties"
91
+    class="modal hide"
92
+    tabindex="-1"
93
+    role="dialog"
94
+    aria-labelledby="myModalLabel"
95
+    aria-hidden="true">
96
+    
97
+    <div class="modal-header">
98
+      <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
99
+      <h3 id="myModalLabel">Document sharing options</h3>
100
+    </div>
101
+    <div class="modal-body">
102
+
103
+      <form id='document-share-form' method="GET" action="${tg.url('/api/set_access_management?node_id=%d'%poNode.node_id)}">
104
+        <fieldset>
105
+          <label class="checkbox">
106
+            <input name="is_shared" type="checkbox" id="document-share-selector" ${('', 'checked')[poNode.is_shared]}/>
107
+            ${_('Share document with collaborators.')} <i class="fa fa-group"></i>
108
+          </label>
109
+          <div id="document-share-people-selector">
110
+            <p>
111
+              ${_('Select read and write access for each group or people...')}</p>
112
+            <script>
113
+            function updateRights(psUserId) {
114
+              var ACCESS_NONE = '';
115
+              var ACCESS_READ = 'R';
116
+              var ACCESS_WRITE = 'RW';
117
+              
118
+              var nodeIdForSelectedUser = 'user-'+psUserId+'-value';
119
+              var widget = $('#'+nodeIdForSelectedUser);
120
+              var oldValue = widget.val();
121
+              var newValue = '';
122
+              if(oldValue==ACCESS_NONE) {
123
+                newValue = ACCESS_READ;
124
+                newHtml = '<span class="label label-success">R</span>';
125
+              } else if(oldValue==ACCESS_READ) {
126
+                newValue = ACCESS_WRITE;
127
+                newHtml = '<span class="label label-success">R</span> <span class="label label-warning">W</span>';
128
+              } else if (oldValue==ACCESS_WRITE) {
129
+                newValue = ACCESS_NONE;
130
+                newHtml = '';
131
+              } else {
132
+                newValue = ACCESS_READ;
133
+                newHtml = '<span class="label label-success">R</span>';
134
+              }
135
+              
136
+              widget.val(newValue);
137
+              visibleid = 'user-'+psUserId+'-rights';
138
+              $("#"+visibleid).html(newHtml);
139
+            }
140
+            </script>
141
+            
142
+            <table class="table table-striped table-hover table-condensed">
143
+              <thead>
144
+                <tr>
145
+                  <th></th>
146
+                  <th>${_('Group')}</th>
147
+                  <th>${_('Access')}</th>
148
+                </tr>
149
+              </thead>
150
+    ######
151
+    ##
152
+    ## FIXME - SET A DYNAMIC SELECT LIST HERE
153
+    ##
154
+              % for loCurrentUser in ((3, 'Research and Development'), (4, 'Sylvain Ferot'), (5, 'Damien Accorsi')):
155
+              <tr id='user-${loCurrentUser[0]}-rights-row'>
156
+                <td>
157
+                  <a
158
+                    class="btn btn-mini"
159
+                    onclick="updateRights(${loCurrentUser[0]})"
160
+                  >
161
+                    <i class="fa fa-key"></i>
162
+                  </a>
163
+                </td>
164
+                <td class='pod-highlightable-access-management-cell'>
165
+                  ${loCurrentUser[1]}
166
+                  <input
167
+                    type="hidden"
168
+                    id="user-${loCurrentUser[0]}-value"
169
+                    name="user[${loCurrentUser[0]}]"
170
+                    value=""
171
+                  />
172
+                </td>
173
+                <td id="user-${loCurrentUser[0]}-rights" class="pod-right-cell"></td>
174
+              </tr>
175
+              % endfor
176
+            </table>
177
+          </div>
178
+        </fieldset>
179
+######
180
+##
181
+## 2014-05-06 - D.A. The documents are not yet sharable through internet
182
+##
183
+##        <fieldset>
184
+##          <label class="checkbox">
185
+##            <input name="is_public" type="checkbox" id="document-public-selector" ${('', 'checked')[poNode.is_public]}/>
186
+##            ${_('Internet shared document')}
187
+##            <i class="fa fa-globe"></i>
188
+##          </label>
189
+##          <label id="document-public-key-selector">
190
+##            ${_('Key')}
191
+##            <div class="input-append">
192
+##              <input name="url_public_key" id="document-public-key" type="text">
193
+##              <span id="document-public-key-refresh-button" class="add-on btn" title="${_('Regenerate key')}">
194
+##                <i class="fa fa-refresh"></i>
195
+##              </span>
196
+##            </div>
197
+##            <p><a id='document-public-key-url' href="">http://share.pod.com/document/azefnzeioguneriugnreiugnre</a></p>
198
+##          </label>
199
+
200
+        </fieldset>
201
+####
202
+## Button replaced by modal dialog button
203
+##        <button type="submit" class="btn btn-success">
204
+##          <i class="fa fa-check"></i>
205
+##          ${_('Save')}
206
+##        </button>
207
+      </form>
208
+    </div>
209
+    <div class="modal-footer">
210
+    <button class="btn" data-dismiss="modal" aria-hidden="true">
211
+      <i class="fa fa-ban"></i> ${_('Cancel')}
212
+    </button>
213
+    <button class="btn btn-success" id="document-share-form-submit-button">
214
+      <i class="fa fa-check"></i> ${_('Save changes')}
215
+    </button>
216
+    </div>
217
+    <script>
218
+##
219
+## 2014-05-06 - D.A. - Documents are not yet sharable through internet
220
+##
221
+##        function refreshDocumentPublicKey(psNewPublicKey) {
222
+##          var lsNewUrl = 'http://share.pod.com/document/'+psNewPublicKey;
223
+##          $('#document-public-key').val(psNewPublicKey);
224
+##          $('#document-public-key-url').attr('href', lsNewUrl);
225
+##          $('#document-public-key-url').text(lsNewUrl);
226
+##        }
227
+      
228
+      function toggleDocumentSharePeopleSelector(pbShowIt) {
229
+        if (pbShowIt) {
230
+          $('#document-share-people-selector').show();
231
+          // $('#document-share-people-selector input').removeAttr("disabled");
232
+        } else {
233
+          $('#document-share-people-selector').hide();
234
+          // $('#document-share-people-selector input').prop('disabled', 'disabled');
235
+        }
236
+      }
237
+
238
+##
239
+## 2014-05-06 - D.A. - Documents are not yet sharable through internet
240
+##
241
+##        function toggleDocumentPublicKeyGenerator(pbShowIt) {
242
+##          if (pbShowIt) {
243
+##            $('#document-public-key-selector input').removeAttr("disabled");
244
+##            $('#document-public-key-refresh-button').removeProp('disabled');
245
+##            $('#document-public-key-refresh-button').removeClass('btn-disabled');
246
+##            $('#document-public-key-selector a').show();
247
+##            $('#document-public-key-refresh-button').on("click").click(function () {
248
+##              refreshDocumentPublicKey(generateStringId()); // New random 32-char id
249
+##            });
250
+##            if($('#document-public-key-selector input').val()=='') {
251
+##              refreshDocumentPublicKey(generateStringId());
252
+##            }
253
+##          } else {
254
+##            $('#document-public-key-refresh-button').prop('disabled', true);
255
+##            $('#document-public-key-refresh-button').addClass('btn-disabled');
256
+##            $('#document-public-key-selector input').prop('disabled', 'disabled');
257
+##            $('#document-public-key-refresh-button').off("click");
258
+##            $('#document-public-key-selector a').hide();
259
+##          }
260
+##        }
261
+##
262
+##
263
+##
264
+
265
+      // Callbacks setup
266
+      $('#document-share-selector').change(function () {
267
+        var checkedValue = $('#document-share-selector').prop("checked");
268
+        toggleDocumentSharePeopleSelector(checkedValue);
269
+      });
270
+
271
+##        $('#document-public-selector').change(function () {
272
+##          var checkedValue = $('#document-public-selector').prop("checked");
273
+##          toggleDocumentPublicKeyGenerator(checkedValue);
274
+##        });
275
+
276
+      // Submit access-management modal dialog form
277
+      $('#document-share-form-submit-button').click(function(){
278
+        $('#document-share-form')[0].submit();
279
+      });
280
+
281
+      // Initial setup
282
+      // Activate or disactivate users selector according
283
+      // to current state of the is_shared property
284
+      //
285
+      // FIXME - 2014-05-06 - This is not working (should be done at document.ready time)
286
+      // note: putting this in a document.ready callback does not work.
287
+      //
288
+      $('#document-share-form')[0].reset();
289
+      toggleDocumentSharePeopleSelector($('#document-share-selector').prop("checked"));
290
+##        toggleDocumentPublicKeyGenerator($('#document-public-selector').prop("checked"));  
291
+##        
292
+##        refreshDocumentPublicKey($('#document-public-key').val()); // First init
293
+
294
+    </script>
295
+  </div>
296
+</%def>
297
+
298
+<%def name="FileTabContent(poNode)">
299
+  <h4>${_('Attachments')}</h4>
300
+  
301
+  % if len(poNode.getFiles())<=0:
302
+    <p class="pod-grey">${_("There is currently no attachment.")}<br/></p>
303
+    <p>${POD.OpenModalButton(h.ID.AddFileModalForm(poNode), _(' Attach first file'))}</p>
304
+  % else:
305
+    <p>${POD.OpenModalButton(h.ID.AddFileModalForm(poNode), _(' Attach a file'))}</p>
306
+  % endif
307
+
308
+  <div>
309
+    % if len(poNode.getFiles())>0:
310
+      % for loFile in poNode.getFiles():
311
+        <p style="list-style-type:none; margin-bottom: 0.5em;">
312
+          <i class="fa fa-paperclip"></i>
313
+          <a
314
+            href="${tg.url('/document/%i'%loFile.node_id)}"
315
+            title="${_('View the attachment')}: ${loFile.getTruncatedLabel(-1)}"
316
+          >
317
+            ${loFile.getTruncatedLabel(50)}
318
+          </a>
319
+          <a
320
+            class="pull-right"
321
+            href="${tg.url('/api/get_file_content/%s'%(loFile.node_id))}"
322
+            title="${_('View the attachment')}"
323
+          >
324
+            <i class="fa fa-download"></i>
325
+          </a>
326
+        </p>
327
+      % endfor
328
+    % endif
329
+  </div>
330
+</%def>
331
+
332
+<%def name="SubdocumentContent(poNode)">
333
+  <h4>${_('Sub-documents')}</h4>
334
+  
335
+  % if len(poNode.getChildren())<=0:
336
+    <p class="pod-grey">${_("There is currently no child documents.")}</p>
337
+  % endif
338
+  <p>${POD.OpenModalButton(h.ID.AddDocumentModalForm(poNode), _('Add a document'))}</p>
339
+
340
+  % if len(poNode.getChildren())>0:
341
+    <div>
342
+      % for subnode in poNode.getChildren():
343
+        <p style="list-style-type:none;">
344
+          <i class="fa-fw ${subnode.getIconClass()}"></i>
345
+            <a href="${tg.url('/document/%i'%subnode.node_id)}">
346
+              ${subnode.data_label}
347
+            </a>
348
+        </p>
349
+      % endfor
350
+    </div>
351
+  % endif
352
+</%def>
353
+
354
+<%def name="EventTabContent(poNode)">
355
+  <h4>${_('Calendar')}</h4>
356
+  
357
+  % if len(poNode.getEvents())<=0:
358
+    <p class="pod-grey">${_("The calendar is empty.")}<br/></p>
359
+    <p>${POD.OpenModalButton(h.ID.AddEventModalForm(poNode), _(' Add first event'))}</p>
360
+  % else:
361
+    <p>${POD.OpenModalButton(h.ID.AddEventModalForm(poNode), _(' Add an event'))}</p>
362
+  % endif
363
+
364
+  % if len(poNode.getEvents())>0:
365
+    <table class="table table-striped table-hover table-condensed">
366
+      <thead>
367
+        <tr>
368
+          <th>Date</th>
369
+          <th>Time</th>
370
+          <th>
371
+            Event
372
+          </th>
373
+          <th>
374
+            <a href="" title="Add an event"><i class="icon-g-plus"></i></a>
375
+          </th>
376
+        </tr>
377
+      </thead>
378
+      % for event in poNode.getEvents():
379
+        <tr class="item-with-data-popoverable" data-content="${event.data_content}" rel="popover" data-placement="left" data-trigger="hover">
380
+          <td>${event.getFormattedDate(event.data_datetime)}</td>
381
+          <td>${event.getFormattedTime(event.data_datetime)}</td>
382
+          <td>${event.data_label}</td>
383
+        </tr>
384
+  ## FIXME                    <script>
385
+  ##                      $('.item-with-data-popoverable').popover({ html: true});
386
+  ##                    </script>
387
+
388
+      % endfor
389
+    </table>
390
+  % endif
391
+</%def>
392
+
393
+<%def name="ContactTabContent(poNode)">
394
+  <h4>${_('Address book')}</h4> 
395
+  % if len(poNode.getContacts())<=0:
396
+    <p class="pod-grey">${_("The address book is empty.")}<br/></p>
397
+    <p>${POD.OpenModalButton(h.ID.AddContactModalForm(poNode), _('Add first contact'))}</p>
398
+  % else:
399
+    <p>${POD.OpenModalButton(h.ID.AddContactModalForm(poNode), _('Add a contact'))}</p>
400
+  % endif
401
+
402
+  <!-- LIST OF CONTACT NODES -->
403
+  % for contact in poNode.getContacts():
404
+    <div class="well">
405
+      <legend class="text-info">
406
+        ${contact.data_label}
407
+        ## TODO - 2013-11-20 - Use the right form in order to update meta-data
408
+        <a class="pull-right" href="${tg.url('/document/%i'%contact.node_id)}"><i class="fa fa-edit"></i></a>
409
+      </legend>
410
+      
411
+      <div>
412
+        ## FIXME - D.A. - 2013-11-15 - Implement localisation stuff <a style='float: right;' href="" title='${_('Search on google maps')}'><i class='icon-g-google-maps'></i></a>
413
+        ${contact.data_content|n}
414
+      </div>
415
+    </div>
416
+  % endfor
417
+</%def>
418
+
419
+<%def name="CommentTabContent(poNode)">
420
+  <h4>${_('Comment thread')}</h4>
421
+  
422
+  % if len(poNode.getComments())<=0:
423
+    <p class="pod-grey">${_("The comment thread is empty.")}<br/></p>
424
+  % endif
425
+
426
+  % if len(poNode.getComments())>0:
427
+    % if len(poNode.getComments())>5:
428
+      ##
429
+      ## We show a "direct down" button in case the page is too long
430
+      ##
431
+      <p>${POD.OpenLinkButton(h.ID.AddCommentInlineForm(), _('Add a comment'))}</p>
432
+    % endif
433
+    <div>
434
+      % for comment in poNode.getComments():
435
+        <p>
436
+          <a href="${tg.url('/api/toggle_share_status', dict(node_id=comment.node_id))}">
437
+            % if comment.is_shared:
438
+              <span class="label label-warning" title="${_('Shared comment. Click to make private.')}">${h.ICON.Shared|n}</span>
439
+            % else:
440
+              <span class="label label-info" title="${_('Private comment. Click to share.')}">${h.ICON.Private|n}</span>
441
+            % endif
442
+          </a>
443
+          <strong>${comment._oOwner.display_name}</strong>
444
+          <i class="pull-right">
445
+            The ${comment.getFormattedDate(comment.updated_at)} 
446
+            at ${comment.getFormattedTime(comment.updated_at)}
447
+          </i>
448
+          <br/>
449
+          ${comment.data_content|n}
450
+          <hr style="border-top: 1px dotted #ccc; margin: 0;"/>
451
+        </p>
452
+      % endfor
453
+    </div>
454
+  % endif
455
+
456
+  <form class="form" id="${h.ID.AddCommentInlineForm()}" action="${tg.url('/api/create_comment')}" method="POST">
457
+    <input type="hidden" name='parent_id' value='${poNode.node_id}'/>
458
+    <input type="hidden" name='data_label' value=""/>
459
+    <input type="hidden" id="add_comment_data_content_textarea" name='data_content' />
460
+    <label>
461
+      ${_('Write your comment below:')}
462
+      ${POD.RichTextEditor('add_comment_data_content_textarea_wysiwyg', '', 'boldanditalic')}
463
+    </label>
464
+    <label>
465
+      <input type="checkbox" name='is_shared'/> ${_('Share this comment')}
466
+    </label>
467
+    <span class="pull-right">
468
+      % if len(poNode.getComments())<=0:
469
+        ${POD.SaveButton('current-document-add-comment-save-button', True, _('Add first comment'))}
470
+      % else:
471
+        ${POD.SaveButton('current-document-add-comment-save-button', True, _('Comment'))}
472
+      % endif
473
+    </span>
474
+  </form>
475
+  <script>
476
+      $('#current-document-add-comment-save-button').on('click', function(e){
477
+      e.preventDefault(); // We don't want this to act as a link so cancel the link action
478
+      $('#add_comment_data_content_textarea_wysiwyg').cleanHtml();
479
+      $('#add_comment_data_content_textarea').val($('#add_comment_data_content_textarea_wysiwyg').html());
480
+      $('#current-document-add-comment-form').submit();
481
+    });
482
+  </script>
483
+</%def>

+ 387 - 42
pboard/pboard/templates/document-widgets.mak View File

@@ -4,7 +4,7 @@
4 4
 <%def name="node_treeview_for_set_parent_menu(node_id, node_list, indentation=-1)">
5 5
   % if indentation==-1:
6 6
     <li>
7
-      <a href="${tg.url('/api/set_parent_node?node_id=%i&new_parent_id=0'%(current_node.node_id))}">
7
+      <a href="${tg.url('/api/set_parent_node', dict(node_id=node_id, new_parent_id=0))}">
8 8
         <i class="fa fa-file-text-o"></i> ${_('Home')}
9 9
       </a>
10 10
       ${node_treeview_for_set_parent_menu(node_id, node_list, 0)}
@@ -14,7 +14,7 @@
14 14
       <ul style="list-style: none;">
15 15
       % for new_parent_node in node_list:
16 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)}
17
+          <a href="${tg.url('/api/set_parent_node', dict(node_id=node_id, new_parent_id=new_parent_node.node_id))}"><i class="fa fa-file-text-o"></i> ${new_parent_node.getTruncatedLabel(40-indentation*2)}
18 18
           </a>
19 19
           ${node_treeview_for_set_parent_menu(node_id, new_parent_node.getStaticChildList(), indentation+1)}
20 20
         </li>
@@ -24,12 +24,26 @@
24 24
   % endif
25 25
 </%def>
26 26
 
27
+<%def name="ToolbarMenuItemModal(psTargetModalId, psIconClasses, psMenuLabel)">
28
+  <li><a href="#${psTargetModalId}" role="button" data-toggle="modal"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
29
+</%def>
30
+
31
+<%def name="ToolbarMenuItemInline(psTargetId, psIconClasses, psMenuLabel)">
32
+  <li><a href="#${psTargetId}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
33
+</%def>
34
+<%def name="ToolbarMenuItemLink(psTargetUrl, psIconClasses, psMenuLabel, psLinkCss='', psLinkTitle='')">
35
+  % if psTargetUrl=='#':
36
+    <li class="disabled"><a href="${psTargetUrl}" class="${psLinkCss}" title="${psLinkTitle}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
37
+  % else:
38
+    <li><a href="${psTargetUrl}" class="${psLinkCss}" title="${psLinkTitle}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
39
+  % endif
40
+</%def>
41
+
42
+        
27 43
 <%def name="Toolbar(poNode, plNodeStatusList, plRootNodes, psDivId)">
28 44
   <div id="${psDivId}">
29 45
     <div class="btn-group">
30
-  % if poNode.parent_id!=None and poNode.parent_id!=0:
31 46
       ${POD.EditButton('current-document-content-edit-button', True)}
32
-  % endif
33 47
       <button class="btn btn-small"  data-toggle="dropdown" href="#"> 
34 48
         <i class="fa  fa-signal"></i>
35 49
         ${_("Change status")}
@@ -38,19 +52,22 @@
38 52
         <span class="caret"></span>
39 53
       </a>
40 54
       <ul class="dropdown-menu">
55
+        <li>
56
+          <div class="pod-grey strong" ><strong><i class="fa fa-magic"></i> ${_('Current status is...')}</strong><br/></div>
57
+        </li>
41 58
       % for node_status in plNodeStatusList:
42 59
         % 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>
60
+          ${ToolbarMenuItemLink('#', node_status.icon_id, node_status.label, 'disabled '+node_status.css, h.getExplanationAboutStatus(node_status.status_id, current_node.getStatus().status_id))}
61
+        % endif
62
+      % endfor
63
+        <li class="divider" role="presentation"></li>
64
+        <li>
65
+          <div class=" strong" ><strong><i class="fa fa-magic"></i> ${_('Change to...')}</strong><br/></div>
66
+          <div class="pod-grey"><i>${_('change the status to...')}</i></div>
53 67
         </li>
68
+      % for node_status in plNodeStatusList:
69
+        % if node_status.status_id!=poNode.getStatus().status_id:
70
+          ${ToolbarMenuItemLink(tg.url('/api/edit_status', dict(node_id=current_node.node_id, node_status=node_status.status_id)), node_status.icon_id, node_status.label, node_status.css, h.getExplanationAboutStatus(node_status.status_id, current_node.getStatus().status_id))}
54 71
         % endif
55 72
       % endfor
56 73
       </ul>
@@ -63,15 +80,18 @@
63 80
       <ul class="dropdown-menu">
64 81
       
65 82
         <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>
83
+          <div class="btn-success strong" ><strong><i class="fa fa-magic"></i> ${_('Add New...')}</strong><br/></div>
84
+          <div class="pod-grey"><i>${_('create a totally new item...')}</i></div>
68 85
         </li>
69 86
 
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>
87
+        ${ToolbarMenuItemModal(h.ID.AddDocumentModalForm(current_node), 'fa fa-file-text-o', _('Document'))}
88
+        ${ToolbarMenuItemModal(h.ID.AddFileModalForm(current_node), 'fa fa-paperclip', _('File'))}
89
+        ${ToolbarMenuItemModal(h.ID.AddEventModalForm(current_node), 'fa fa-calendar', _('Event'))}
90
+        ${ToolbarMenuItemModal(h.ID.AddContactModalForm(current_node), 'fa fa-user', _('Contact'))}
91
+##
92
+## FIXME - DA - 07-05-2014 - The link below is not working clean
93
+##
94
+        ${ToolbarMenuItemInline(h.ID.AddCommentInlineForm(), 'fa fa-comments-o', _('Comment'))}
75 95
 
76 96
         <li class="divider" role="presentation"></li>
77 97
 
@@ -79,12 +99,7 @@
79 99
           <div class="btn-warning strong" ><strong><i class="fa fa-link"></i> Add Existing...</strong><br/></div>
80 100
           <div class="pod-grey"><i>link to an existing item...</i></div>
81 101
         </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
-
102
+        <li><p class="pod-grey"><i class="fa fa-danger"></i> coming soon!</p></li>
88 103
       </ul>
89 104
     </div>
90 105
     <div class="btn-group ">
@@ -103,7 +118,7 @@
103 118
       </ul>
104 119
       <a
105 120
         class="btn btn-small btn-danger"
106
-        href='${tg.url('/api/edit_status?node_id=%i&node_status=%s'%(poNode.node_id, 'deleted'))}'
121
+        href='${tg.url('/api/edit_status', dict(node_id=poNode.node_id, node_status='deleted'))}'
107 122
         id='current-document-force-delete-button' onclick="return confirm('${_('Delete current document?')}');"
108 123
         title="${_('Delete')}"
109 124
         ><i class="fa fa-trash-o"></i></a>
@@ -114,15 +129,21 @@
114 129
 <%def name="BreadCrumb(poNode)">
115 130
   <ul class="breadcrumb span12">
116 131
     <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 132
       <span class="divider">/</span>
133
+      <a href="${tg.url('/document/')}">Documents</a>
123 134
     </li>
124
-    % endfor
125
-    <li class="active">${poNode.data_label}</li>
135
+    % if poNode!=None:
136
+      % for breadcrumb_node in poNode.getBreadCrumbNodes():
137
+      <li>
138
+        <span class="divider">/</span>
139
+        <a href="${tg.url('/document/%s'%(breadcrumb_node.node_id))}">${breadcrumb_node.getTruncatedLabel(30)}</a>
140
+      </li>
141
+      % endfor
142
+      <li class="active">
143
+        <span class="divider">/</span>
144
+        ${poNode.data_label}
145
+      </li>
146
+    % endif
126 147
   </ul>
127 148
 </%def>
128 149
 
@@ -140,13 +161,16 @@
140 161
     <div style="padding: 0.5em 0 0 0">
141 162
       <input type="hidden" name="node_id" value="${current_node.node_id}"/>
142 163
       <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
-      />
164
+      <label>
165
+        ${_('Title')}
166
+        <input
167
+          type="text"
168
+          name="data_label"
169
+          value="${current_node.data_label}"
170
+          class="span4"
171
+          placeholder="${_('document title')}"
172
+        />
173
+      </label>
150 174
     </div>
151 175
     <div>
152 176
       ${POD.RichTextEditor('current_node_textarea_wysiwyg', current_node.data_content)}
@@ -172,8 +196,32 @@
172 196
   <h3 id="${psId}" title="Document ${poNode.node_id}: ${poNode.data_label}">
173 197
     ${poNode.data_label}
174 198
     <sup class="label ${poNode.getStatus().css}" href="#">
199
+      <i class="${poNode.getStatus().icon_id}"></i>
175 200
       ${poNode.getStatus().label}
176 201
     </sup>
202
+    
203
+    % if poNode.is_shared==False:
204
+      <sup class="label label-info" title="${_('This document is private')}">
205
+        <i class="fa fa-key"></i>
206
+        ${_('private')}
207
+      </sup>
208
+    % else:
209
+      <sup class="label label-warning" title="${_('This document is collaborative')}">
210
+        <i class="fa fa-group"></i>
211
+        ${_('collaborative')}
212
+      </sup>
213
+    % endif
214
+######
215
+##
216
+## 2014-05-06 - D.A. - The document is not yet internet-sharable
217
+##
218
+##    % if poNode.is_public==True:
219
+##      <sup class="label label-warning" href="#">
220
+##        <i class="fa fa-globe"></i>
221
+##        <span title="${_('This document is published through internet at %s')%poNode.public_url_key}">${_('shared')}</span>
222
+##      </sup>
223
+##    % endif
224
+
177 225
   </h3>
178 226
 </%def>
179 227
 
@@ -192,3 +240,300 @@
192 240
   </a>
193 241
 </%def>
194 242
 
243
+
244
+<%def name="FirstTimeFakeDocument()">
245
+  <div id='application-document-panel' class="span5">
246
+    <div id='current-document-content' class="">
247
+      <p class="well">
248
+        <strong>${_('Welcome aboard')}</strong>
249
+        <i class="fa fa-smile-o fa-2x"></i>
250
+      </p>
251
+      ${_('<p>We suggest you to...<br/><br/></p>')|n}
252
+      <h4>
253
+        <i class="fa fa-angle-double-left fa-3x fa-fw pod-blue" style="vertical-align: middle"></i>
254
+        ${_('work on existing documents')}
255
+      </h4>
256
+      <p class="text-center">${_('or')}</p>
257
+      <h4 class="text-right">
258
+        ${_('create a new document')}
259
+        <i class="fa fa-angle-double-down fa-3x fa-fw pod-blue" style="vertical-align: middle"></i>
260
+      </h4>
261
+      <p class="pull-right">
262
+        <a href="#${h.ID.AddDocumentModalForm()}" role="button" class="btn btn-success" data-toggle="modal">
263
+          <i class="fa fa-plus"></i>
264
+          ${_('Create a new document')}
265
+        </a>
266
+      </p>
267
+  
268
+      ${DocumentEditModalDialog(None, None, tg.url('/api/create_document'), h.ID.AddDocumentModalForm(), 'Create your first document')}
269
+      <div style="clear: both;"></div>
270
+      <p class="alert alert-info" style="margin-top: 2em;">
271
+        <i class="fa fa-info-circle"></i> ${_('<strong>Note :</strong> You can even create a dummy document: you will be able to remove it later.')|n}
272
+      </p>
273
+    </div>
274
+    <script>
275
+    </script>
276
+  </div>
277
+</%def>
278
+
279
+<%def name="DocumentEditModalDialog(piParentNodeId, poNode, psPostUrl, psModalId, psTitle)">
280
+  <div
281
+    id="${psModalId}"
282
+    class="modal hide"
283
+    tabindex="-1"
284
+    role="dialog"
285
+    aria-labelledby="myModalLabel"
286
+    aria-hidden="true">
287
+    
288
+   <div class="modal-header">
289
+## MODAL HEADER
290
+      <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
291
+      <h3 id="myModalLabel">${psTitle}</h3>
292
+## MODAL HEADER [END]
293
+    </div>
294
+
295
+    <div class="modal-body">
296
+## MODAL BODY
297
+      <form id='${psModalId}-form' method="GET" action="${psPostUrl}">
298
+        <div style="padding: 0.5em 0 0 0">
299
+          % if poNode!=None:
300
+            <input type="hidden" name="node_id" value="${poNode.node_id}"/>
301
+          % endif
302
+          <input type="hidden" name="parent_id" value="${piParentNodeId if piParentNodeId else 0}"/>
303
+          
304
+          <input type="hidden" name="data_content" id="${psModalId}-textarea" />
305
+          <input
306
+            type="text"
307
+            name="data_label"
308
+            value="${poNode.data_label if poNode!=None else ''}"
309
+            class="span4"
310
+            placeholder="${_('document title')}"
311
+          />
312
+        </div>
313
+        <div>
314
+          ${POD.RichTextEditor(psModalId+'-textarea-wysiwyg', poNode.data_content if poNode!=None else '')}
315
+        </div>
316
+      </form>
317
+
318
+## MODAL BODY [END]
319
+    </div>
320
+    
321
+    <div class="modal-footer">
322
+## MODAL FOOTER
323
+      <button class="btn" data-dismiss="modal" aria-hidden="true">
324
+        <i class="fa fa-ban"></i> ${_('Cancel')}
325
+      </button>
326
+      <button class="btn btn-success" id="${psModalId}-form-submit-button">
327
+        <i class="fa fa-check"></i> ${_('Save changes')}
328
+      </button>
329
+## MODAL FOOTER [END]
330
+      <script>
331
+        $('#${psModalId}-form-submit-button').click(function(){
332
+          $('#${psModalId}-textarea-wysiwyg').cleanHtml();
333
+          $('#${psModalId}-textarea').val($('#${psModalId}-textarea-wysiwyg').html());
334
+          $('#${psModalId}-form')[0].submit();
335
+        });
336
+      </script>
337
+    </div>
338
+  </div>
339
+</%def>
340
+
341
+
342
+<%def name="FileEditModalDialog(piParentNodeId, poNode, psPostUrl, psModalId, psTitle)">
343
+  <div
344
+    id="${psModalId}"
345
+    class="modal hide"
346
+    tabindex="-1"
347
+    role="dialog"
348
+    aria-labelledby="myModalLabel"
349
+    aria-hidden="true">
350
+    
351
+    <div class="modal-header">
352
+    ## MODAL HEADER
353
+      <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
354
+      <h3 id="myModalLabel">${psTitle}</h3>
355
+    ## MODAL HEADER [END]
356
+    </div>
357
+
358
+    <div class="modal-body">
359
+    ## MODAL BODY
360
+      <form id='${psModalId}-form' method="POST" action="${psPostUrl}" enctype="multipart/form-data">
361
+          % if poNode!=None:
362
+            <input type="hidden" name="node_id" value="${poNode.node_id}"/>
363
+          % endif
364
+          <input type="hidden" name="parent_id" value="${piParentNodeId if piParentNodeId else 0}"/>
365
+          <input type="hidden" name="data_content" id="${psModalId}-textarea" />
366
+        <div>
367
+          <label>
368
+            ${_('Title')}
369
+            <input
370
+              type="text"
371
+              name="data_label"
372
+              value="${poNode.data_label if poNode!=None else ''}"
373
+              class="span4"
374
+              placeholder="${_('this field is optionnal')}"
375
+            />
376
+          </label>
377
+          <label>
378
+            ${_('Choose a file...')}
379
+            <input type="file" class="span4" placeholder="${_('choose a file...')}" name="data_file"/>
380
+          </label>
381
+          
382
+        </div>
383
+        <div>
384
+          <label>${_('File description (optionnal)')}</label>
385
+          ${POD.RichTextEditor(psModalId+'-textarea-wysiwyg', poNode.data_content if poNode!=None else '', '')}
386
+        </div>
387
+      </form>
388
+    ## MODAL BODY [END]
389
+    </div>
390
+    
391
+    <div class="modal-footer">
392
+    ## MODAL FOOTER
393
+      <button class="btn" data-dismiss="modal" aria-hidden="true">
394
+        <i class="fa fa-ban"></i> ${_('Cancel')}
395
+      </button>
396
+      <button class="btn btn-success" id="${psModalId}-form-submit-button">
397
+        <i class="fa fa-check"></i> ${_('Save changes')}
398
+      </button>
399
+      <script>
400
+        $('#${psModalId}-form-submit-button').click(function(){
401
+          $('#${psModalId}-textarea-wysiwyg').cleanHtml();
402
+          $('#${psModalId}-textarea').val($('#${psModalId}-textarea-wysiwyg').html());
403
+          $('#${psModalId}-form')[0].submit();
404
+        });
405
+      </script>
406
+    ## MODAL FOOTER [END]
407
+    </div>
408
+  </div>
409
+</%def>
410
+
411
+<%def name="EventEditModalDialog(piParentNodeId, poNode, psPostUrl, psModalId, psTitle)">
412
+  <div
413
+    id="${psModalId}"
414
+    class="modal hide"
415
+    tabindex="-1"
416
+    role="dialog"
417
+    aria-labelledby="myModalLabel"
418
+    aria-hidden="true">
419
+    
420
+    <div class="modal-header">
421
+    ## MODAL HEADER
422
+      <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
423
+      <h3 id="myModalLabel">
424
+        ${psTitle}
425
+      </h3>
426
+    ## MODAL HEADER [END]
427
+    </div>
428
+
429
+    <div class="modal-body">
430
+    ###### MODAL BODY
431
+      <form id='${psModalId}-form' action='${psPostUrl}' method='POST'>
432
+        <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
433
+        <fieldset>
434
+          <label>
435
+            ${_('Event')}
436
+            <input type="text" name='data_label' placeholder="Event"/>
437
+          </label>
438
+          <label>
439
+            ${_('Date and time')}
440
+            <div class="datetime-picker-input-div input-append date">
441
+              <input name='data_datetime' data-format="dd/MM/yyyy hh:mm" type="text" placeholder="date and time"/>
442
+              <span class="add-on"><i data-time-icon="icon-g-clock" data-date-icon="icon-g-calendar"></i></span>
443
+            </div>
444
+          </label>
445
+          <label>
446
+            ${_('Event description:')}
447
+            <div>
448
+              <input type="hidden" name="data_content" id="${psModalId}-textarea" />
449
+              ${POD.RichTextEditor(psModalId+'-textarea-wysiwyg', poNode.data_content if poNode!=None else '', 'boldanditalic')}
450
+            </div>
451
+          </label>
452
+        </fieldset>
453
+      </form>
454
+    ###### MODAL BODY [END]
455
+    </div>
456
+    
457
+    <div class="modal-footer">
458
+    ###### MODAL FOOTER
459
+      <button class="btn" data-dismiss="modal" aria-hidden="true">
460
+        <i class="fa fa-ban"></i> ${_('Cancel')}
461
+      </button>
462
+      <button class="btn btn-success" id="${psModalId}-form-submit-button">
463
+        <i class="fa fa-check"></i> ${_('Save changes')}
464
+      </button>
465
+      <script>
466
+        $('#${psModalId}-form-submit-button').click(function(){
467
+          $('#${psModalId}-textarea-wysiwyg').cleanHtml();
468
+          $('#${psModalId}-textarea').val($('#${psModalId}-textarea-wysiwyg').html());
469
+          $('#${psModalId}-form')[0].submit();
470
+        });
471
+      </script>
472
+    ###### MODAL FOOTER [END]
473
+    </div>
474
+  </div>
475
+</%def>
476
+
477
+<%def name="ContactEditModalDialog(piParentNodeId, poNode, psPostUrl, psModalId, psTitle)">
478
+  <div
479
+    id="${psModalId}"
480
+    class="modal hide"
481
+    tabindex="-1"
482
+    role="dialog"
483
+    aria-labelledby="myModalLabel"
484
+    aria-hidden="true">
485
+    
486
+    <div class="modal-header">
487
+    ## MODAL HEADER
488
+      <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
489
+      <h3 id="myModalLabel">${psTitle}</h3>
490
+    ## MODAL HEADER [END]
491
+    </div>
492
+
493
+    <div class="modal-body">
494
+    ## MODAL BODY
495
+      <form id='${psModalId}-form' method="POST" action="${psPostUrl}">
496
+          % if poNode!=None:
497
+            <input type="hidden" name="node_id" value="${poNode.node_id}"/>
498
+          % endif
499
+          <input type="hidden" name="parent_id" value="${piParentNodeId if piParentNodeId else 0}"/>
500
+          <input type="hidden" name="data_content" id="${psModalId}-textarea" />
501
+        <div>
502
+          <label>
503
+            ${_('Contact name and firstname')}
504
+            <input
505
+              type="text"
506
+              name="data_label"
507
+              value="${poNode.data_label if poNode!=None else ''}"
508
+              class="span4"
509
+              placeholder="${_('name, firstname, title...')}"
510
+            />
511
+          </label>
512
+        </div>
513
+        <div>
514
+          <label>${_('Address, phone, email, company...')}</label>
515
+          ${POD.RichTextEditor(psModalId+'-textarea-wysiwyg', poNode.data_content if poNode!=None else '', 'boldanditalic')}
516
+        </div>
517
+      </form>
518
+    ## MODAL BODY [END]
519
+    </div>
520
+    
521
+    <div class="modal-footer">
522
+    ## MODAL FOOTER
523
+      <button class="btn" data-dismiss="modal" aria-hidden="true">
524
+        <i class="fa fa-ban"></i> ${_('Cancel')}
525
+      </button>
526
+      <button class="btn btn-success" id="${psModalId}-form-submit-button">
527
+        <i class="fa fa-check"></i> ${_('Save changes')}
528
+      </button>
529
+      <script>
530
+        $('#${psModalId}-form-submit-button').click(function(){
531
+          $('#${psModalId}-textarea-wysiwyg').cleanHtml();
532
+          $('#${psModalId}-textarea').val($('#${psModalId}-textarea-wysiwyg').html());
533
+          $('#${psModalId}-form')[0].submit();
534
+        });
535
+      </script>
536
+    ## MODAL FOOTER [END]
537
+    </div>
538
+  </div>
539
+</%def>

+ 41 - 297
pboard/pboard/templates/document.mak View File

@@ -1,28 +1,34 @@
1 1
 <%inherit file="local:templates.master"/>
2 2
 <%namespace name="POD" file="pboard.templates.pod"/>
3 3
 <%namespace name="DOC" file="pboard.templates.document-widgets"/>
4
+<%namespace name="DOCTABS" file="pboard.templates.document-widgets-tabs"/>
4 5
 
5 6
 <%def name="title()">
6
-pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id} / ${current_node.getStatus().label}]
7
+  % if current_node!=None:
8
+    pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id} / ${current_node.getStatus().label}]
9
+  % else:
10
+    pod :: document root
11
+  % endif
7 12
 </%def>
8 13
 
9 14
 <%def name="node_treeview(node_list, indentation=-1)">
10 15
   % if indentation==-1:
11 16
     <div id='pod-menu-item-0' class="pod-toolbar-parent" style="padding-left: 0.5em; position: relative;">
12 17
       <a class="toggle-child-menu-items"><i class='fa fa-folder-open'></i></a>
13
-      <a href="?node=0" title="${_('Root')}">
18
+      <a href="${tg.url('/document')}" title="${_('Root')}">
14 19
         ${_('Root')}
15 20
       </a>
16
-      <div class="pod-toolbar">
17
-        <a href="${tg.url('/api/create_document?parent_id=0')}" title="${_('Add child document')}"><i class="fa fa-plus-circle"></i></a>
18
-      </div>
19 21
     </div>
20 22
     <div id="pod-menu-item-0-children">${node_treeview(node_list, 0)}</div>
21 23
     
22 24
   % else:
25
+    % if len(node_list)<=0 and indentation==0:
26
+      <p class="pod-grey">${_('You have no document yet.')}</p>
27
+    % endif
28
+    
23 29
     % if len(node_list)>0:
24 30
       % for node in node_list:
25
-        <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;">
31
+        <div id='pod-menu-item-${node.node_id}' class="pod-toolbar-parent ${'pod-status-active' if current_node!=None and node.node_id==current_node.node_id else ''}" style="padding-left: ${(indentation+2)*0.5}em; position: relative;">
26 32
           <a class="toggle-child-menu-items"><i class='${node.getIconClass()}'></i></a>
27 33
           <a href="${tg.url('/document/%s'%(node.node_id))}" title="${node.data_label}">
28 34
             % if node.getStatus().status_family=='closed' or node.getStatus().status_family=='invisible':
@@ -36,7 +42,6 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
36 42
           <div class="pod-toolbar">
37 43
             <a href="${tg.url('/api/move_node_upper?node_id=%i'%(node.node_id))}" title="${_('Move up')}"><i class="fa fa-arrow-up"></i></a>
38 44
             <a href="${tg.url('/api/move_node_lower?node_id=%i'%(node.node_id))}" title="${_('Move down')}"><i class="fa fa-arrow-down"></i></a>
39
-            <a href="${tg.url('/api/create_document?parent_id=%i'%(node.node_id))}" title="${_('Add child document')}"><i class="fa  fa-plus-circle"></i></a>
40 45
           </div>
41 46
           <div class="pod-status ${node.getStatus().css}" title='${node.getStatus().label}'>
42 47
              <i class='${node.getStatus().icon}'></i>
@@ -80,6 +85,12 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
80 85
     </div>
81 86
     <div id='application-main-panel' class="span9">
82 87
 
88
+      % if current_node==None:
89
+        <div class="row">
90
+          ${DOC.FirstTimeFakeDocument()}
91
+        </div>
92
+        
93
+      % else:
83 94
       <div class="row">
84 95
         <div id='application-document-panel' class="span5">
85 96
           <div id='current-document-content' class="">
@@ -96,8 +107,17 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
96 107
           ${DOC.EditForm(current_node)}
97 108
         </div>
98 109
         <div id='application-metadata-panel' class="span4">
110
+          ######
111
+          ##
112
+          ## HERE WE INCLUDE ALL MODAL DIALOG WHICH WILL BE ACCESSIBLE THROUGH TABS OR MENU
113
+          ##
114
+          ${DOC.DocumentEditModalDialog(current_node.node_id, None, tg.url('/api/create_document'), h.ID.AddDocumentModalForm(current_node), _('New Sub-document'))}
115
+          ${DOC.EventEditModalDialog(current_node.node_id, None, tg.url('/api/create_event'), h.ID.AddEventModalForm(current_node), _('Add an event'))}
116
+          ${DOC.ContactEditModalDialog(current_node.node_id, None, tg.url('/api/create_contact'), h.ID.AddContactModalForm(current_node), _('Add a new contact'))}
117
+          ${DOC.FileEditModalDialog(current_node.node_id, None, tg.url('/api/create_file'), h.ID.AddFileModalForm(current_node), _('Add a new file'))}
118
+          
99 119
           <div class="tabbable">
100
-            <ul class="nav nav-tabs" style="margin-bottom: 0.5em;">
120
+            <ul class="nav nav-tabs" style="margin-bottom: 0em;">
101 121
                 <li>${DOC.MetadataTab('#subdocuments', 'tab', _('Subdocuments'), 'fa-file-text-o', current_node.getChildren())}</li>
102 122
                 <li class="active">${DOC.MetadataTab('#events', 'tab', _('Calendar'), 'fa-calendar', current_node.getEvents())}</li>
103 123
                 <li>${DOC.MetadataTab('#contacts', 'tab', _('Address book'), 'fa-user', current_node.getContacts())}</li>
@@ -105,299 +125,23 @@ pod :: document ${current_node.getTruncatedLabel(40)} [#${current_node.node_id}
105 125
                 <li>${DOC.MetadataTab('#files', 'tab', _('Attachments'), 'fa-paperclip', current_node.getFiles())}</li>
106 126
                 <li class="pull-right">${DOC.MetadataTab('#accessmanagement', 'tab', _('Access Management'), 'fa-key', [])}</li>
107 127
             </ul>
128
+            ################################
129
+            ##
130
+            ## PANEL SHOWING ASSOCIATED DATA AND METADATA
131
+            ##
132
+            ################################
108 133
             <div class="tab-content">
109
-                ################################
110
-                ##
111
-                ## PANEL SHOWING LIST OF SUB DOCUMENTS
112
-                ##
113
-                ################################
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>
142
-                    % endfor
143
-                  </div>
144
-                % endif
145
-                </div>
146
-                
147
-                ################################
148
-                ##
149
-                ## PANEL SHOWING LIST OF EVENTS
150
-                ##
151
-                ################################
152
-                <div class="tab-pane active" id="events">
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
-                
161
-                  <form style='display: none;' id='current-document-add-event-form' action='${tg.url('/api/create_event')}' method='post' class="well">
162
-                    <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
163
-                    <fieldset>
164
-                      <legend>Add an event</legend>
165
-                      <label>
166
-                        <input type="text" name='data_label' placeholder="Event"/>
167
-                      </label>
168
-                      <label>
169
-                        <div class="datetime-picker-input-div input-append date">
170
-                          <input name='data_datetime' data-format="dd/MM/yyyy hh:mm" type="text" placeholder="date and time"/>
171
-                          <span class="add-on"><i data-time-icon="icon-g-clock" data-date-icon="icon-g-calendar"></i></span>
172
-                        </div>
173
-                      </label>
174
-                      <label>
175
-                        <div>
176
-                          <input type="hidden" id="add_event_data_content_textarea" name='data_content' />
177
-                          ${POD.RichTextEditor('add_event_data_content_textarea_wysiwyg', '', 'boldanditalic|undoredo|fullscreen')}
178
-                        </div>
179
-                      </label>
180
-                      <label class="checkbox">
181
-                        <input disabled name='add_reminder' type="checkbox"> add a reminder
182
-                      </label>
183
-                      <label>
184
-                        <div class="datetime-picker-input-div input-append date">
185
-                          <input disabled name='data_reminder_datetime' data-format="dd/MM/yyyy hh:mm" type="text" placeholder="date and time"/>
186
-                          <span class="add-on"><i data-time-icon="icon-g-clock" data-date-icon="icon-g-calendar"></i></span>
187
-                        </div>
188
-                      </label>
189
-
190
-
191
-                      ${POD.CancelButton('current-document-add-event-cancel-button', True)}
192
-                      ${POD.SaveButton('current-document-add-event-save-button', True)}
193
-                    </fieldset>
194
-                  </form>
195
-
196
-                % if len(current_node.getEvents())>0:
197
-                  <table class="table table-striped table-hover table-condensed">
198
-                    <thead>
199
-                      <tr>
200
-                        <th>Date</th>
201
-                        <th>Time</th>
202
-                        <th>
203
-                          Event
204
-                        </th>
205
-                        <th>
206
-                          <a href="" title="Add an event"><i class="icon-g-plus"></i></a>
207
-                        </th>
208
-                      </tr>
209
-                    </thead>
210
-                    % for event in current_node.getEvents():
211
-                      <tr class="item-with-data-popoverable" data-content="${event.data_content}" rel="popover" data-placement="left" data-trigger="hover">
212
-                        <td>${event.getFormattedDate(event.data_datetime)}</td>
213
-                        <td>${event.getFormattedTime(event.data_datetime)}</td>
214
-                        <td>${event.data_label}</td>
215
-                      </tr>
216
-  ## FIXME                    <script>
217
-  ##                      $('.item-with-data-popoverable').popover({ html: true});
218
-  ##                    </script>
219
-
220
-                    % endfor
221
-                  </table>
222
-                % endif
223
-                </div>
224
-                ##############################
225
-                ## 
226
-                ## PANEL SHOWING LIST OF CONTACTS
227
-                ##
228
-                ##############################
229
-                <div class="tab-pane" id="contacts">
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
-
238
-                  <!-- ADD CONTACT FORM -->
239
-                  <form style='display: none;' id='current-document-add-contact-form' action='${tg.url('/api/create_contact')}' method='post' class="well">
240
-                    <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
241
-                    <fieldset>
242
-                      <legend>${_('Add a contact')}</legend>
243
-                      <label>
244
-                        <input type="text" name='data_label' placeholder="Title"/>
245
-                      </label>
246
-                      <label>
247
-                        <div>
248
-                          <input type="hidden" id="add_contact_data_content_textarea" name='data_content' />
249
-                          ${POD.RichTextEditor('add_contact_data_content_textarea_wysiwyg', '', 'boldanditalic|undoredo|fullscreen')}
250
-                        </div>
251
-                      </label>
252
-                      ${POD.CancelButton('current-document-add-contact-cancel-button', True)}
253
-                      ${POD.SaveButton('current-document-add-contact-save-button', True)}
254
-                    </fieldset>
255
-                  </form>
256
-
257
-                  <!-- LIST OF CONTACT NODES -->
258
-                  % for contact in current_node.getContacts():
259
-                    <div class="well">
260
-                      <legend class="text-info">
261
-                        ${contact.data_label}
262
-                        ## TODO - 2013-11-20 - Use the right form in order to update meta-data
263
-                        <a class="pull-right" href="${tg.url('/document/%i'%contact.node_id)}"><i class="fa fa-edit"></i></a>
264
-                      </legend>
265
-                      
266
-                      <div>
267
-                        ## FIXME - D.A. - 2013-11-15 - Implement localisation stuff <a style='float: right;' href="" title='${_('Search on google maps')}'><i class='icon-g-google-maps'></i></a>
268
-                        ${contact.data_content|n}
269
-                      </div>
270
-                    </div>
271
-                  % endfor
272
-                </div>
273
-                ################################
274
-                ##
275
-                ## PANEL SHOWING LIST OF COMMENTS
276
-                ##
277
-                ################################
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
-
287
-                  <!-- ADD COMMENT FORM -->
288
-                  <form style='display: none;' id='current-document-add-comment-form' action='${tg.url('/api/create_comment')}' method='post' class="well">
289
-                    <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
290
-                    <fieldset>
291
-                      <legend>${_('Add a comment')}</legend>
292
-                      <label>
293
-                        <input type="text" name='data_label' placeholder="Title"/>
294
-                      </label>
295
-                      <label>
296
-                        <div>
297
-                          <input type="hidden" id="add_comment_data_content_textarea" name='data_content' />
298
-                          ${POD.RichTextEditor('add_comment_data_content_textarea_wysiwyg', '', 'boldanditalic|undoredo|fullscreen')}
299
-                        </div>
300
-                      </label>
301
-                      ${POD.CancelButton('current-document-add-comment-cancel-button', True)}
302
-                      ${POD.SaveButton('current-document-add-comment-save-button', True)}
303
-                    </fieldset>
304
-                  </form>
305
-
306
-                  <!-- LIST OF COMMENTS -->
307
-                % if len(current_node.getComments())>0:
308
-                  <table class="table table-striped table-hover table-condensed">
309
-                    % for comment in current_node.getComments():
310
-                      <tr title="Last updated: ${comment.updated_at}">
311
-                        <td>
312
-                          <i>The ${comment.getFormattedDate(comment.updated_at)} at ${comment.getFormattedTime(comment.updated_at)}: </i><br/>
313
-                          <b>${comment.data_label}</b>
314
-                          ## TODO - 2013-11-20 - Use the right form in order to update meta-data
315
-                          <a class="pull-right" href="${tg.url('/document/%i'%comment.node_id)}"><i class="fa fa-edit"></i></a>
316
-                          <br/>
317
-                          <p>
318
-                            ${comment.data_content|n}
319
-                          </p>
320
-                        </td>
321
-                      </tr>
322
-                    % endfor
323
-                  </table>
324
-                % endif
325
-                </div>
326
-                ################################
327
-                ##
328
-                ## PANEL SHOWING LIST OF FILES
329
-                ##
330
-                ################################
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
-
340
-                  <!-- ADD FILE FORM -->
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">
342
-                    <input type="hidden" name='parent_id' value='${current_node.node_id}'/>
343
-                    <fieldset>
344
-                      <legend>${_('Add a file')}</legend>
345
-                      <label>
346
-                        <input type="text" name='data_label' placeholder="Title"/>
347
-                      </label>
348
-                      <label>
349
-                        <input type="file" name='data_file' placeholder="choose a file..."/>
350
-                      </label>
351
-                      <label>
352
-                        <div>
353
-                          <input type="hidden" id="add_file_data_content_textarea" name='data_content' />
354
-                          ${POD.RichTextEditor('add_file_data_content_textarea_wysiwyg', '', 'boldanditalic|undoredo|fullscreen')}
355
-                        </div>
356
-                      </label>
357
-                      ${POD.CancelButton('current-document-add-file-cancel-button', True)}
358
-                      ${POD.SaveButton('current-document-add-file-save-button', True)}
359
-                    </fieldset>
360
-                  </form>
361
-
362
-                  <!-- LIST OF FILES -->
363
-                  <div>
364
-                % if len(current_node.getFiles())>0:
365
-                    % for current_file in current_node.getFiles():
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>
382
-                    % endfor
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
395
-                </div>
396
-              </div>
134
+              <div class="tab-pane" id="subdocuments">${DOCTABS.SubdocumentContent(current_node)}</div>
135
+              <div class="tab-pane active" id="events">${DOCTABS.EventTabContent(current_node)}</div>
136
+              <div class="tab-pane" id="contacts">${DOCTABS.ContactTabContent(current_node)}</div>
137
+              <div class="tab-pane" id="comments">${DOCTABS.CommentTabContent(current_node)}</div>
138
+              <div class="tab-pane" id="files">${DOCTABS.FileTabContent(current_node)}</div>
139
+              <div class="tab-pane" id="accessmanagement">${DOCTABS.AccessManagementTab(current_node)}</div>
397 140
             </div>
398 141
           </div>
399 142
         </div>
400 143
       </div>
144
+      % endif
401 145
     </div>
402 146
   </div>
403
-
147
+</div>

+ 6 - 2
pboard/pboard/templates/master.mak View File

@@ -55,7 +55,11 @@
55 55
 
56 56
 <%def name="footer()">
57 57
   <div class="footer hidden-tablet hidden-phone text-center">
58
-    <p class="pod-blue"><i>${_("Using pod, you can: search a job, manage projects, track and manage clients and prospects, document processes and knowledge, ...")}</i></p>
58
+    <p class="pod-blue">
59
+      <i>${_("collaborative work  ♦  improved efficiency  ♦  full traceability")}</i>
60
+      <br/>
61
+      this is pod
62
+    </p>
59 63
     <hr style="width: 50%; margin: 0.5em auto;"/>
60 64
     <p>Copyright &copy; 2013 - ${h.current_year()} pod project.</p>
61 65
   </div>
@@ -95,7 +99,7 @@
95 99
             </li>
96 100
 
97 101
             <li title="Rebuild document index">
98
-            % if current_node is UNDEFINED:
102
+            % if current_node is UNDEFINED or current_node==None:
99 103
               <a href="${tg.url('/api/reindex_nodes?back_to_node_id=0')}"><i class="fa fa-refresh"></i></a>
100 104
             % else:
101 105
               <a href="${tg.url('/api/reindex_nodes?back_to_node_id=%i'%(current_node.node_id))}"><i class="fa fa-refresh"></i></a>

+ 19 - 3
pboard/pboard/templates/pod.mak View File

@@ -35,8 +35,9 @@
35 35
   <button id='${piId}' type="button" class="${psButtonCssClass}" title="${psButtonTitle}"><i class="${psButtonIcon}"></i>${'' if (pbWithLabel==False) else ' %s'%(psButtonLabel)}</button>
36 36
 </%def>
37 37
 
38
-<%def name="SaveButton(piId, pbWithLabel=False)" >
39
-  ${Button(piId, pbWithLabel, 'btn btn-small btn-success', _('Save'), ' icon-g-ok-2 icon-g-white', _('Save'))}
38
+<%def name="SaveButton(piId, pbWithLabel=False, psLabel='Save')" >
39
+## FIXME - Make the default value use _() in order to be translated
40
+  ${Button(piId, pbWithLabel, 'btn btn-small btn-success', psLabel, ' icon-g-ok-2 icon-g-white', psLabel)}
40 41
 </%def>
41 42
 <%def name="EditButton(piId, pbWithLabel=False)" >
42 43
   ${Button(piId, pbWithLabel, 'btn btn-small', _('Edit'), 'fa fa-edit', _('Edit'))}
@@ -51,6 +52,20 @@
51 52
   ${Button(piId, pbWithLabel, 'btn btn-small', psLabel or _('New'), 'fa fa-plus', psLabel or _('New'))}
52 53
 % endif
53 54
 </%def>
55
+
56
+###
57
+##
58
+## GREEN CALL-TO-ACTION BUTTONS IN THE INTERFACE
59
+##
60
+##
61
+<%def name="OpenModalButton(psModalAnchor, psLabel)">
62
+  <a href="#${psModalAnchor}" role="button" class="btn btn-success btn-small" data-toggle="modal"><i class="fa fa-plus"></i> ${psLabel}</a>
63
+</%def>
64
+<%def name="OpenLinkButton(psModalAnchor, psLabel)">
65
+  <a href="#${psModalAnchor}" class="btn btn-success btn-small"><i class="fa fa-plus"></i> ${psLabel}</a>
66
+</%def>
67
+## END OF GREEN CALL-TO-ACTION BUTTONS
68
+
54 69
 <%def name='Badge(psLabel, psCssClass="")'>
55 70
   <span class='badge ${psCssClass}'>${psLabel}</span>
56 71
 </%def>
@@ -153,7 +168,7 @@
153 168
       % endif
154 169
       % if psMenuOptions.find('fullscreen')>=0:
155 170
         <div class="btn-group">
156
-          <a class="btn btn-primary pod-toggle-full-screen-button"
171
+          <a class="btn btn-success pod-toggle-full-screen-button"
157 172
              title="Toggle fullscreen"
158 173
              onclick="toggleFullScreen('#${psRichTextEditorNodeId}-widget', '#${psRichTextEditorNodeId}-widget-inner')"
159 174
             >
@@ -207,3 +222,4 @@
207 222
 
208 223
 </%def>
209 224
 
225
+<%def name="AddDocumentModalFormId(poNode)">add-document-modal-form-${poNode.node_id if poNode!=None else ''}</%def>