xiaq avatar xiaq committed 54062fc

Separate "directory" and "file" items in the index view.

Comments (0)

Files changed (5)

MoinMoin/apps/frontend/views.py

     initials = list(set(initials))
     initials = sorted(initials)
 
-    index = item.get_index(startswith, selected_groups)
-    index = sorted(index, key=lambda e: e.relname.lower())
+    dirs, files = item.get_index(startswith, selected_groups)
+    # index = sorted(index, key=lambda e: e.relname.lower())
 
     item_names = item_name.split(u'/')
     return render_template(item.index_template,
                            item_names=item_names,
                            item_name=item_name,
-                           index=index,
+                           files=files,
+                           dirs=dirs,
                            initials=initials,
                            startswith=startswith,
                            form=form,

MoinMoin/items/__init__.py

         return form
 
 
-IndexEntry = namedtuple('IndexEntry', 'relname meta hassubitems')
+IndexEntry = namedtuple('IndexEntry', 'relname meta')
+
+MixedIndexEntry = namedtuple('MixedIndexEntry', 'relname meta hassubitems')
 
 class Item(object):
     """ Highlevel (not storage) Item, wraps around a storage Revision"""
     @timed()
     def make_flat_index(self, subitems):
         """
-        Create a list of IndexEntry from a list of subitems.
+        Create two IndexEntry lists - ``dirs`` and ``files`` - from a list of
+        subitems.
 
-        The resulting list contains only IndexEntry for *direct* subitems, e.g.
-        'foo' but not 'foo/bar'. When the latter is encountered, the former has
-        its `hassubitems` flag set in its IndexEntry.
+        Direct subitems are added to the ``files`` list.
 
-        When disconnected levels are detected, e.g. when there is foo/bar but no
-        foo, a dummy IndexEntry is created for the latter, with 'nonexistent'
-        itemtype and 'application/x.nonexistent' contenttype. Its `hassubitems`
-        flag is also set.
+        For indirect subitems, its ancestor which is a direct subitem is added
+        to the ``dirs`` list. Supposing current index root is 'foo' and when
+        'foo/bar/la' is encountered, 'foo/bar' is added to ``dirs``.
+
+        The direct subitem need not exist.
+
+        When both a subitem itself and some of its subitems are in the subitems
+        list, it appears in both ``files`` and ``dirs``.
         """
         prefix = self.subitems_prefix
         prefixlen = len(prefix)
-        index = []
-
-        # relnames of all encountered subitems
-        relnames = set()
-        # relnames of subitems that need to have `hassubitems` flag set (but didn't)
-        relnames_to_patch = set()
+        # IndexEntry instances of "file" subitems
+        files = []
+        # IndexEntry instances of "directory" subitems
+        dirs = []
+        added_dir_relnames = set()
 
         for rev in subitems:
             fullname = rev.meta[NAME]
                 # 'foo', and current item (`rev`) is 'foo/bar/lorem/ipsum',
                 # 'foo/bar' will be found.
                 direct_relname = relname.partition('/')[0]
-                direct_fullname = prefix + direct_relname
-                if direct_relname not in relnames:
-                    # Join disconnected level with a dummy IndexEntry.
-                    # NOTE: Patching the index when encountering a disconnected
-                    # subitem might break the ordering. e.g. suppose the global
-                    # index has ['lorem-', 'lorem/ipsum'] (thus 'lorem' is a
-                    # disconnected level; also, note that ord('-') < ord('/'))
-                    # the patched index will have lorem after lorem-, requiring
-                    # one more pass of sorting after generating the index.
-                    e = IndexEntry(direct_relname, DummyRev(DummyItem(direct_fullname)).meta, True)
-                    index.append(e)
-                    relnames.add(direct_relname)
-                else:
-                    relnames_to_patch.add(direct_relname)
+                if direct_relname not in added_dir_relnames:
+                    added_dir_relnames.add(direct_relname)
+                    direct_fullname = prefix + direct_relname
+                    direct_rev = get_storage_revision(direct_fullname)
+                    dirs.append(IndexEntry(direct_relname, direct_rev.meta))
             else:
-                e = IndexEntry(relname, rev.meta, False)
-                index.append(e)
-                relnames.add(relname)
+                files.append(IndexEntry(relname, rev.meta))
 
-        for i in xrange(len(index)):
-            if index[i].relname in relnames_to_patch:
-                index[i] = index[i]._replace(hassubitems=True)
-
-        return index
+        return dirs, files
 
     @timed()
     def filter_index(self, index, startswith=None, selected_groups=None):
         return index
 
     def get_index(self, startswith=None, selected_groups=None):
-        return self.filter_index(self.make_flat_index(self.get_subitem_revs()), startswith, selected_groups)
+        dirs, files = self.make_flat_index(self.get_subitem_revs())
+        return dirs, self.filter_index(files, startswith, selected_groups)
+
+    def get_mixed_index(self):
+        dirs, files = self.make_flat_index(self.get_subitem_revs())
+        dirs_dict = dict([(e.relname, MixedIndexEntry(*e, hassubitems=True)) for e in dirs])
+        index_dict = dict([(e.relname, MixedIndexEntry(*e, hassubitems=False)) for e in files])
+        index_dict.update(dirs_dict)
+        return sorted(index_dict.values())
 
     index_template = 'index.html'
 

MoinMoin/items/_tests/test_Item.py

 from MoinMoin.util import diff_html
 
 from MoinMoin._tests import become_trusted, update_item
-from MoinMoin.items import Item, NonExistent, IndexEntry
+from MoinMoin.items import Item, NonExistent, IndexEntry, MixedIndexEntry
 from MoinMoin.items.content import Binary, Text, Image, TransformableBitmapImage, MarkupItem
 from MoinMoin.constants.keys import ITEMTYPE, CONTENTTYPE, NAME, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION
 
 
-def build_index(basename, spec):
+def build_index(basename, relnames):
     """
-    Build an index by hand, useful as a test helper.
+    Build a list of IndexEntry by hand, useful as a test helper.
     """
-    return [(IndexEntry(relname, Item.create('/'.join((basename, relname))).meta, hassubitem))
+    return [(IndexEntry(relname, Item.create('/'.join((basename, relname))).meta))
+            for relname in relnames]
+
+def build_mixed_index(basename, spec):
+    """
+    Build a list of MixedIndexEntry by hand, useful as a test helper.
+
+    :spec is a list of (relname, hassubitem) tuples.
+    """
+    return [(MixedIndexEntry(relname, Item.create('/'.join((basename, relname))).meta, hassubitem))
             for relname, hassubitem in spec]
 
 class TestItem(object):
 
         # test Item.make_flat_index
         # TODO: test Item.get_subitem_revs
-        index = baseitem.make_flat_index(baseitem.get_subitem_revs())
-        assert index == build_index(basename, [
+        dirs, files = baseitem.make_flat_index(baseitem.get_subitem_revs())
+        assert dirs == build_index(basename, [u'cd', u'ij'])
+        assert files == build_index(basename, [u'ab', u'gh', u'ij', u'mn'])
+
+        # test Item.get_mixed_index
+        mixed_index = baseitem.get_mixed_index()
+        assert mixed_index == build_mixed_index(basename, [
             (u'ab', False),
             (u'cd', True),
             (u'gh', False),
             (u'ij', True),
-            (u'mn', False)
+            (u'mn', False),
         ])
 
         # test Item.filter_index
         # check filtered index when startswith param is passed
-        filtered = baseitem.filter_index(index, startswith=u'a')
-        assert filtered == build_index(basename, [(u'ab', False)])
-
-        # check that virtual container item is returned with startswith
-        filtered = baseitem.filter_index(index, startswith=u'c')
-        assert filtered == build_index(basename, [(u'cd', True)])
+        filtered_files = baseitem.filter_index(files, startswith=u'a')
+        assert filtered_files == build_index(basename, [u'ab'])
 
         # check filtered index when contenttype_groups is passed
         ctgroups = ["image items"]
-        filtered = baseitem.filter_index(index, selected_groups=ctgroups)
-        assert filtered == build_index(basename, [(u'mn', False)])
+        filtered_files = baseitem.filter_index(files, selected_groups=ctgroups)
+        assert filtered_files == build_index(basename, [u'mn'])
 
         # If we ask for text/plain type, should Foo/cd be returned?
 

MoinMoin/templates/index.html

         </div>
     </div>
     <div class="moin-index-separator"></div>
-    {% if index %}
+    {% if files or dirs %}
     <div id="moin-initials">
         {% if not startswith %}
             <a class="selected" href="{{ url_for('frontend.index', item_name=item_name) }}">{{ _("All") }}</a>
             {% endif %}
         {% endfor %}
     </div>
-    <div class="moin-item-index">
-        {% for e in index %}
-            {{ render_entry(e) }}
-        {% endfor %}
-    </div>
-    <div class="moin-clr"></div>
+    {% if dirs %}
+        <div class="moin-item-index">
+            <p>{{ _("These items have subitems that match your filter:") }}</p>
+            {% for e in dirs %}
+                {{ render_entry(e) }}
+            {% endfor %}
+        </div>
+        <div class="moin-clr"></div>
+        <hr />
+    {% endif %}
+    {% if files %}
+        <div class="moin-item-index">
+            {% for e in files %}
+                {{ render_entry(e) }}
+            {% endfor %}
+        </div>
+        <div class="moin-clr"></div>
+    {% endif %}
     {% endif %}
     <div id="popup">
         <div id="popup-for-action" class="popup-container">

MoinMoin/themes/__init__.py

         """
         from MoinMoin.items import Item
         item = Item.create(item_name)
-        item_index = item.get_index()
-        # Sort items by whether or not they have children, then by name:
-        item_index = sorted(item_index, key=attrgetter('hassubitems', 'relname'))
-        return item_index
+        return item.get_mixed_index()
 
     def userhome(self):
         """
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.