Browse Source

show full history on pages and files

Damien ACCORSI 9 years ago
parent
commit
1a0ea4b571

+ 0 - 1
tracim/tracim/controllers/workspace.py View File

@@ -79,7 +79,6 @@ class UserWorkspaceRestController(TIMRestController):
79 79
 
80 80
         if not current_id:
81 81
             # Default case is to return list of workspaces
82
-            print('ignore : ', ignored_ids)
83 82
             api = WorkspaceApi(tmpl_context.current_user)
84 83
             workspaces = api.get_all_for_user(tmpl_context.current_user,
85 84
                                               ignored_ids)

+ 2 - 2
tracim/tracim/lib/content.py View File

@@ -373,13 +373,13 @@ class ContentApi(object):
373 373
         elif new_workspace:
374 374
             item.workspace = new_workspace
375 375
 
376
-        item.revision_type = ActionDescription.EDITION
376
+        item.revision_type = ActionDescription.MOVE
377 377
 
378 378
     def move_recursively(self, item: Content,
379 379
                          new_parent: Content, new_workspace: Workspace):
380 380
         self.move(item, new_parent, False, new_workspace)
381 381
         self.save(item, do_notify=False)
382
-        print('saved item #', item.content_id, new_workspace)
382
+
383 383
         for child in item.children:
384 384
             self.move_recursively(child, item, new_workspace)
385 385
         return

+ 3 - 0
tracim/tracim/lib/helpers.py View File

@@ -177,6 +177,9 @@ def delete_label_for_item(item) -> str:
177 177
     return ContentType._DELETE_LABEL[item.type]
178 178
 
179 179
 def is_item_still_editable(item):
180
+    if item.type.id != 'comment':
181
+        return False
182
+
180 183
     # HACK - D.A - 2014-12-24 - item contains a datetime object!!!
181 184
     # 'item' is a variable which is created by serialization and it should be an instance of DictLikeClass.
182 185
     # therefore, it contains strins, integers and booleans (something json-ready or almost json-ready)

+ 0 - 1
tracim/tracim/model/auth.py View File

@@ -213,7 +213,6 @@ class User(DeclarativeBase):
213 213
 
214 214
     def get_role(self, workspace: 'Workspace') -> int:
215 215
         for role in self.roles:
216
-            print('IS EQUALS ? ', role.workspace, workspace)
217 216
             if role.workspace == workspace:
218 217
                 return role.role
219 218
 

+ 99 - 17
tracim/tracim/model/data.py View File

@@ -24,7 +24,7 @@ from sqlalchemy.types import LargeBinary
24 24
 from sqlalchemy.types import Text
25 25
 from sqlalchemy.types import Unicode
26 26
 
27
-from tg.i18n import lazy_ugettext as l_
27
+from tg.i18n import lazy_ugettext as l_, ugettext as _
28 28
 
29 29
 from tracim.model import DeclarativeBase
30 30
 from tracim.model.auth import User
@@ -169,29 +169,32 @@ class ActionDescription(object):
169 169
     STATUS_UPDATE = 'status-update'
170 170
     UNARCHIVING = 'unarchiving'
171 171
     UNDELETION = 'undeletion'
172
+    MOVE = 'move'
172 173
 
173 174
     _ICONS = {
174 175
         'archiving': 'fa fa-archive',
175 176
         'content-comment': 'fa-comment-o',
176 177
         'creation': 'fa-magic',
177
-        'deletion': 'fa fa-trash',
178
-        'edition': 'fa fa-edit',
178
+        'deletion': 'fa-trash',
179
+        'edition': 'fa-edit',
179 180
         'revision': 'fa-history',
180 181
         'status-update': 'fa-random',
181
-        'unarchiving': 'fa fa-file-archive-o',
182
-        'undeletion': 'fa-trash-o'
182
+        'unarchiving': 'fa-file-archive-o',
183
+        'undeletion': 'fa-trash-o',
184
+        'move': 'fa-arrows'
183 185
     }
184 186
 
185 187
     _LABELS = {
186 188
         'archiving': l_('archive'),
187
-        'content-comment': l_('commente'),
188
-        'creation': l_('creation'),
189
-        'deletion': l_('deletion'),
190
-        'edition': l_('modified'),
191
-        'revision': l_('revision'),
192
-        'status-update': l_('statut'),
193
-        'unarchiving': l_('un-archived'),
194
-        'undeletion': l_('un-deleted'),
189
+        'content-comment': l_('Item commented'),
190
+        'creation': l_('Item created'),
191
+        'deletion': l_('Item deleted'),
192
+        'edition': l_('item modified'),
193
+        'revision': l_('New revision'),
194
+        'status-update': l_('New status'),
195
+        'unarchiving': l_('Item unarchived'),
196
+        'undeletion': l_('Item undeleted'),
197
+        'move': l_('Item moved')
195 198
     }
196 199
 
197 200
     def __init__(self, id):
@@ -199,6 +202,7 @@ class ActionDescription(object):
199 202
         self.id = id
200 203
         self.label = ActionDescription._LABELS[id]
201 204
         self.icon = ActionDescription._ICONS[id]
205
+        self.css = ''
202 206
 
203 207
     @classmethod
204 208
     def allowed_values(cls):
@@ -210,7 +214,8 @@ class ActionDescription(object):
210 214
                 cls.REVISION,
211 215
                 cls.STATUS_UPDATE,
212 216
                 cls.UNARCHIVING,
213
-                cls.UNDELETION]
217
+                cls.UNDELETION,
218
+                cls.MOVE]
214 219
 
215 220
 
216 221
 class ContentStatus(object):
@@ -406,10 +411,15 @@ class ContentType(object):
406 411
     def sorted(cls, types: ['ContentType']) -> ['ContentType']:
407 412
         return sorted(types, key=lambda content_type: content_type.priority)
408 413
 
414
+    @property
415
+    def type(self):
416
+        return self.id
417
+
409 418
     def __init__(self, type):
410
-        self.type = type
419
+        self.id = type
411 420
         self.icon = ContentType._CSS_ICONS[type]
412
-        self.color = ContentType._CSS_COLORS[type]
421
+        self.color = ContentType._CSS_COLORS[type]  # deprecated
422
+        self.css = ContentType._CSS_COLORS[type]
413 423
         self.label = ContentType._LABEL[type]
414 424
         self.priority = ContentType._ORDER_WEIGHT[type]
415 425
 
@@ -594,10 +604,21 @@ class Content(DeclarativeBase):
594 604
         except Exception as e:
595 605
             print(e.__str__())
596 606
             print('----- /*\ *****')
597
-            raise ValueError('No allowed content property')
607
+            raise ValueError('Not allowed content property')
598 608
 
599 609
         return ContentType.sorted(types)
600 610
 
611
+    def get_history(self) -> '[VirtualEvent]':
612
+        events = []
613
+        for comment in self.get_comments():
614
+            events.append(VirtualEvent.create_from_content(comment))
615
+        for revision in self.revisions:
616
+            events.append(VirtualEvent.create_from_content_revision(revision))
617
+
618
+        sorted_events = sorted(events,
619
+                               key=lambda event: event.created, reverse=True)
620
+        return sorted_events
621
+
601 622
 
602 623
 class ContentChecker(object):
603 624
 
@@ -649,6 +670,7 @@ class ContentRevisionRO(DeclarativeBase):
649 670
     file_mimetype = Column(Unicode(255),  unique=False, nullable=False, default='')
650 671
     file_content = deferred(Column(LargeBinary(), unique=False, nullable=False, default=None))
651 672
 
673
+    type = Column(Unicode(32), unique=False, nullable=False)
652 674
     status = Column(Unicode(32), unique=False, nullable=False)
653 675
     created = Column(DateTime, unique=False, nullable=False)
654 676
     updated = Column(DateTime, unique=False, nullable=False)
@@ -665,6 +687,9 @@ class ContentRevisionRO(DeclarativeBase):
665 687
     def get_status(self):
666 688
         return ContentStatus(self.status)
667 689
 
690
+    def get_label(self):
691
+        return self.label if self.label else self.file_name if self.file_name else ''
692
+
668 693
     def get_last_action(self) -> ActionDescription:
669 694
         return ActionDescription(self.revision_type)
670 695
 
@@ -678,3 +703,60 @@ class NodeTreeItem(object):
678 703
         self.node = node
679 704
         self.children = children
680 705
         self.is_selected = is_selected
706
+
707
+class VirtualEvent(object):
708
+    @classmethod
709
+    def create_from(cls, object):
710
+        if Content == object.__class__:
711
+            return cls.create_from_content(object)
712
+        elif ContentRevisionRO == object.__class__:
713
+            return cls.create_from_content_revision(object)
714
+
715
+    @classmethod
716
+    def create_from_content(cls, content: Content):
717
+        content_type = ContentType(content.type)
718
+
719
+        label = content.get_label()
720
+        if content.type==ContentType.Comment:
721
+            label = _('<strong>{}</strong> wrote:').format(content.owner.get_display_name())
722
+
723
+        return VirtualEvent(id=content.content_id,
724
+                            created=content.created,
725
+                            owner=content.owner,
726
+                            type=content_type,
727
+                            label=label,
728
+                            content=content.description,
729
+                            ref_object=content)
730
+
731
+    @classmethod
732
+    def create_from_content_revision(cls, revision: ContentRevisionRO):
733
+        action_description = ActionDescription(revision.revision_type)
734
+
735
+        return VirtualEvent(id=revision.revision_id,
736
+                            created=revision.created,
737
+                            owner=revision.owner,
738
+                            type=action_description,
739
+                            label=action_description.label,
740
+                            content='',
741
+                            ref_object=revision)
742
+
743
+    def __init__(self, id, created, owner, type, label, content, ref_object):
744
+        self.id = id
745
+        self.created = created
746
+        self.owner = owner
747
+        self.type = type
748
+        self.label = label
749
+        self.content = content
750
+        self.ref_object = ref_object
751
+
752
+        print(type)
753
+        assert hasattr(type, 'id')
754
+        assert hasattr(type, 'css')
755
+        assert hasattr(type, 'icon')
756
+        assert hasattr(type, 'label')
757
+
758
+    def created_as_delta(self, delta_from_datetime:datetime=None):
759
+        if not delta_from_datetime:
760
+            delta_from_datetime = datetime.now()
761
+        return format_timedelta(delta_from_datetime - self.created,
762
+                                locale=tg.i18n.get_lang()[0])

+ 45 - 17
tracim/tracim/model/serializers.py View File

@@ -22,6 +22,7 @@ from tracim.model.data import Content
22 22
 from tracim.model.data import ContentType
23 23
 from tracim.model.data import RoleType
24 24
 from tracim.model.data import UserRoleInWorkspace
25
+from tracim.model.data import VirtualEvent
25 26
 from tracim.model.data import Workspace
26 27
 
27 28
 from tracim.model import data as pmd
@@ -64,6 +65,7 @@ class CTX(object):
64 65
     ADMIN_WORKSPACE = 'ADMIN_WORKSPACE'
65 66
     ADMIN_WORKSPACES = 'ADMIN_WORKSPACES'
66 67
     CONTENT_LIST = 'CONTENT_LIST'
68
+    CONTENT_HISTORY = 'CONTENT_HISTORY'
67 69
     CURRENT_USER = 'CURRENT_USER'
68 70
     DEFAULT = 'DEFAULT' # default context. This will allow to define a serialization method to be used by default
69 71
     EMAIL_NOTIFICATION = 'EMAIL_NOTIFICATION'
@@ -351,8 +353,6 @@ def serialize_node_for_page(content: Content, context: Context):
351 353
     if content.type in (ContentType.Page, ContentType.File) :
352 354
         data_container = content
353 355
 
354
-
355
-
356 356
         # The following properties are overriden by revision values
357 357
         if content.revision_to_serialize>0:
358 358
             for revision in content.revisions:
@@ -361,20 +361,21 @@ def serialize_node_for_page(content: Content, context: Context):
361 361
                     break
362 362
 
363 363
         result = DictLikeClass(
364
-            id = content.content_id,
365
-            parent = context.toDict(content.parent),
366
-            workspace = context.toDict(content.workspace),
367
-            type = content.type,
368
-
369
-            content = data_container.description,
370
-            created = data_container.created,
371
-            label = data_container.label,
372
-            icon = ContentType.get_icon(content.type),
373
-            owner = context.toDict(data_container.owner),
374
-            status = context.toDict(data_container.get_status()),
375
-            links = context.toDict(content.extract_links_from_content(data_container.description)),
376
-            revisions = context.toDict(sorted(content.revisions, key=lambda v: v.created, reverse=True)),
377
-            selected_revision = 'latest' if content.revision_to_serialize<=0 else content.revision_to_serialize
364
+            id=content.content_id,
365
+            parent=context.toDict(content.parent),
366
+            workspace=context.toDict(content.workspace),
367
+            type=content.type,
368
+
369
+            content=data_container.description,
370
+            created=data_container.created,
371
+            label=data_container.label,
372
+            icon=ContentType.get_icon(content.type),
373
+            owner=context.toDict(data_container.owner),
374
+            status=context.toDict(data_container.get_status()),
375
+            links=context.toDict(content.extract_links_from_content(data_container.description)),
376
+            revisions=context.toDict(sorted(content.revisions, key=lambda v: v.created, reverse=True)),
377
+            selected_revision='latest' if content.revision_to_serialize<=0 else content.revision_to_serialize,
378
+            history=Context(CTX.CONTENT_HISTORY).toDict(content.get_history())
378 379
         )
379 380
 
380 381
         if content.type==ContentType.File:
@@ -395,6 +396,31 @@ def serialize_node_for_page(content: Content, context: Context):
395 396
     raise NotImplementedError
396 397
 
397 398
 
399
+@pod_serializer(VirtualEvent, CTX.CONTENT_HISTORY)
400
+def serialize_content_for_history(event: VirtualEvent, context: Context):
401
+    urls = DictLikeClass({'delete': None})
402
+    if ContentType.Comment == event.type.id:
403
+        urls = context.toDict({
404
+          'delete': context.url('/workspaces/{wid}/folders/{fid}/{ctype}/{cid}/comments/{commentid}/put_delete'.format(
405
+              wid = event.ref_object.workspace_id,
406
+              fid=event.ref_object.parent.parent_id,
407
+              ctype=event.ref_object.parent.type+'s',
408
+              cid=event.ref_object.parent.content_id,
409
+              commentid=event.ref_object.content_id))
410
+        })
411
+
412
+    return DictLikeClass(
413
+        owner=context.toDict(event.owner),
414
+        id=event.id,
415
+        label=event.label,
416
+        type=context.toDict(event.type),
417
+        created=event.created,
418
+        created_as_delta=event.created_as_delta(),
419
+        content=event.content,
420
+        urls = urls
421
+    )
422
+
423
+
398 424
 @pod_serializer(Content, CTX.THREAD)
399 425
 def serialize_node_for_page(item: Content, context: Context):
400 426
     if item.type==ContentType.Thread:
@@ -411,7 +437,8 @@ def serialize_node_for_page(item: Content, context: Context):
411 437
             status = context.toDict(item.get_status()),
412 438
             type = item.type,
413 439
             workspace = context.toDict(item.workspace),
414
-            comments = reversed(context.toDict(item.get_comments()))
440
+            comments = reversed(context.toDict(item.get_comments())),
441
+            history = Context(CTX.CONTENT_HISTORY).toDict(item.get_history())
415 442
         )
416 443
 
417 444
     if item.type==ContentType.Comment:
@@ -660,6 +687,7 @@ def serialize_content_for_folder_content_list(content: Content, context: Context
660 687
 def serialize_breadcrumb_item(content_type: ContentType, context: Context):
661 688
     return DictLikeClass(content_type.toDict())
662 689
 
690
+
663 691
 @pod_serializer(Content, CTX.SEARCH)
664 692
 def serialize_content_for_search_result(content: Content, context: Context):
665 693
 

+ 5 - 1
tracim/tracim/public/assets/css/dashboard.css View File

@@ -349,4 +349,8 @@ h3 { background-color: #f5f5f5;}
349 349
     padding: 0;
350 350
     position: absolute;
351 351
     top: 0;
352
-}
352
+}
353
+
354
+#t-full-app-alert-message-id > div.alert {
355
+    box-shadow: 0px 0px 5px 5px rgba(0, 0, 0, 0.3);
356
+}

+ 5 - 21
tracim/tracim/templates/file/getone.mak View File

@@ -154,31 +154,15 @@
154 154
     <div class="col-sm-7 col-sm-offset-3">
155 155
         <div class="t-spacer-above">
156 156
             <span id="associated-revisions" ></span>
157
-            <h4 class="anchored-title">${_('File revisions')}</h4>
157
+            <h4 class="anchored-title">${_('File history')}</h4>
158 158
             <div>
159 159
                 <table class="table table-striped table-hover">
160
-                    % for revid, revision in reversed(list(enumerate(reversed(result.file.revisions)))):
161
-                        <% warning_or_not = ('', 'warning')[result.file.selected_revision==revision.id] %>
162
-                        <tr class="${warning_or_not}">
163
-## FIXME - 2015-07-22 - D.A. - Do we really need to show a rev. id ?!
164
-## <td><span class="label label-default">v${revid}</span></td>
165
-                            <td class="t-less-visible">
166
-                                <span class="label label-default">${ICON.FA_FW(revision.action.icon)}
167
-                                ${revision.action.label}</span>
168
-                            </td>
169
-                            <td>${h.date(revision.created)}</td>
170
-                            <td>${h.time(revision.created)}</td>
171
-                            <td>${revision.owner.name}</td>
172
-                            <td><a href="${tg.url('/workspaces/{}/folders/{}/files/{}?revision_id={}').format(result.file.workspace.id, result.file.parent.id, result.file.id, revision.id)}">${revision.label}</a></td>
173
-                            <td class="t-less-visible" title="${_('Currently shown')}">
174
-                                % if warning_or_not:
175
-                                    ${ICON.FA_FW('fa fa-caret-left')} ${_('shown').format(result.file.selected_revision)}
176
-                                % endif
177
-                            </td>
178
-                        </tr>
160
+                    % for event in result.file.history:
161
+                        ${WIDGETS.SECURED_HISTORY_VIRTUAL_EVENT_AS_TABLE_ROW(fake_api.current_user, event, result.file.selected_revision)}
179 162
                     % endfor
180 163
                 </table>
181 164
             </div>
182 165
         </div>
183 166
     </div>
184
-<div/>
167
+<div/>
168
+

+ 4 - 20
tracim/tracim/templates/page/getone.mak View File

@@ -98,31 +98,15 @@
98 98
     <div class="col-sm-7 col-sm-offset-3">
99 99
         <div class="t-spacer-above">
100 100
             <span id="associated-revisions" ></span>
101
-            <h4 class="anchored-title">${_('Page revisions')}</h4>
101
+            <h4 class="anchored-title">${_('Page history')}</h4>
102 102
             <div>
103 103
                 <table class="table table-striped table-hover">
104
-                    % for revid, revision in reversed(list(enumerate(reversed(result.page.revisions)))):
105
-                        <% warning_or_not = ('', 'warning')[result.page.selected_revision==revision.id] %>
106
-                        <tr class="${warning_or_not}">
107
-## FIXME - 2015-07-22 - D.A. - Do we really need to show a rev. id ?!
108
-## <td><span class="label label-default">v${revid}</span></td>
109
-                            <td class="t-less-visible">
110
-                                <span class="label label-default">${ICON.FA_FW(revision.action.icon)}
111
-                                ${revision.action.label}</span>
112
-                            </td>
113
-                            <td>${h.date(revision.created)}</td>
114
-                            <td>${h.time(revision.created)}</td>
115
-                            <td>${revision.owner.name}</td>
116
-                            <td><a href="${tg.url('/workspaces/{}/folders/{}/pages/{}?revision_id={}').format(result.page.workspace.id, result.page.parent.id, result.page.id, revision.id)}">${revision.label}</a></td>
117
-                            <td class="t-less-visible" title="${_('Currently shown')}">
118
-                                % if warning_or_not:
119
-                                    ${ICON.FA_FW('fa fa-caret-left')} ${_('shown').format(result.page.selected_revision)}
120
-                                % endif
121
-                            </td>
122
-                        </tr>
104
+                    % for event in result.page.history:
105
+                        ${WIDGETS.SECURED_HISTORY_VIRTUAL_EVENT_AS_TABLE_ROW(fake_api.current_user, event, result.page.selected_revision)}
123 106
                     % endfor
124 107
                 </table>
125 108
             </div>
126 109
         </div>
127 110
     </div>
128 111
 <div/>
112
+

+ 10 - 22
tracim/tracim/templates/thread/getone.mak View File

@@ -100,27 +100,15 @@
100 100
     </div>
101 101
 </div>
102 102
 
103
-% for comment in result.thread.comments:
104
-    ${WIDGETS.SECURED_TIMELINE_ITEM(fake_api.current_user, comment)}
103
+% for event in result.thread.history:
104
+    ## TODO - D.A. - 2015-08-20
105
+    ## Allow to show full history (with status change and archive/unarchive)
106
+    % if event.type.id in ('comment', 'creation'):
107
+        ${WIDGETS.SECURED_HISTORY_VIRTUAL_EVENT(fake_api.current_user, event)}
108
+    % endif
105 109
 % endfor
106 110
 
107
-## <hr class="tracim-panel-separator"/>
108
-## <div>
109
-##     <h4 id="associated-links" class="anchored-title" >${_('Links extracted from the thread')}</h4>
110
-##     <div>
111
-##         % if len(result.thread.links)<=0:
112
-##             <p class="pod-empty">${_('No link found.')}</p>
113
-##         % else:
114
-##             <ul>
115
-##                 % for link in result.thread.links:
116
-##                     <li><a href="${link.href}">${link.label if link.label else link.href}</a></li>
117
-##                 % endfor
118
-##             </ul>
119
-##         % endif
120
-##     </div>
121
-##     <hr/>
122
-## 
123
-##     % for comment in result.thread.comments:
124
-##         ${comment}
125
-##     % endfor
126
-## </div>
111
+## % for comment in result.thread.comments:
112
+##     ${WIDGETS.SECURED_TIMELINE_ITEM(fake_api.current_user, comment)}
113
+## % endfor
114
+##

+ 76 - 10
tracim/tracim/templates/user_workspace_widgets.mak View File

@@ -303,34 +303,100 @@
303 303
 </%def>
304 304
 
305 305
 <%def name="SECURED_TIMELINE_ITEM(user, item)">
306
+##     <div class="row t-odd-or-even t-hacky-thread-comment-border-top">
307
+##         <div class="col-sm-7 col-sm-offset-3">
308
+##             <div class="t-timeline-item">
309
+## ##                <i class="fa fa-fw fa-3x fa-comment-o t-less-visible" style="margin-left: -1.5em; float:left;"></i>
310
+##                 ${ICON.FA_FW('fa fa-3x fa-comment-o t-less-visible t-timeline-item-icon')}
311
+##
312
+##                 <h5 style="margin: 0;">
313
+##                     <span class="tracim-less-visible">${_('<strong>{}</strong> wrote:').format(item.owner.name)|n}</span>
314
+##
315
+##                     <div class="pull-right text-right t-timeline-item-moment" title="${h.date_time(item.created)|n}">
316
+##                         ${_('{delta} ago').format(delta=item.created_as_delta)}
317
+##
318
+##                         % if h.is_item_still_editable(item) and item.owner.id==user.id:
319
+##                             <br/>
320
+## ##                            <div class="btn-group">
321
+##                                 <a class="t-timeline-comment-delete-button" href="${item.urls.delete}">
322
+##                                     ${_('delete')} ${ICON.FA('fa fa-trash-o')}
323
+## ##                                    ${TIM.ICO_TOOLTIP(16, 'status/user-trash-full', h.delete_label_for_item(item))}
324
+##                                 </a>
325
+## ##                            </div>
326
+##                         % endif
327
+##                     </div>
328
+##                 </h5>
329
+##                 <div class="t-timeline-item-content">
330
+##                     <div>${item.content|n}</div>
331
+##                     <br/>
332
+##                 </div>
333
+##             </div>
334
+##         </div>
335
+##     </div>
336
+</%def>
337
+
338
+<%def name="SECURED_HISTORY_VIRTUAL_EVENT(user, event)">
306 339
     <div class="row t-odd-or-even t-hacky-thread-comment-border-top">
307 340
         <div class="col-sm-7 col-sm-offset-3">
308 341
             <div class="t-timeline-item">
309 342
 ##                <i class="fa fa-fw fa-3x fa-comment-o t-less-visible" style="margin-left: -1.5em; float:left;"></i>
310
-                ${ICON.FA_FW('fa fa-3x fa-comment-o t-less-visible t-timeline-item-icon')}
343
+
344
+                ${ICON.FA_FW('fa fa-3x t-less-visible t-timeline-item-icon '+event.type.icon)}
311 345
 
312 346
                 <h5 style="margin: 0;">
313
-                    <span class="tracim-less-visible">${_('<strong>{}</strong> wrote:').format(item.owner.name)|n}</span>
314 347
 
315
-                    <div class="pull-right text-right t-timeline-item-moment" title="${h.date_time(item.created)|n}">
316
-                        ${_('{delta} ago').format(delta=item.created_as_delta)}
348
+                    % if 'comment' == event.type.id:
349
+                        <span class="tracim-less-visible">${_('<strong>{}</strong> wrote:').format(event.owner.name)|n}</span>
350
+                    %else:
351
+                        <span class="tracim-less-visible">${_('{} by <strong>{}</strong>').format(event.label, event.owner.name)|n}</span>
352
+                    % endif
353
+
354
+                    <div class="pull-right text-right t-timeline-item-moment" title="${h.date_time(event.created)|n}">
355
+                        ${_('{delta} ago').format(delta=event.created_as_delta)}
317 356
 
318
-                        % if h.is_item_still_editable(item) and item.owner.id==user.id:
357
+                        % if h.is_item_still_editable(event) and event.owner.id==user.id:
319 358
                             <br/>
320
-##                            <div class="btn-group">
321
-                                <a class="t-timeline-comment-delete-button" href="${item.urls.delete}">
359
+                                <a class="t-timeline-comment-delete-button" href="${event.urls.delete}">
322 360
                                     ${_('delete')} ${ICON.FA('fa fa-trash-o')}
323
-##                                    ${TIM.ICO_TOOLTIP(16, 'status/user-trash-full', h.delete_label_for_item(item))}
324 361
                                 </a>
325
-##                            </div>
326 362
                         % endif
327 363
                     </div>
328 364
                 </h5>
329 365
                 <div class="t-timeline-item-content">
330
-                    <div>${item.content|n}</div>
366
+                    <div>${event.content|n}</div>
331 367
                     <br/>
332 368
                 </div>
333 369
             </div>
334 370
         </div>
335 371
     </div>
336 372
 </%def>
373
+
374
+<%def name="SECURED_HISTORY_VIRTUAL_EVENT_AS_TABLE_ROW(user, event, current_revision_id)">
375
+    <% warning_or_not = ('', 'warning')[current_revision_id==event.id] %>
376
+    <tr class="${warning_or_not}">
377
+        <td class="t-less-visible">
378
+            <span class="label label-default">${ICON.FA_FW(event.type.icon)} ${event.type.label}</span>
379
+        </td>
380
+        <td title="${h.date_time(event.created)|n}">${_('{delta} ago').format(delta=event.created_as_delta)}</td>
381
+        <td>${event.owner.name}</td>
382
+## FIXME - REMOVE                            <td>${event}</td>
383
+
384
+        % if 'comment' == event.type.id:
385
+            <td colspan="2">
386
+                ${event.content|n}
387
+            </td>
388
+        % else:
389
+
390
+            <td>
391
+                % if event.type.id in ('creation', 'edition', 'revision'):
392
+                    <a href="${'?revision_id={}'.format(event.id)}">${_('View revision')}</a>
393
+                % endif
394
+            </td>
395
+            <td class="t-less-visible" title="${_('Currently shown')}">
396
+                % if warning_or_not:
397
+                    ${ICON.FA_FW('fa fa-caret-left')}&nbsp;${_('shown')}
398
+                % endif
399
+            </td>
400
+        % endif
401
+    </tr>
402
+</%def>