浏览代码

add search engine based on a ilike sql query and a filterable result page

Damien ACCORSI 9 年前
父节点
当前提交
d82731c2b0

+ 3 - 30
tracim/tracim/controllers/__init__.py 查看文件

@@ -126,40 +126,13 @@ class TIMRestController(RestController, BaseController):
126 126
 
127 127
 class TIMRestControllerWithBreadcrumb(TIMRestController):
128 128
 
129
-    def get_breadcrumb(self, folder_id=None) -> [BreadcrumbItem]:
129
+    def get_breadcrumb(self, item_id=None) -> [BreadcrumbItem]:
130 130
         """
131 131
         TODO - Remove this and factorize it with other get_breadcrumb_xxx methods
132
-        :param folder_id:
132
+        :param item_id: an item id (item may be normal content or folder
133 133
         :return:
134 134
         """
135
-        workspace = tmpl_context.workspace
136
-        workspace_id = tmpl_context.workspace_id
137
-        breadcrumb = []
138
-
139
-        breadcrumb.append(BreadcrumbItem(ContentType.icon(ContentType.FAKE_Dashboard), _('Workspaces'), tg.url('/workspaces')))
140
-        breadcrumb.append(BreadcrumbItem(ContentType.icon(ContentType.FAKE_Workspace), workspace.label, tg.url('/workspaces/{}'.format(workspace.workspace_id))))
141
-
142
-        content_api = ContentApi(tmpl_context.current_user)
143
-        if folder_id:
144
-            breadcrumb_folder_items = []
145
-            current_item = content_api.get_one(folder_id, ContentType.Any, workspace)
146
-            is_active = True
147
-
148
-            while current_item:
149
-                breadcrumb_item = BreadcrumbItem(ContentType.icon(current_item.type),
150
-                                                 current_item.label,
151
-                                                 tg.url('/workspaces/{}/folders/{}'.format(workspace_id, current_item.content_id)),
152
-                                                 is_active)
153
-                is_active = False # the first item is True, then all other are False => in the breadcrumb, only the last item is "active"
154
-                breadcrumb_folder_items.append(breadcrumb_item)
155
-                current_item = current_item.parent
156
-
157
-            for item in reversed(breadcrumb_folder_items):
158
-                breadcrumb.append(item)
159
-
160
-
161
-        return breadcrumb
162
-
135
+        return ContentApi(tmpl_context.current_user).build_breadcrumb(tmpl_context.workspace, item_id)
163 136
 
164 137
 
165 138
 class TIMWorkspaceContentRestController(TIMRestControllerWithBreadcrumb):

+ 0 - 1
tracim/tracim/controllers/content.py 查看文件

@@ -762,7 +762,6 @@ class UserWorkspaceFolderRestController(TIMRestControllerWithBreadcrumb):
762 762
     @tg.require(current_user_is_content_manager())
763 763
     @tg.expose()
764 764
     def put_archive_undo(self, item_id):
765
-        print('AGAGA')
766 765
         # TODO - CHECK RIGHTS
767 766
         item_id = int(item_id)
768 767
         content_api = ContentApi(tmpl_context.current_user, True, True) # Here we do not filter deleted items

+ 25 - 0
tracim/tracim/controllers/root.py 查看文件

@@ -123,3 +123,28 @@ class RootController(StandardController):
123 123
         fake_api = Context(CTX.CURRENT_USER).toDict({'current_user': current_user_content})
124 124
 
125 125
         return DictLikeClass(fake_api=fake_api)
126
+
127
+    @require(predicates.not_anonymous())
128
+    @expose('tracim.templates.search')
129
+    def search(self, keywords = ''):
130
+        from tracim.lib.content import ContentApi
131
+
132
+        user = tmpl_context.current_user
133
+        api = ContentApi(user)
134
+
135
+        items = []
136
+        keyword_list = api.get_keywords(keywords)
137
+
138
+        result = api.search(keyword_list)
139
+        if result:
140
+            items = result.limit(ContentApi.SEARCH_DEFAULT_RESULT_NB).all()
141
+
142
+        current_user_content = Context(CTX.CURRENT_USER).toDict(user)
143
+        fake_api = Context(CTX.CURRENT_USER).toDict({'current_user': current_user_content})
144
+
145
+        search_results = Context(CTX.SEARCH).toDict(items, 'results', 'result_nb')
146
+        search_results.keywords = keyword_list
147
+
148
+        return DictLikeClass(fake_api=fake_api, search=search_results)
149
+
150
+

+ 0 - 1
tracim/tracim/controllers/workspace.py 查看文件

@@ -80,7 +80,6 @@ class UserWorkspaceRestController(TIMRestController):
80 80
             dictified_workspaces = Context(CTX.MENU_API).toDict(workspaces, 'd')
81 81
             return dictified_workspaces
82 82
 
83
-
84 83
         allowed_content_types = ContentType.allowed_types_from_str(folder_allowed_content_types)
85 84
         ignored_item_ids = [int(ignore_id)] if ignore_id else []
86 85
 

+ 1 - 1
tracim/tracim/lib/base.py 查看文件

@@ -113,7 +113,7 @@ class Logger(object):
113 113
         self._logger = logging.getLogger(self._name)
114 114
 
115 115
     def _txt(self, instance_or_class):
116
-        if instance_or_class.__class__.__name__=='type':
116
+        if instance_or_class.__class__.__name__ in ('function', 'type'):
117 117
             return instance_or_class.__name__
118 118
         else:
119 119
             return instance_or_class.__class__.__name__

+ 96 - 4
tracim/tracim/lib/content.py 查看文件

@@ -2,15 +2,25 @@
2 2
 
3 3
 __author__ = 'damien'
4 4
 
5
+import re
6
+
5 7
 import tg
8
+from tg.i18n import ugettext as _
6 9
 
10
+import sqlalchemy
11
+from sqlalchemy.orm import aliased
12
+from sqlalchemy.orm import joinedload
7 13
 from sqlalchemy.orm.attributes import get_history
8 14
 from sqlalchemy import not_
15
+from sqlalchemy import or_
9 16
 from tracim.lib import cmp_to_key
10 17
 from tracim.lib.notifications import NotifierFactory
11 18
 from tracim.model import DBSession
12 19
 from tracim.model.auth import User
13
-from tracim.model.data import ContentStatus, ContentRevisionRO, ActionDescription
20
+from tracim.model.data import ActionDescription
21
+from tracim.model.data import BreadcrumbItem
22
+from tracim.model.data import ContentStatus
23
+from tracim.model.data import ContentRevisionRO
14 24
 from tracim.model.data import Content
15 25
 from tracim.model.data import ContentType
16 26
 from tracim.model.data import NodeTreeItem
@@ -48,6 +58,10 @@ def compare_tree_items_for_sorting_by_type_and_name(item1: NodeTreeItem, item2:
48 58
 
49 59
 class ContentApi(object):
50 60
 
61
+    SEARCH_SEPARATORS = ',| '
62
+    SEARCH_DEFAULT_RESULT_NB = 50
63
+
64
+
51 65
     def __init__(self, current_user: User, show_archived=False, show_deleted=False, all_content_in_treeview=True):
52 66
         self._user = current_user
53 67
         self._show_archived = show_archived
@@ -71,6 +85,45 @@ class ContentApi(object):
71 85
         content_list.sort(key=cmp_to_key(compare_content_for_sorting_by_type_and_name))
72 86
         return content_list
73 87
 
88
+    def build_breadcrumb(self, workspace, item_id=None, skip_root=False) -> [BreadcrumbItem]:
89
+        """
90
+        TODO - Remove this and factorize it with other get_breadcrumb_xxx methods
91
+        :param item_id: an item id (item may be normal content or folder
92
+        :return:
93
+        """
94
+        workspace_id = workspace.workspace_id
95
+        breadcrumb = []
96
+
97
+        if not skip_root:
98
+            breadcrumb.append(BreadcrumbItem(ContentType.icon(ContentType.FAKE_Dashboard), _('Workspaces'), tg.url('/workspaces')))
99
+        breadcrumb.append(BreadcrumbItem(ContentType.icon(ContentType.FAKE_Workspace), workspace.label, tg.url('/workspaces/{}'.format(workspace.workspace_id))))
100
+
101
+        if item_id:
102
+            breadcrumb_folder_items = []
103
+            current_item = self.get_one(item_id, ContentType.Any, workspace)
104
+            is_active = True
105
+            if current_item.type==ContentType.Folder:
106
+                next_url = tg.url('/workspaces/{}/folders/{}'.format(workspace_id, current_item.content_id))
107
+            else:
108
+                next_url = tg.url('/workspaces/{}/folders/{}/{}s/{}'.format(workspace_id, current_item.parent_id, current_item.type, current_item.content_id))
109
+
110
+            while current_item:
111
+                breadcrumb_item = BreadcrumbItem(ContentType.icon(current_item.type),
112
+                                                 current_item.label,
113
+                                                 next_url,
114
+                                                 is_active)
115
+                is_active = False # the first item is True, then all other are False => in the breadcrumb, only the last item is "active"
116
+                breadcrumb_folder_items.append(breadcrumb_item)
117
+                current_item = current_item.parent
118
+                if current_item:
119
+                    # In last iteration, the parent is None, and there is no more breadcrumb item to build
120
+                    next_url = tg.url('/workspaces/{}/folders/{}'.format(workspace_id, current_item.content_id))
121
+
122
+            for item in reversed(breadcrumb_folder_items):
123
+                breadcrumb.append(item)
124
+
125
+
126
+        return breadcrumb
74 127
 
75 128
     def _base_query(self, workspace: Workspace=None):
76 129
         result = DBSession.query(Content)
@@ -79,10 +132,16 @@ class ContentApi(object):
79 132
             result = result.filter(Content.workspace_id==workspace.workspace_id)
80 133
 
81 134
         if not self._show_deleted:
82
-            result = result.filter(Content.is_deleted==False)
135
+            parent = aliased(Content)
136
+            result.join(parent, Content.parent).\
137
+                filter(Content.is_deleted==False).\
138
+                filter(parent.is_deleted==False)
83 139
 
84 140
         if not self._show_archived:
85
-            result = result.filter(Content.is_archived==False)
141
+            parent = aliased(Content)
142
+            result.join(parent, Content.parent).\
143
+                filter(Content.is_archived==False).\
144
+                filter(parent.is_archived==False)
86 145
 
87 146
         return result
88 147
 
@@ -110,7 +169,7 @@ class ContentApi(object):
110 169
             all()
111 170
 
112 171
         if not filter_by_allowed_content_types or len(filter_by_allowed_content_types)<=0:
113
-            return folders
172
+            filter_by_allowed_content_types = ContentType.allowed_types_for_folding()
114 173
 
115 174
         # Now, the case is to filter folders by the content that they are allowed to contain
116 175
         result = []
@@ -118,6 +177,7 @@ class ContentApi(object):
118 177
             for allowed_content_type in filter_by_allowed_content_types:
119 178
                 if folder.type==ContentType.Folder and folder.properties['allowed_content'][allowed_content_type]==True:
120 179
                     result.append(folder)
180
+                    break
121 181
 
122 182
         return result
123 183
 
@@ -295,3 +355,35 @@ class ContentApi(object):
295 355
         if do_notify:
296 356
             NotifierFactory.create(self._user).notify_content_update(content)
297 357
 
358
+
359
+    def get_keywords(self, search_string, search_string_separators=None) -> [str]:
360
+        """
361
+        :param search_string: a list of coma-separated keywords
362
+        :return: a list of str (each keyword = 1 entry
363
+        """
364
+
365
+        search_string_separators = search_string_separators or ContentApi.SEARCH_SEPARATORS
366
+
367
+        keywords = []
368
+        if search_string:
369
+            keywords = [keyword.strip() for keyword in re.split(search_string_separators, search_string)]
370
+
371
+        return keywords
372
+
373
+    def search(self, keywords: [str]) -> sqlalchemy.orm.query.Query:
374
+        """
375
+        :return: a sorted list of Content items
376
+        """
377
+
378
+        if len(keywords)<=0:
379
+            return None
380
+
381
+        filter_group_label = list(Content.label.ilike('%{}%'.format(keyword)) for keyword in keywords)
382
+        filter_group_desc = list(Content.description.ilike('%{}%'.format(keyword)) for keyword in keywords)
383
+        title_keyworded_items = self._base_query().\
384
+            filter(or_(*(filter_group_label+filter_group_desc))).\
385
+            options(joinedload('children')).\
386
+            options(joinedload('parent'))
387
+
388
+        return title_keyworded_items
389
+

+ 11 - 0
tracim/tracim/lib/helpers.py 查看文件

@@ -180,5 +180,16 @@ def is_item_still_editable(item):
180 180
             return True
181 181
     return False
182 182
 
183
+def shorten(text: str, maxlength: int, add_three_points=True) -> str:
184
+
185
+    result = text
186
+    if len(text)>maxlength:
187
+        result = text[:maxlength]
188
+
189
+        if add_three_points:
190
+            result += '…'
191
+
192
+    return result
193
+
183 194
 from tracim.config.app_cfg import CFG as CFG_ORI
184 195
 CFG = CFG_ORI.get_instance() # local CFG var is an instance of CFG class found in app_cfg

+ 17 - 0
tracim/tracim/lib/utils.py 查看文件

@@ -0,0 +1,17 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import time
4
+
5
+from tracim.lib.base import logger
6
+
7
+def exec_time_monitor():
8
+    def decorator_func(func):
9
+        def wrapper_func(*args, **kwargs):
10
+            start = time.time()
11
+            retval = func(*args, **kwargs)
12
+            end = time.time()
13
+            logger.debug(func, 'exec time: {} seconds'.format(end-start))
14
+            return retval
15
+        return wrapper_func
16
+    return decorator_func
17
+

+ 6 - 1
tracim/tracim/model/data.py 查看文件

@@ -311,11 +311,16 @@ class ContentType(object):
311 311
         return [cls.Folder, cls.File, cls.Comment, cls.Thread, cls.Page]
312 312
 
313 313
     @classmethod
314
+    def allowed_types_for_folding(cls):
315
+        # This method is used for showing only "main" types in the left-side treeview
316
+        return [cls.Folder, cls.File, cls.Thread, cls.Page]
317
+
318
+    @classmethod
314 319
     def allowed_types_from_str(cls, allowed_types_as_string: str):
315 320
         allowed_types = []
316 321
         # HACK - THIS
317 322
         for item in allowed_types_as_string.split(ContentType._STRING_LIST_SEPARATOR):
318
-            if item and item in ContentType.allowed_types():
323
+            if item and item in ContentType.allowed_types_for_folding():
319 324
                 allowed_types.append(item)
320 325
         return allowed_types
321 326
 

+ 63 - 12
tracim/tracim/model/serializers.py 查看文件

@@ -1,10 +1,12 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 import types
3 3
 
4
+from bs4 import BeautifulSoup
4 5
 import tg
5 6
 from tg.util import LazyString
6 7
 from tracim.lib.base import logger
7
-from tracim.model.auth import Group, Profile
8
+from tracim.lib.utils import exec_time_monitor
9
+from tracim.model.auth import Profile
8 10
 from tracim.model.auth import User
9 11
 from tracim.model.data import BreadcrumbItem, ActionDescription
10 12
 from tracim.model.data import ContentStatus
@@ -47,7 +49,6 @@ class pod_serializer(object):
47 49
         Context.register_converter(self.context, self.model_class, func)
48 50
         return func
49 51
 
50
-
51 52
 class ContextConverterNotFoundException(Exception):
52 53
     def __init__(self, context_string, model_class):
53 54
         message = 'converter not found (context: {0} - model: {1})'.format(context_string, model_class.__name__)
@@ -68,6 +69,7 @@ class CTX(object):
68 69
     MENU_API_BUILD_FROM_TREE_ITEM = 'MENU_API_BUILD_FROM_TREE_ITEM'
69 70
     PAGE = 'PAGE'
70 71
     PAGES = 'PAGES'
72
+    SEARCH = 'SEARCH'
71 73
     THREAD = 'THREAD'
72 74
     THREADS = 'THREADS'
73 75
     USER = 'USER'
@@ -412,17 +414,7 @@ def serialize_node_for_page(item: Content, context: Context):
412 414
 
413 415
     if item.type==ContentType.Folder:
414 416
         return Context(CTX.DEFAULT).toDict(item)
415
-    ### CODE BELOW IS REPLACED BY THE TWO LINES UP ^^
416
-    # 2014-10-08 - IF YOU FIND THIS COMMENT, YOU CAn REMOVE THE CODE
417
-    #
418
-    #if item.type==ContentType.Folder:
419
-    #    value = DictLikeClass(
420
-    #        id = item.content_id,
421
-    #        label = item.label,
422
-    #    )
423
-    #    return value
424 417
 
425
-    raise NotImplementedError
426 418
 
427 419
 
428 420
 @pod_serializer(Content, CTX.THREADS)
@@ -556,6 +548,65 @@ def serialize_content_for_workspace_and_folder(content: Content, context: Contex
556 548
     return result
557 549
 
558 550
 
551
+from tg import cache
552
+
553
+@pod_serializer(Content, CTX.SEARCH)
554
+def serialize_content_for_search_result(content: Content, context: Context):
555
+
556
+    def serialize_it():
557
+        nonlocal content
558
+
559
+        if content.type == ContentType.Comment:
560
+            logger.info('serialize_content_for_search_result', 'Serializing parent class {} instead of {} [content #{}]'.format(content.parent.type, content.type, content.content_id))
561
+            content = content.parent
562
+
563
+        data_container = content
564
+
565
+        if content.revision_to_serialize>0:
566
+            for revision in content.revisions:
567
+                if revision.revision_id==content.revision_to_serialize:
568
+                    data_container = revision
569
+                    break
570
+
571
+        # FIXME - D.A. - 2015-02-23 - This import should not be there...
572
+        from tracim.lib.content import ContentApi
573
+        breadcrumbs = ContentApi(None).build_breadcrumb(data_container.workspace, data_container.content_id, skip_root=True)
574
+
575
+        last_comment_datetime = data_container.updated
576
+        comments = data_container.get_comments()
577
+        if comments:
578
+            last_comment_datetime = max(last_comment_datetime, max(comment.updated for comment in comments))
579
+
580
+        result = DictLikeClass(
581
+            id = content.content_id,
582
+            parent = context.toDict(content.parent),
583
+            workspace = context.toDict(content.workspace),
584
+            type = content.type,
585
+
586
+            content = data_container.description,
587
+            content_raw = BeautifulSoup(data_container.description).text,
588
+
589
+            created = data_container.created,
590
+            label = data_container.label,
591
+            icon = ContentType.icon(content.type),
592
+            owner = context.toDict(data_container.owner),
593
+            status = context.toDict(data_container.get_status()),
594
+            breadcrumb = context.toDict(breadcrumbs),
595
+            last_activity = last_comment_datetime
596
+        )
597
+
598
+        if content.type==ContentType.File:
599
+            result.label = content.label.__str__() if content.label else content.file_name.__str__()
600
+
601
+        if not result.label or ''==result.label:
602
+            result.label = 'No title'
603
+
604
+        return result
605
+
606
+    return serialize_it()
607
+
608
+
609
+
559 610
 ########################################################################################################################
560 611
 # ContentStatus
561 612
 

+ 17 - 3
tracim/tracim/public/assets/css/dashboard.css 查看文件

@@ -178,12 +178,26 @@ table.user_content, table.user_content td  {
178 178
 
179 179
 table.first_row_headers tr:first-child td {
180 180
     font-weight: bold;
181
-    background-color: #EEE;
181
+    background-color: #EEE;
182 182
     align: center;
183 183
 }
184 184
 
185 185
 table.first_column_headers tr td:first-child {
186 186
     font-weight: bold;
187 187
     background-color: #EEE;
188
-    align: center;
189
-}
188
+    align: center;
189
+}
190
+
191
+.search-result-item-breadcrumb {
192
+    margin-top: -1em;
193
+}
194
+
195
+.search-result-item-breadcrumb a {
196
+    color: #090;
197
+}
198
+
199
+#search-result-dynamic-resume {
200
+    color: #090;
201
+    margin-top: -1em;
202
+    margin-bottom: 0;
203
+}

二进制
tracim/tracim/public/assets/icons/32x32/emblems/emblem-checked.png 查看文件


二进制
tracim/tracim/public/assets/icons/32x32/places/jstree-folder.png 查看文件


二进制
tracim/tracim/public/assets/icons/32x32/status/status-open.png 查看文件


二进制
tracim/tracim/public/assets/icons/32x32/status/status-outdated.png 查看文件


+ 7 - 0
tracim/tracim/templates/master_authenticated.mak 查看文件

@@ -113,6 +113,13 @@
113 113
                                 </ul>
114 114
                             </li>
115 115
                         % endif
116
+                        
117
+                        <form class="navbar-form navbar-left" role="search" action="${tg.url('/search?')}">
118
+                            <div class="form-group">
119
+                                <input type="text" class="form-control" placeholder="${_('Search for...')}" name="keywords" value="${','.join(search.keywords) if search else ''}">
120
+                            </div>
121
+                            <button type="submit" class="btn btn-default">${_('Search')}</button>
122
+                        </form>
116 123
                     </ul>
117 124
                 % endif
118 125
 

+ 77 - 0
tracim/tracim/templates/search.mak 查看文件

@@ -0,0 +1,77 @@
1
+<%inherit file="local:templates.master_authenticated_left_treeview_right_toolbar"/>
2
+
3
+<%namespace name="TIM" file="tracim.templates.pod"/>
4
+<%namespace name="TOOLBAR" file="tracim.templates.search_toolbars"/>
5
+<%namespace name="FORMS" file="tracim.templates.user_workspace_forms"/>
6
+<%namespace name="WIDGETS" file="tracim.templates.user_workspace_widgets"/>
7
+
8
+<%def name="title()">${_('My workspaces')}</%def>
9
+
10
+<%def name="SIDEBAR_LEFT_CONTENT()">
11
+    <h4>${_('Workspaces')}</h4>
12
+    ${WIDGETS.TREEVIEW('sidebar-left-menu', '__')}
13
+    <hr/>
14
+</%def>
15
+
16
+<%def name="SIDEBAR_RIGHT_CONTENT()">
17
+    ${TOOLBAR.SECURED_SEARCH(fake_api.current_user)}
18
+</%def>
19
+
20
+<%def name="REQUIRED_DIALOGS()">
21
+</%def>
22
+
23
+############################################################################
24
+##
25
+## PAGE CONTENT BELOW
26
+##
27
+############################################################################
28
+
29
+<div class="row">
30
+    <h1 class="page-header">
31
+        ${TIM.ICO(32, 'actions/system-search')} ${_('Search results')}
32
+        <small>
33
+            ${_('<span class="badge">{}</span> results for keywords: '.format(search.result_nb))|n}
34
+            % for keyword in search.keywords:
35
+                <span class="label label-default">${keyword}</span>
36
+            % endfor
37
+        </small>
38
+    </h1>
39
+</div>
40
+<div class="row">
41
+    <p id="search-result-dynamic-resume">${_('loading...')}</p>
42
+</div>
43
+<div class="row">
44
+    <div id='application-document-panel'>
45
+        <ol class="search-results">
46
+            % for item in search.results:
47
+                <li class="search-result-type-${item.type} search-result-status-${item.status.id}">
48
+                    <h4>
49
+                        <a href="${item.breadcrumb[-1].url}">${TIM.ICO(16, item.icon)} ${item.label}</a>
50
+                         &nbsp;&nbsp;<span style="color: #AAA;">—</span>
51
+                         <button type="button" class="btn btn-default btn-disabled btn-link ">
52
+                             ${TIM.ICO(16, item['status']['icon'])}&nbsp;
53
+                             <span class="${item.status.css}">${item.status.label}</span>
54
+                         </button>
55
+                    </h4>
56
+                    <p style="margin-bottom: 2em;" class="search-result-item-breadcrumb">
57
+                        <i style="color: #AAA;" class="fa fa-fw fa-map-marker"></i>
58
+                        % for bread in item.breadcrumb:
59
+                            / <a href="${bread.url}">${bread.label}</a>
60
+                        % endfor
61
+                        <br/>
62
+                        <span
63
+                            style="color: #AAA;"
64
+##                            rel="tooltip"
65
+##                            data-toggle="tooltip"
66
+##                            data-placement="top"
67
+                            title="${_('Last known activty')}" ><i class="fa fa-fw fa-calendar"></i> ${h.date_time_in_long_format(item.last_activity, '%d %B %Y at %I:%M')}</span> &mdash;
68
+                            ${h.shorten(item.content_raw, 300)}
69
+                    </p>
70
+
71
+##                    <hr style="width: 33%; margin-left: 0;"/>
72
+                </li>
73
+            % endfor
74
+        </ol>
75
+    </div>
76
+</div>
77
+

+ 83 - 0
tracim/tracim/templates/search_toolbars.mak 查看文件

@@ -0,0 +1,83 @@
1
+<%namespace name="TIM" file="tracim.templates.pod"/>
2
+
3
+<%def name="SECURED_SEARCH(user)">
4
+    <%
5
+        content_types = [
6
+            ('page', 'mimetypes/text-html'),
7
+            ('file', 'status/mail-attachment'),
8
+            ('thread', 'apps/internet-group-chat'),
9
+            ('folder', 'places/jstree-folder')
10
+        ]
11
+
12
+        statuses = [
13
+            ('open', 'status/status-open'),
14
+            ('closed-validated', 'emblems/emblem-checked'),
15
+            ('closed-cancelled', 'emblems/emblem-unreadable'),
16
+            ('closed-deprecated', 'status/status-outdated')
17
+        ]
18
+    %>
19
+    <div class="btn-group btn-group-vertical" data-toggle="buttons">
20
+        % for content_type, icon in content_types:
21
+            <label class="btn btn-default active search-result-filter-button" id='show-hide-search-result-of-type-${content_type}'>
22
+                <input type="checkbox" autocomplete="off" checked> ${TIM.ICO(32, icon)}
23
+            </label>
24
+        % endfor
25
+    </div>
26
+    <p></p>
27
+    <div class="btn-group btn-group-vertical" data-toggle="buttons">
28
+        % for status, icon in statuses:
29
+            <label class="btn btn-default active search-result-filter-button" id='show-hide-search-result-with-status-${status}'>
30
+                <input type="checkbox" autocomplete="off" checked> ${TIM.ICO(32, icon)}
31
+            </label>
32
+        % endfor
33
+    </div>
34
+    <p></p>
35
+    
36
+    <script>
37
+        $(document).ready(function() {
38
+            % for content_type, icon in content_types: # python code
39
+                $('#show-hide-search-result-of-type-${content_type}').click(function() {
40
+                    if ($('#show-hide-search-result-of-type-${content_type}').hasClass('active')) {
41
+                        $('.search-result-type-${content_type}').hide();
42
+                    } else {
43
+                        $('.search-result-type-${content_type}').show();
44
+                    }
45
+                });
46
+            % endfor # python code
47
+
48
+            % for status, icon in statuses: # python code
49
+                $('#show-hide-search-result-with-status-${status}').click(function() {
50
+                    console.log('clieck')
51
+                    if ($('#show-hide-search-result-with-status-${status}').hasClass('active')) {
52
+                        $('.search-result-status-${status}').hide();
53
+                    } else {
54
+                        $('.search-result-status-${status}').show();
55
+                    }
56
+                });
57
+            % endfor # python code
58
+            
59
+            function refresh_search_result_count() {
60
+                var itemNb = $('ol.search-results > li').length;
61
+                var visibleItemNb = $('ol.search-results > li:visible').length;
62
+                var message = "${_('Showing {0} filtered items of {1}')}"
63
+                if(visibleItemNb<=1) {
64
+                    message = "${_('Showing {0} filtered item of a total of {1}')}"
65
+                } else if (visibleItemNb==itemNb) {
66
+                    message = "${_('Showing all items. You can filter by clicking right toolbar buttons.')}"
67
+                }
68
+
69
+                message = message.replace('{0}', visibleItemNb)
70
+                message = message.replace('{1}', itemNb)
71
+                $('#search-result-dynamic-resume').html(message);
72
+            }
73
+
74
+            $('.search-result-filter-button').click(function() {
75
+                refresh_search_result_count();
76
+            });
77
+            
78
+            refresh_search_result_count();
79
+        });
80
+    </script>
81
+
82
+</%def>
83
+