Peter Nixon avatar Peter Nixon committed 8ebd3c9

Add photolog

Comments (0)

Files changed (22)

apps/photolog/__init__.py

+VERSION = (3, 0, 'b')

apps/photolog/admin.py

+""" Newforms Admin configuration for Photologue
+
+"""
+from django.contrib import admin
+from models import *
+
+class GalleryAdmin(admin.ModelAdmin):
+    list_display = ('title', 'date_added', 'photo_count', 'is_public')
+    list_filter = ['date_added', 'is_public']
+    date_hierarchy = 'date_added'
+    prepopulated_fields = {'title_slug': ('title',)}
+    filter_horizontal = ('photos',)
+
+class PhotoAdmin(admin.ModelAdmin):
+    list_display = ('title', 'date_added', 'is_public', 'view_count', 'admin_thumbnail_view')
+    list_filter = ['date_added', 'is_public']
+    list_per_page = 10
+    prepopulated_fields = {'title_slug': ('title',)}
+
+
+admin.site.register(Gallery, GalleryAdmin)
+admin.site.register(GalleryUpload)
+admin.site.register(Photo, PhotoAdmin)

apps/photolog/models.py

+import os
+import random
+import zipfile
+
+from datetime import datetime
+from django.conf import settings
+from django.core.files.base import ContentFile
+from django.core.files.storage import FileSystemStorage
+from django.core.urlresolvers import reverse
+from django.db import models
+from django.template.defaultfilters import slugify
+from django.utils.translation import ugettext_lazy as _
+
+from imagekit.models import ImageModel
+from imagekit.lib import Image
+
+
+# Allow user to specify their own spec module in their Django setting file
+PHOTOLOG_SPEC_MODULE = getattr(settings, 'PHOTOLOG_SPEC_MODULE', 'photolog.specs')
+
+
+class Gallery(models.Model):
+    date_added = models.DateTimeField(_('date published'), default=datetime.now)
+    title = models.CharField(_('title'), max_length=100, unique=True)
+    title_slug = models.SlugField(_('title slug'), unique=True,
+                                  help_text=_('A "slug" is a unique URL-friendly title for an object.'))
+    description = models.TextField(_('description'), blank=True)
+    is_public = models.BooleanField(_('is public'), default=True,
+                                    help_text=_('Public galleries will be displayed in the default views.'))
+    photos = models.ManyToManyField('Photo', related_name='galleries', verbose_name=_('photos'),
+                                    null=True, blank=True)
+
+    class Meta:
+        ordering = ['-date_added']
+        get_latest_by = 'date_added'
+        verbose_name = _('gallery')
+        verbose_name_plural = _('galleries')
+
+    def __unicode__(self):
+        return self.title
+
+    def __str__(self):
+        return self.__unicode__()
+
+    def get_absolute_url(self):
+        return reverse('pl-gallery', args=[self.title_slug])
+
+    def latest(self, limit=0, public=True):
+        if limit == 0:
+            limit = self.photo_count()
+        if public:
+            return self.public()[:limit]
+        else:
+            return self.photos.all()[:limit]
+
+    def sample(self, count=0, public=True):
+        if count == 0 or count > self.photo_count():
+            count = self.photo_count()
+        if public:
+            photo_set = self.public()
+        else:
+            photo_set = self.photos.all()
+        return random.sample(photo_set, count)
+
+    def photo_count(self, public=True):
+        if public:
+            return self.public().count()
+        else:
+            return self.photos.all().count()
+    photo_count.short_description = _('count')
+
+    def public(self):
+        return self.photos.filter(is_public=True)
+
+
+class GalleryUpload(models.Model):
+    zip_file = models.FileField(_('images file (.zip)'),
+                                upload_to='photolog/tmp',
+                                storage=FileSystemStorage(),
+                                help_text=_('Select a .zip file of images to upload into a new Gallery.'))
+    gallery = models.ForeignKey(Gallery, null=True, blank=True, help_text=_('Select a gallery to add these images to. leave this empty to create a new gallery from the supplied title.'))
+    title = models.CharField(_('title'), max_length=75, help_text=_('All photos in the gallery will be given a title made up of the gallery title + a sequential number.'))
+    caption = models.TextField(_('caption'), blank=True, help_text=_('Caption will be added to all photos.'))
+    description = models.TextField(_('description'), blank=True, help_text=_('A description of this Gallery.'))
+    is_public = models.BooleanField(_('is public'), default=True, help_text=_('Uncheck this to make the uploaded gallery and included photographs private.'))
+
+    class Meta:
+        verbose_name = _('gallery upload')
+        verbose_name_plural = _('gallery uploads')
+
+    def save(self, *args, **kwargs):
+        super(GalleryUpload, self).save(*args, **kwargs)
+        gallery = self.process_zipfile()
+        super(GalleryUpload, self).delete()
+        return gallery
+
+    def process_zipfile(self):
+        if os.path.isfile(self.zip_file.path):
+            # TODO: implement try-except here
+            zip = zipfile.ZipFile(self.zip_file.path)
+            bad_file = zip.testzip()
+            if bad_file:
+                raise Exception('"%s" in the .zip archive is corrupt.' % bad_file)
+            count = 1
+            if self.gallery:
+                gallery = self.gallery
+            else:
+                gallery = Gallery.objects.create(title=self.title,
+                                                 title_slug=slugify(self.title),
+                                                 description=self.description,
+                                                 is_public=self.is_public)
+            from cStringIO import StringIO
+            for filename in zip.namelist():
+                if filename.startswith('__'): # do not process meta files
+                    continue
+                data = zip.read(filename)
+                if len(data):
+                    try:
+                        # the following is taken from django.newforms.fields.ImageField:
+                        #  load() is the only method that can spot a truncated JPEG,
+                        #  but it cannot be called sanely after verify()
+                        trial_image = Image.open(StringIO(data))
+                        trial_image.load()
+                        # verify() is the only method that can spot a corrupt PNG,
+                        #  but it must be called immediately after the constructor
+                        trial_image = Image.open(StringIO(data))
+                        trial_image.verify()
+                    except Exception, e:
+                        # if a "bad" file is found we just skip it.
+                        raise e
+                        continue
+                    while 1:
+                        title = ' '.join([self.title, str(count)])
+                        slug = slugify(title)
+                        try:
+                            p = Photo.objects.get(title_slug=slug)
+                        except Photo.DoesNotExist:
+                            photo = Photo(title=title,
+                                          title_slug=slug,
+                                          caption=self.caption,
+                                          is_public=self.is_public)
+                            photo.image.save(filename, ContentFile(data))
+                            gallery.photos.add(photo)
+                            count = count + 1
+                            break
+                        count = count + 1
+            zip.close()
+            return gallery
+
+
+class Photo(ImageModel):
+    crop_horz_choices = (
+        (0, 'left'),
+        (1, 'center'),
+        (2, 'right'),
+    )
+    crop_vert_choices = (
+        (0, 'top'),
+        (1, 'center'),
+        (2, 'bottom'),
+    )
+    image = models.ImageField(_('image'), upload_to='photolog')
+    crop_horz = models.PositiveIntegerField(_('crop horizontal'),
+                                            choices=crop_horz_choices,
+                                            default=1)
+    crop_vert = models.PositiveIntegerField(_('crop vertical'),
+                                            choices=crop_vert_choices,
+                                            default=1)
+    title = models.CharField(_('title'), max_length=100, unique=True)
+    title_slug = models.SlugField(_('slug'), unique=True,
+                                  help_text=('A "slug" is a unique URL-friendly title for an object.'))
+    caption = models.TextField(_('caption'), blank=True)
+    date_added = models.DateTimeField(_('date added'), default=datetime.now, editable=False)
+    view_count = models.PositiveIntegerField(default=0, editable=False)
+    is_public = models.BooleanField(_('is public'), default=True, help_text=_('Public photographs will be displayed in the default views.'))
+
+    class Meta:
+        ordering = ['-date_added']
+        get_latest_by = 'date_added'
+        verbose_name = _("photo")
+        verbose_name_plural = _("photos")
+        
+    class IKOptions:
+        spec_module = PHOTOLOG_SPEC_MODULE
+        save_count_as = 'view_count'
+        cache_dir = 'photolog'
+        cache_filename_format = "%(specname)s/%(filename)s.%(extension)s"
+
+    def __unicode__(self):
+        return self.title
+
+    def __str__(self):
+        return self.__unicode__()
+
+    def save(self, *args, **kwargs):
+        if self.title_slug is None:
+            self.title_slug = slugify(self.title)
+        super(Photo, self).save(*args, **kwargs)
+
+    def get_absolute_url(self):
+        return reverse('pl-photo', args=[self.title_slug])
+
+    def public_galleries(self):
+        """Return the public galleries to which this photo belongs."""
+        return self.galleries.filter(is_public=True)
+
+    def get_previous_in_gallery(self, gallery):
+        try:
+            return self.get_previous_by_date_added(galleries__exact=gallery,
+                                                   is_public=True)
+        except Photo.DoesNotExist:
+            return None
+
+    def get_next_in_gallery(self, gallery):
+        try:
+            return self.get_next_by_date_added(galleries__exact=gallery,
+                                               is_public=True)
+        except Photo.DoesNotExist:
+            return None
+

apps/photolog/specs.py

+""" Default Photologue image specifications """
+
+from imagekit.specs import ImageSpec
+from imagekit import processors
+    
+
+class ResizeThumbnail(processors.Resize):
+    width = 100
+    height = 75
+    crop = True
+    
+class ResizeDisplay(processors.Resize):
+    width = 600
+    
+class EnhanceSmall(processors.Adjustment):
+    contrast = 1.2
+    sharpness = 1.1
+    
+class AdminThumbnail(ImageSpec):
+    access_as = 'admin_thumbnail'
+    processors = [ResizeThumbnail, EnhanceSmall]
+
+class Display(ImageSpec):
+    increment_count = True
+    processors = [ResizeDisplay]
+        
+class Thumbnail(ImageSpec):
+    processors = [ResizeThumbnail, EnhanceSmall]
+    pre_cache = True

apps/photolog/templates/photolog/gallery_archive.html

+{% extends "photolog/root.html" %}
+
+{% block title %}Latest Photo Galleries{% endblock %}
+
+{% block content %}
+
+<h1>Latest Photo Galleries</h1>
+
+{% if latest %}
+    {% for gallery in latest %}
+    <div class="photo-gallery">
+        <h2><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></h2>
+        {% for photo in gallery.sample|slice:sample_size %}
+        <div class="gallery-photo">
+            <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.thumbnail.url }}" alt="{{ photo.title }}"/></a>
+        </div>
+        {% endfor %}
+    </div>
+    {% endfor %}
+{% else %}
+    <p>No galleries were found.</p>
+{% endif %}
+
+<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p>
+
+{% endblock %}

apps/photolog/templates/photolog/gallery_archive_day.html

+{% extends "photolog/root.html" %}
+
+{% block title %}Galleries for {{ day|date }}{% endblock %}
+
+{% block content %}
+
+<h1>Galleries for {{ day|date }}</h1>
+
+{% if object_list %}
+    {% for gallery in object_list %}
+    <div class="photo-gallery">
+        <h2>{{ gallery.title }}</h2>
+        {% for photo in gallery.sample|slice:sample_size %}
+        <div class="gallery-photo">
+            <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.thumbnail.url }}" alt="{{ photo.title }}"/></a>
+        </div>
+        {% endfor %}
+    </div>
+    {% endfor %}
+{% else %}
+    <p>No galleries were found.</p>
+{% endif %}
+
+<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p>
+
+{% endblock %}

apps/photolog/templates/photolog/gallery_archive_month.html

+{% extends "photolog/root.html" %}
+
+{% block title %}Galleries for {{ month|date:"F Y" }}{% endblock %}
+
+{% block content %}
+
+<h1>Galleries for {{ month|date:"F Y" }}</h1>
+
+{% if object_list %}
+    {% for gallery in object_list %}
+    <div class="photo-gallery">
+        <h2>{{ gallery.title }}</h2>
+        {% for photo in gallery.sample|slice:sample_size %}
+        <div class="gallery-photo">
+            <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.thumbnail.url }}" alt="{{ photo.title }}"/></a>
+        </div>
+        {% endfor %}
+    </div>
+    {% endfor %}
+{% else %}
+    <p>No galleries were found.</p>
+{% endif %}
+
+<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p>
+
+{% endblock %}

apps/photolog/templates/photolog/gallery_archive_year.html

+{% extends "photolog/root.html" %}
+
+{% block title %}Galleries for {{ year }}{% endblock %}
+
+{% block content %}
+
+<h1>Galleries for {{ year }}</h1>
+<ul>
+{% for date in date_list %}
+<li><a href="{{ date|date:"M"|lower }}/">{{ date|date:"F" }}</a></li>
+{% endfor %}
+</ul>
+
+<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p>
+
+{% endblock %}

apps/photolog/templates/photolog/gallery_detail.html

+{% extends "photolog/root.html" %}
+
+{% block title %}{{ object.title }}{% endblock %}
+
+{% block content %}
+
+<h1>{{ object.title }}</h1>
+<h2>Originally published {{ object.date_added|date:"l, F jS, Y" }}</h2>
+{% if object.description %}<p>{{ object.description }}</p>{% endif %}
+<div class="photo-gallery">
+    {% for photo in object.public %}
+    <div class="gallery-photo">
+        <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.thumbnail.url }}" alt="{{ photo.title }}"/></a>
+    </div>
+    {% endfor %}
+</div>
+<p><a href="{% url pl-gallery-list 1 %}">View all galleries</a></p>
+
+{% endblock %}

apps/photolog/templates/photolog/gallery_list.html

+{% extends "photolog/root.html" %}
+
+{% block title %}All Galleries{% endblock %}
+
+{% block content %}
+
+<h1>All galleries</h1>
+
+{% if object_list %}
+    {% for gallery in object_list %}
+    <div class="photo-gallery">
+        <h2><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></h2>
+        {% for photo in gallery.sample|slice:sample_size %}
+        <div class="gallery-photo">
+            <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.thumbnail.url }}" alt="{{ photo.title }}"/></a>
+        </div>
+        {% endfor %}
+    </div>
+    {% endfor %}
+{% else %}
+    <p>No galleries were found.</p>
+{% endif %}
+
+{% if is_paginated %}
+<p>{{ hits }} galleries total.</p>
+<div id="page_controls">
+    <p>{% if has_previous %}<a href="{% url pl-gallery-list previous %}">Previous</a> | {% endif %} page {{ page }} of {{ pages }} {% if has_next %}| <a href="{% url pl-gallery-list next %}">Next</a>{% endif %}</p>
+</div>
+{% endif %}
+
+{% endblock %}

apps/photolog/templates/photolog/photo_archive.html

+{% extends "photolog/root.html" %}
+
+{% block title %}Latest Photos{% endblock %}
+
+{% block content %}
+
+<h1>Latest Photos</h1>
+
+{% if latest %}
+    {% for photo in latest %}
+    <div class="gallery-photo">
+        <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.thumbnail.url }}" alt="{{ photo.title }}"/></a>
+    </div>
+    {% endfor %}
+{% else %}
+<p>No photos were found.</p>
+{% endif %}
+<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p>
+
+{% endblock %}

apps/photolog/templates/photolog/photo_archive_day.html

+{% extends "photolog/root.html" %}
+
+{% block title %}Photos for {{ day|date }}{% endblock %}
+
+{% block content %}
+
+<h1>Photos for {{ day|date }}</h1>
+
+{% if object_list %}
+    {% for photo in object_list %}
+    <div class="gallery-photo">
+        <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.thumbnail.url }}" alt="{{ photo.title }}"/></a>
+    </div>
+    {% endfor %}
+{% else %}
+<p>No photos were found.</p>
+{% endif %}
+<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p>
+
+{% endblock %}

apps/photolog/templates/photolog/photo_archive_month.html

+{% extends "photolog/root.html" %}
+
+{% block title %}Photos for {{ month|date:"F Y" }}{% endblock %}
+
+{% block content %}
+
+<h1>Photos for {{ month|date:"F Y" }}</h1>
+
+{% if object_list %}
+    {% for photo in object_list %}
+    <div class="gallery-photo">
+        <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.thumbnail.url }}" alt="{{ photo.title }}"/></a>
+    </div>
+    {% endfor %}
+{% else %}
+<p>No photos were found.</p>
+{% endif %}
+<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p>
+
+{% endblock %}

apps/photolog/templates/photolog/photo_archive_year.html

+{% extends "photolog/root.html" %}
+
+{% block title %}Galleries for {{ year }}{% endblock %}
+
+{% block content %}
+
+<h1>Photos for {{ year }}</h1>
+<ul>
+{% for date in date_list %}
+<li><a href="{{ date|date:"M"|lower }}/">{{ date|date:"F" }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}

apps/photolog/templates/photolog/photo_detail.html

+{% extends "photolog/root.html" %}
+
+{% load photolog_tags %}
+
+{% block title %}{{ object.title }}{% endblock %}
+
+{% block content %}
+
+<h1>{{ object.title }}</h1>
+<div class="gallery-photo">
+    <a href="{{ object.image.url }}"><img src="{{ object.display.url }}" alt="{{ object.title }}"/></a>
+    {% if object.caption %}<p>{{ object.caption }}</p>{% endif %}
+</div>
+{% if object.public_galleries %}
+<h2>This photo is found in the following galleries:</h2>
+<ol>
+{% for gallery in object.public_galleries %}
+    <li>{%previous_in_gallery object gallery%} <a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a> {%next_in_gallery object gallery%}</li>
+{% endfor %}
+</ol>
+{% endif %}
+
+{% endblock %}

apps/photolog/templates/photolog/photo_list.html

+{% extends "photolog/root.html" %}
+
+{% block title %}All Photos{% endblock %}
+
+{% block content %}
+
+<h1>All Photos</h1>
+
+{% if object_list %}
+    {% for photo in object_list %}
+    <div class="gallery-photo">
+        <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.thumbnail.url }}" alt="{{ photo.title }}"/></a>
+    </div>
+    {% endfor %}
+{% else %}
+<p>No photos were found.</p>
+{% endif %}
+
+{% if is_paginated %}
+<p>{{ hits }} photos total.</p>
+<div id="page_controls">
+    <p>{% if has_previous %}<a href="{% url pl-photo-list previous %}">Previous</a> | {% endif %} page {{ page }} of {{ pages }} {% if has_next %}| <a href="{% url pl-photo-list next %}">Next</a>{% endif %}</p>
+</div>
+{% endif %}
+
+{% endblock %}

apps/photolog/templates/photolog/root.html

+{% extends "base.html" %}

apps/photolog/templates/photolog/tags/next_in_gallery.html

+{% if photo %}
+<a title="{{ photo.title }}" href="{{ photo.get_absolute_url }}"><img src="{{ photo.thumbnail.url }}"/></a>
+{% endif %}

apps/photolog/templates/photolog/tags/prev_in_gallery.html

+{% if photo %}
+<a title="{{ photo.title }}" href="{{ photo.get_absolute_url }}"><img src="{{ photo.thumbnail.url }}"/></a>
+{% endif %}
Add a comment to this file

apps/photolog/templatetags/__init__.py

Empty file added.

apps/photolog/templatetags/photolog_tags.py

+from django import template
+
+register = template.Library()
+
+@register.inclusion_tag('photolog/tags/next_in_gallery.html')
+def next_in_gallery(photo, gallery):
+    return {'photo': photo.get_next_in_gallery(gallery)}
+
+@register.inclusion_tag('photolog/tags/prev_in_gallery.html')
+def previous_in_gallery(photo, gallery):
+    return {'photo': photo.get_previous_in_gallery(gallery)}

apps/photolog/urls.py

+from django.conf import settings
+from django.conf.urls.defaults import *
+from models import *
+
+# Number of random images from the gallery to display.
+SAMPLE_SIZE = ":%s" % getattr(settings, 'GALLERY_SAMPLE_SIZE', 5)
+
+# galleries
+gallery_args = {'date_field': 'date_added', 'allow_empty': True, 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}}
+urlpatterns = patterns('django.views.generic.date_based',
+    url(r'^gallery/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\w{1,2})/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'date_field': 'date_added', 'slug_field': 'title_slug', 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery-detail'),
+    url(r'^gallery/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\w{1,2})/$', 'archive_day', gallery_args, name='pl-gallery-archive-day'),
+    url(r'^gallery/(?P<year>\d{4})/(?P<month>\d{2})/$', 'archive_month', gallery_args, name='pl-gallery-archive-month'),
+    url(r'^gallery/(?P<year>\d{4})/$', 'archive_year', gallery_args, name='pl-gallery-archive-year'),
+    url(r'^gallery/?$', 'archive_index', gallery_args, name='pl-gallery-archive'),
+)
+urlpatterns += patterns('django.views.generic.list_detail',
+    url(r'^gallery/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'slug_field': 'title_slug', 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery'),
+    url(r'^gallery/page/(?P<page>[0-9]+)/$', 'object_list', {'queryset': Gallery.objects.filter(is_public=True), 'allow_empty': True, 'paginate_by': 5, 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery-list'),
+)
+
+# photographs
+photo_args = {'date_field': 'date_added', 'allow_empty': True, 'queryset': Photo.objects.filter(is_public=True)}
+urlpatterns += patterns('django.views.generic.date_based',
+    url(r'^photo/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\w{1,2})/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'date_field': 'date_added', 'slug_field': 'title_slug', 'queryset': Photo.objects.filter(is_public=True)}, name='pl-photo-detail'),
+    url(r'^photo/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\w{1,2})/$', 'archive_day', photo_args, name='pl-photo-archive-day'),
+    url(r'^photo/(?P<year>\d{4})/(?P<month>\d{2})/$', 'archive_month', photo_args, name='pl-photo-archive-month'),
+    url(r'^photo/(?P<year>\d{4})/$', 'archive_year', photo_args, name='pl-photo-archive-year'),
+    url(r'^photo/$', 'archive_index', photo_args, name='pl-photo-archive'),
+)
+urlpatterns += patterns('django.views.generic.list_detail',
+    url(r'^photo/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'slug_field': 'title_slug', 'queryset': Photo.objects.filter(is_public=True)}, name='pl-photo'),
+    url(r'^photo/page/(?P<page>[0-9]+)/$', 'object_list', {'queryset': Photo.objects.filter(is_public=True), 'allow_empty': True, 'paginate_by': 20}, name='pl-photo-list'),
+)
+
+
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.