← Back to team overview

zim-wiki team mailing list archive

Re: Searching on the server

 

On 08/05/20 16:13, Helder Guerreiro wrote:
Is it possible to add a search box to the internal Zim server?

Since it seems there is no easy way to add search to the server I made the patch attached (against master). There's an example search box in the "Default_with_index" template.

But this got me thinking, now I'm looking for other features, tag list, task list, maybe even marking tasks directly from the server or editing pages... This seems to be a little too much for this server. Using a framework, Django or whatever, would be easier. Maybe a separated project from Zim?

/Helder
diff --git a/data/templates/html/Default_with_index.html b/data/templates/html/Default_with_index.html
index e2191c6c..2863e41a 100644
--- a/data/templates/html/Default_with_index.html
+++ b/data/templates/html/Default_with_index.html
@@ -44,6 +44,9 @@
 		.menu{
 			float:left; width: 300px;
 		}
+		.search {
+			float: right;
+		}
 		hr{clear:both;}
 	</style>
 </head>
@@ -68,6 +71,12 @@
 [% ELSE %]
 	[ <span class='insen'>[% gettext("Next") %]</span> ]
 [% END %]
+<div class="search">
+  <form method="GET" action="/search">
+    <input type="search" name="q" value="" maxlength="128" placeholder="Search">
+    <button type="submit" value="Submit">Go</button>
+  </form>
+</div>
 </div>
 
 <hr />
diff --git a/zim/www.py b/zim/www.py
index 09bc7eaa..82415689 100644
--- a/zim/www.py
+++ b/zim/www.py
@@ -42,9 +42,55 @@ from zim.export.exporters import createIndexPage
 
 from zim.formats import get_format
 
+# For searching
+from zim.newfs.mock import MockFile
+from zim.formats import ParseTreeBuilder, \
+	FORMATTEDTEXT, HEADING, BULLETLIST, LISTITEM, LINK, PARAGRAPH
+from zim.search import SearchSelection, Query
+
 logger = logging.getLogger('zim.www')
 
 
+def search(notebook, query):
+		if query and not query.isspace():
+			logger.info('Searching for: %s', query)
+			query = Query(query)
+		else:
+			return []
+
+		selection = SearchSelection(notebook)
+		selection.search(query)
+		return [path for path in selection]
+		# return [notebook.get_page(path) for path in selection]
+
+
+def createSearchResultPage(notebook, query):
+		pagelist = search(notebook, query)
+		builder = ParseTreeBuilder()
+
+		builder.start(FORMATTEDTEXT)
+		builder.append(HEADING, {'level': 1}, 'Search Results')
+
+		if pagelist:
+			builder.start(BULLETLIST)
+			for page in sorted(pagelist, key=lambda p: p.name):
+				builder.start(LISTITEM)
+				builder.append(LINK,
+					{'type': 'page', 'href': page.name}, page.name)
+				builder.end(LISTITEM)
+			builder.end(BULLETLIST)
+		else:
+			builder.append(PARAGRAPH, {}, 'No results')
+
+		builder.end(FORMATTEDTEXT)
+
+		tree = builder.get_parsetree()
+
+		indexpage = Page(Path(':'), False, MockFile('/search'), None)
+		indexpage.set_parsetree(tree)
+		return indexpage
+
+
 class WWWError(Error):
 	'''Error with http error code'''
 
@@ -182,6 +228,15 @@ class WWWInterface(object):
 			if path == '/':
 				headers.add_header('Content-Type', 'text/html', charset='utf-8')
 				content = self.render_index()
+			elif path == '/search':
+				qs = urllib.parse.parse_qs(environ.get('QUERY_STRING', ''))
+				if 'q' not in qs:
+					qs['q'] = ['']
+				query = qs['q'][0]
+				if len(query) > 128:
+					raise WebPathNotValidError()
+				headers.add_header('Content-Type', 'text/html', charset='utf-8')
+				content = self.render_search(query)
 			elif path.startswith('/+docs/'):
 				dir = self.notebook.document_root
 				if not dir:
@@ -275,6 +330,14 @@ class WWWInterface(object):
 		page = createIndexPage(self.notebook, path, namespace)
 		return self.render_page(page)
 
+	def render_search(self, query):
+		'''Search Zim notebook
+		@param query: query string
+		@returns: list of matching pages
+		'''
+		page = createSearchResultPage(self.notebook, query)
+		return self.render_page(page)
+
 	def render_page(self, page):
 		'''Render a single page from the notebook
 		@param page: a L{Page} object

Follow ups

References