wikimore 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ venv/
2
+ .venv/
3
+ __pycache__/
4
+ *.pyc
@@ -0,0 +1,27 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Python Debugger: Flask",
9
+ "type": "debugpy",
10
+ "request": "launch",
11
+ "module": "flask",
12
+ "env": {
13
+ "FLASK_DEBUG": "1",
14
+ },
15
+ "args": [
16
+ "--app",
17
+ "wikimore",
18
+ "run",
19
+ "--no-debugger",
20
+ "--no-reload",
21
+ "--port=8109"
22
+ ],
23
+ "jinja": true,
24
+ "autoStartBrowser": false
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "files.associations": {
3
+ "*.html": "jinja-html"
4
+ }
5
+ }
wikimore-0.1.0/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2024 Private.coffee Team <support@private.coffee>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.3
2
+ Name: wikimore
3
+ Version: 0.1.0
4
+ Summary: A simple frontend for Wikimedia wikis
5
+ Project-URL: Homepage, https://git.private.coffee/privatecoffee/wikimore
6
+ Project-URL: Bug Tracker, https://git.private.coffee/privatecoffee/wikimore/issues
7
+ Project-URL: Source Code, https://git.private.coffee/privatecoffee/wikimore
8
+ Author-email: "Private.coffee Team" <support@private.coffee>
9
+ License: Copyright (c) 2024 Private.coffee Team <support@private.coffee>
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
30
+ Classifier: Operating System :: OS Independent
31
+ Classifier: Programming Language :: Python :: 3
32
+ Requires-Python: >=3.10
33
+ Requires-Dist: bs4
34
+ Requires-Dist: flask
35
+ Description-Content-Type: text/markdown
36
+
37
+ # Wikimore - A simple frontend for Wikimedia projects
38
+
39
+ [![Support Private.coffee!](https://shields.private.coffee/badge/private.coffee-support%20us!-pink?logo=coffeescript)](https://private.coffee)
40
+ [![Matrix](https://shields.private.coffee/badge/Matrix-join%20us!-blue?logo=matrix)](https://matrix.pcof.fi/#/#wikimore:private.coffee)
41
+ [![PyPI](https://shields.private.coffee/pypi/v/wikimore)](https://pypi.org/project/wikimore/)
42
+ [![PyPI - Python Version](https://shields.private.coffee/pypi/pyversions/wikimore)](https://pypi.org/project/wikimore/)
43
+ [![PyPI - License](https://shields.private.coffee/pypi/l/wikimore)](https://pypi.org/project/wikimore/)
44
+ [![Latest Git Commit](https://shields.private.coffee/gitea/last-commit/privatecoffee/wikimore?gitea_url=https://git.private.coffee)](https://git.private.coffee/privatecoffee/wikimore)
45
+
46
+ Wikimore is a simple frontend for Wikimedia projects. It uses the MediaWiki API to fetch data from Wikimedia projects and display it in a user-friendly way. It is built using Flask.
47
+
48
+ This project is still in development and more features will be added in the future. It is useful for anyone who wants to access Wikimedia projects with a more basic frontend, or to provide access to Wikimedia projects to users who cannot access them directly, for example due to state censorship.
49
+
50
+ ## Features
51
+
52
+ - Supports all Wikimedia projects in all languages
53
+ - Search functionality
54
+ - Proxy support for Wikimedia images
55
+
56
+ ## Instances
57
+
58
+ | URL | Provided by | Country | Comments |
59
+ | ----------------------------------------------------------- | ----------------------------------------- | ------- | -------- |
60
+ | [wikimore.private.coffee](https://wikimore.private.coffee/) | [Private.coffee](https://private.coffee/) | Austria | |
61
+
62
+ If you operate a public instance of Wikimore and would like to have it listed here, please open an issue or a pull request.
63
+
64
+ ## Installation
65
+
66
+ ### Production
67
+
68
+ 1. Create a virtual environment and activate it
69
+
70
+ ```bash
71
+ python3 -m venv venv
72
+ source venv/bin/activate
73
+ ```
74
+
75
+ 2. Install the package from PyPI
76
+
77
+ ```bash
78
+ pip install wikimore
79
+ ```
80
+
81
+ 3. Run the application
82
+
83
+ ```bash
84
+ wikimore
85
+ ```
86
+
87
+ 4. Open your browser and navigate to `http://localhost:8109`
88
+
89
+ ## Development
90
+
91
+ 1. Clone the repository
92
+
93
+ ```bash
94
+ git clone https://git.private.coffee/privatecoffee/wikimore.git
95
+ cd wikimore
96
+ ```
97
+
98
+ 2. Create a virtual environment and activate it
99
+
100
+ ```bash
101
+ python3 -m venv venv
102
+ source venv/bin/activate
103
+ ```
104
+
105
+ 3. Install the package in editable mode
106
+
107
+ ```bash
108
+ pip install -e .
109
+ ```
110
+
111
+ 4. Run the application
112
+
113
+ ```bash
114
+ flask --app wikimore run
115
+ ```
116
+
117
+ 5. Open your browser and navigate to `http://localhost:5000`
118
+
119
+ ## License
120
+
121
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,85 @@
1
+ # Wikimore - A simple frontend for Wikimedia projects
2
+
3
+ [![Support Private.coffee!](https://shields.private.coffee/badge/private.coffee-support%20us!-pink?logo=coffeescript)](https://private.coffee)
4
+ [![Matrix](https://shields.private.coffee/badge/Matrix-join%20us!-blue?logo=matrix)](https://matrix.pcof.fi/#/#wikimore:private.coffee)
5
+ [![PyPI](https://shields.private.coffee/pypi/v/wikimore)](https://pypi.org/project/wikimore/)
6
+ [![PyPI - Python Version](https://shields.private.coffee/pypi/pyversions/wikimore)](https://pypi.org/project/wikimore/)
7
+ [![PyPI - License](https://shields.private.coffee/pypi/l/wikimore)](https://pypi.org/project/wikimore/)
8
+ [![Latest Git Commit](https://shields.private.coffee/gitea/last-commit/privatecoffee/wikimore?gitea_url=https://git.private.coffee)](https://git.private.coffee/privatecoffee/wikimore)
9
+
10
+ Wikimore is a simple frontend for Wikimedia projects. It uses the MediaWiki API to fetch data from Wikimedia projects and display it in a user-friendly way. It is built using Flask.
11
+
12
+ This project is still in development and more features will be added in the future. It is useful for anyone who wants to access Wikimedia projects with a more basic frontend, or to provide access to Wikimedia projects to users who cannot access them directly, for example due to state censorship.
13
+
14
+ ## Features
15
+
16
+ - Supports all Wikimedia projects in all languages
17
+ - Search functionality
18
+ - Proxy support for Wikimedia images
19
+
20
+ ## Instances
21
+
22
+ | URL | Provided by | Country | Comments |
23
+ | ----------------------------------------------------------- | ----------------------------------------- | ------- | -------- |
24
+ | [wikimore.private.coffee](https://wikimore.private.coffee/) | [Private.coffee](https://private.coffee/) | Austria | |
25
+
26
+ If you operate a public instance of Wikimore and would like to have it listed here, please open an issue or a pull request.
27
+
28
+ ## Installation
29
+
30
+ ### Production
31
+
32
+ 1. Create a virtual environment and activate it
33
+
34
+ ```bash
35
+ python3 -m venv venv
36
+ source venv/bin/activate
37
+ ```
38
+
39
+ 2. Install the package from PyPI
40
+
41
+ ```bash
42
+ pip install wikimore
43
+ ```
44
+
45
+ 3. Run the application
46
+
47
+ ```bash
48
+ wikimore
49
+ ```
50
+
51
+ 4. Open your browser and navigate to `http://localhost:8109`
52
+
53
+ ## Development
54
+
55
+ 1. Clone the repository
56
+
57
+ ```bash
58
+ git clone https://git.private.coffee/privatecoffee/wikimore.git
59
+ cd wikimore
60
+ ```
61
+
62
+ 2. Create a virtual environment and activate it
63
+
64
+ ```bash
65
+ python3 -m venv venv
66
+ source venv/bin/activate
67
+ ```
68
+
69
+ 3. Install the package in editable mode
70
+
71
+ ```bash
72
+ pip install -e .
73
+ ```
74
+
75
+ 4. Run the application
76
+
77
+ ```bash
78
+ flask --app wikimore run
79
+ ```
80
+
81
+ 5. Open your browser and navigate to `http://localhost:5000`
82
+
83
+ ## License
84
+
85
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "wikimore"
7
+ version = "0.1.0"
8
+ authors = [{ name = "Private.coffee Team", email = "support@private.coffee" }]
9
+ description = "A simple frontend for Wikimedia wikis"
10
+ readme = "README.md"
11
+ license = { file = "LICENSE" }
12
+ requires-python = ">=3.10"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "License :: OSI Approved :: GNU Affero General Public License v3",
16
+ "Operating System :: OS Independent",
17
+ ]
18
+ dependencies = ["flask", "bs4"]
19
+
20
+ [project.scripts]
21
+ wikimore = "wikimore.app:main"
22
+
23
+ [project.urls]
24
+ "Homepage" = "https://git.private.coffee/privatecoffee/wikimore"
25
+ "Bug Tracker" = "https://git.private.coffee/privatecoffee/wikimore/issues"
26
+ "Source Code" = "https://git.private.coffee/privatecoffee/wikimore"
@@ -0,0 +1,2 @@
1
+ flask
2
+ bs4
@@ -0,0 +1,3 @@
1
+ from .app import app
2
+
3
+ __all__ = ["app"]
@@ -0,0 +1,223 @@
1
+ from flask import Flask, render_template, request, redirect, url_for
2
+ import urllib.request
3
+ from urllib.parse import urlencode, urlparse
4
+ from html import escape
5
+ import json
6
+ import os
7
+ import logging
8
+ from bs4 import BeautifulSoup
9
+
10
+ app = Flask(__name__)
11
+
12
+ logging.basicConfig(level=logging.DEBUG)
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ handler = logging.StreamHandler()
17
+ handler.setLevel(logging.DEBUG)
18
+ logger.addHandler(handler)
19
+
20
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
21
+ handler.setFormatter(formatter)
22
+
23
+
24
+ def get_wikimedia_projects():
25
+ url = "https://meta.wikimedia.org/w/api.php?action=sitematrix&format=json"
26
+ with urllib.request.urlopen(url) as response:
27
+ data = json.loads(response.read().decode())
28
+
29
+ projects = {}
30
+ languages = {}
31
+
32
+ for key, value in data["sitematrix"].items():
33
+ if key.isdigit():
34
+ language = value["name"]
35
+ language_code = value["code"]
36
+ language_projects = {}
37
+
38
+ for site in value["site"]:
39
+ language_projects[site["code"]] = site["url"]
40
+
41
+ if language_code == "en":
42
+ projects[site["code"]] = site["sitename"]
43
+
44
+ if language_projects:
45
+ languages[language_code] = {
46
+ "projects": language_projects,
47
+ "name": language,
48
+ }
49
+
50
+ return projects, languages
51
+
52
+
53
+ def get_proxy_url(url):
54
+ if url.startswith("//"):
55
+ url = "https:" + url
56
+
57
+ if not url.startswith("https://upload.wikimedia.org/"):
58
+ logger.debug(f"Not generating proxy URL for {url}")
59
+ return url
60
+
61
+ logger.debug(f"Generating proxy URL for {url}")
62
+ return f"/proxy?{urlencode({'url': url})}"
63
+
64
+
65
+ @app.route("/proxy")
66
+ def proxy():
67
+ url = request.args.get("url")
68
+
69
+ if not url or not url.startswith("https://upload.wikimedia.org/"):
70
+ logger.error(f"Invalid URL for proxying: {url}")
71
+ return "Invalid URL"
72
+
73
+ logger.debug(f"Proxying {url}")
74
+
75
+ with urllib.request.urlopen(url) as response:
76
+ data = response.read()
77
+ return data
78
+
79
+
80
+ @app.route("/")
81
+ def home():
82
+ return render_template(
83
+ "home.html",
84
+ wikimedia_projects=app.wikimedia_projects,
85
+ languages=app.languages,
86
+ )
87
+
88
+
89
+ @app.route("/search", methods=["GET", "POST"])
90
+ def search():
91
+ if request.method == "POST":
92
+ query = request.form["query"]
93
+ lang = request.form["lang"]
94
+ project = request.form["project"]
95
+ return redirect(
96
+ url_for("search_results", project=project, lang=lang, query=query)
97
+ )
98
+ return render_template(
99
+ "search.html",
100
+ wikimedia_projects=app.wikimedia_projects,
101
+ languages=app.languages,
102
+ )
103
+
104
+
105
+ @app.route("/<project>/<lang>/wiki/<title>")
106
+ def wiki_article(project, lang, title):
107
+ language_projects = app.languages.get(lang, {}).get("projects", {})
108
+ base_url = language_projects.get(project)
109
+
110
+ if not base_url:
111
+ return "Invalid language or project"
112
+
113
+ logger.debug(f"Fetching {title} from {base_url}")
114
+
115
+ url = f"{base_url}/w/api.php?action=query&format=json&titles={escape(title.replace(" ", "_"), True)}&prop=revisions&rvprop=content&rvparse=1"
116
+ with urllib.request.urlopen(url) as response:
117
+ data = json.loads(response.read().decode())
118
+ pages = data["query"]["pages"]
119
+ article_html = next(iter(pages.values()))["revisions"][0]["*"]
120
+
121
+ soup = BeautifulSoup(article_html, "html.parser")
122
+
123
+ for a in soup.find_all("a", href=True):
124
+ href = a["href"]
125
+
126
+ if href.startswith("/wiki/"):
127
+ a["href"] = f"/{project}/{lang}{href}"
128
+
129
+ elif href.startswith("//") or href.startswith("https://"):
130
+ parts = urlparse(href)
131
+
132
+ target_domain = parts.netloc
133
+ path_parts = parts.path.split("/")
134
+
135
+ if len(path_parts) > 4:
136
+ target_title = "/".join(path_parts[4:])
137
+ target_lang = target_domain.split(".")[0]
138
+
139
+ found = False
140
+ for language, language_projects in app.languages.items():
141
+ for project_name, project_url in language_projects.items():
142
+ if target_domain == project_url:
143
+ a["href"] = (
144
+ f"/{project_name}/{target_lang}/wiki/{target_title}"
145
+ )
146
+ found = True
147
+ break
148
+ if found:
149
+ break
150
+
151
+ for span in soup.find_all("span", class_="mw-editsection"):
152
+ span.decompose()
153
+
154
+ for style in soup.find_all("style"):
155
+ style.decompose()
156
+
157
+ for img in soup.find_all("img"):
158
+ img["src"] = get_proxy_url(img["src"])
159
+
160
+ for source in soup.find_all("source"):
161
+ source["src"] = get_proxy_url(source["src"])
162
+
163
+ for video in soup.find_all("video"):
164
+ video["poster"] = get_proxy_url(video["poster"])
165
+
166
+ for li in soup.find_all("li"):
167
+ if any(cls in li.get("class", []) for cls in ["nv-view", "nv-talk", "nv-edit"]):
168
+ li.decompose()
169
+
170
+ processed_html = str(soup)
171
+ return render_template(
172
+ "article.html",
173
+ title=title.replace("_", " "),
174
+ content=processed_html,
175
+ wikimedia_projects=app.wikimedia_projects,
176
+ languages=app.languages,
177
+ )
178
+
179
+
180
+ @app.route("/<project>/<lang>/search/<query>")
181
+ def search_results(project, lang, query):
182
+ language_projects = app.languages.get(lang, {}).get("projects", {})
183
+ base_url = language_projects.get(project)
184
+
185
+ if not base_url:
186
+ return "Invalid language or project"
187
+
188
+ logger.debug(f"Searching {base_url} for {query}")
189
+
190
+ url = f"{base_url}/w/api.php?action=query&format=json&list=search&srsearch={query}"
191
+ with urllib.request.urlopen(url) as response:
192
+ data = json.loads(response.read().decode())
193
+ search_results = data["query"]["search"]
194
+ return render_template(
195
+ "search_results.html",
196
+ query=query,
197
+ search_results=search_results,
198
+ project=project,
199
+ lang=lang,
200
+ wikimedia_projects=app.wikimedia_projects,
201
+ languages=app.languages,
202
+ )
203
+
204
+
205
+ @app.route("/<project>/<lang>/wiki/Special:Search/<query>")
206
+ def search_redirect(project, lang, query):
207
+ return redirect(url_for("search_results", project=project, lang=lang, query=query))
208
+
209
+
210
+ app.wikimedia_projects, app.languages = get_wikimedia_projects()
211
+
212
+ logger.debug(
213
+ f"Loaded {len(app.wikimedia_projects)} Wikimedia projects and {len(app.languages)} languages"
214
+ )
215
+
216
+ def main():
217
+ port = int(os.environ.get("PORT", 8109))
218
+ host = os.environ.get("HOST", "0.0.0.0")
219
+ debug = os.environ.get("DEBUG", False)
220
+ app.run(port=port, host=host, debug=debug)
221
+
222
+ if __name__ == "__main__":
223
+ main()
@@ -0,0 +1,6 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <h1>{{ title }}</h1>
5
+ <p>{{ content|safe }}</p>
6
+ {% endblock %}
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ title }}{% if title %} &dash; {% endif %}Wikimore</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ margin: 0;
11
+ padding: 0;
12
+ background-color: #f8f9fa;
13
+ }
14
+ #header {
15
+ background-color: #ffffff;
16
+ border-bottom: 1px solid #a2a9b1;
17
+ padding: 10px;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: space-between;
21
+ }
22
+ #header h1 {
23
+ margin: 0;
24
+ font-size: 1.5em;
25
+ }
26
+ #header h1 a {
27
+ text-decoration: none;
28
+ color: #333;
29
+ }
30
+ #search-form {
31
+ margin: 0;
32
+ padding: 0;
33
+ display: flex;
34
+ align-items: center;
35
+ }
36
+ #search-form input[type="text"] {
37
+ width: 300px;
38
+ padding: 5px;
39
+ }
40
+ #search-form select {
41
+ padding: 5px;
42
+ }
43
+ #search-form button {
44
+ padding: 5px 10px;
45
+ }
46
+ #content {
47
+ padding: 20px;
48
+ }
49
+ h1 {
50
+ font-size: 2em;
51
+ padding-bottom: 10px;
52
+ margin-bottom: 20px;
53
+ }
54
+ .sidebar {
55
+ float: right;
56
+ width: 250px;
57
+ margin-left: 20px;
58
+ background-color: #ffffff;
59
+ border: 1px solid #a2a9b1;
60
+ padding: 10px;
61
+ box-sizing: border-box;
62
+ }
63
+ .toc {
64
+ list-style-type: none;
65
+ padding: 0;
66
+ }
67
+ .toc li {
68
+ margin-bottom: 5px;
69
+ }
70
+ .toc a {
71
+ text-decoration: none;
72
+ color: #333;
73
+ }
74
+ .toc a:hover {
75
+ text-decoration: underline;
76
+ }
77
+ #footer {
78
+ background-color: #ffffff;
79
+ border-top: 1px solid #a2a9b1;
80
+ padding: 10px;
81
+ text-align: center;
82
+ }
83
+ </style>
84
+ </head>
85
+ <body>
86
+ <div id="header">
87
+ <h1><a href="/">Wikimore</a></h1>
88
+ <form id="search-form" action="{{ url_for('search') }}" method="post">
89
+ <select name="project" id="project">
90
+ {% for key, value in wikimedia_projects.items() %}
91
+ <option value="{{ key }}">{{ value }}</option>
92
+ {% endfor %}
93
+ </select>
94
+ <select name="lang" id="lang">
95
+ {% for key, value in languages.items() %}
96
+ <option {% if key == "en" %}selected="selected"{% endif %} value="{{ key }}">{{ value["name"] }}</option>
97
+ {% endfor %}
98
+ </select>
99
+ <input type="text" name="query" id="query" placeholder="Search Wikipedia" required>
100
+ <button type="submit">Search</button>
101
+ </form>
102
+ </div>
103
+ <div id="content">
104
+ {% block content %}{% endblock %}
105
+ </div>
106
+ <div id="footer">
107
+ <p>Brought to you by <a href="https://git.private.coffee/privatecoffee/wikimore">Wikimore</a>, a <a href="https://private.coffee">Private.coffee</a> project</p>
108
+ </div>
109
+ </body>
110
+ </html>
@@ -0,0 +1,6 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <h1>Welcome to Wikimore</h1>
5
+ <p>Use the search form above to find articles on Wikipedia.</p>
6
+ {% endblock %}
@@ -0,0 +1,13 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <h1>Search Results for "{{ query }}"</h1>
5
+ <ul>
6
+ {% for result in search_results %}
7
+ <li>
8
+ <a href="{{ url_for('wiki_article', project=project, lang=lang, title=result['title']) }}">{{ result['title'] }}</a>
9
+ <p>{{ result['snippet']|safe }}</p>
10
+ </li>
11
+ {% endfor %}
12
+ </ul>
13
+ {% endblock %}