@@ -1,14 +1,11 @@
import flask
import re
from webclient.main import app, template, redirect, protected, api_get, api_post, api_put, api_delete
from webclient.main import app, template, redirect, protected


_licenses = ["GPL v2", "GPL v3", "LGPL v2.1",
"CC-0 v1.0",
"CC-BY v3.0", "CC-BY-SA v3.0", "CC-BY-NC-SA v3.0", "CC-BY-NC-ND v3.0",
"Custom"]
_branches = ["master"]
_dep_pattern = re.compile("([-a-z]*)/([0-9a-f]{8})/([0-9a-f]{8})$")


def get_compatibility(version):
@@ -27,114 +24,84 @@ def get_compatibility(version):
return res


def record_change(changes, data, key, value, empty_values=False):
if value is None:
return

o = data.get(key)
if o == value:
return

if empty_values and (o is None) and (not value):
return

changes[key] = value


def record_change_compatibility(changes, data, form):
compat = []
for b in _branches:
conds = []
c1 = form.get("compatibility_{}_min".format(b), "").strip()
c2 = form.get("compatibility_{}_max".format(b), "").strip()

if c1:
conds.append(c1)
if c2:
conds.append(c2)

if conds:
compat.append({"name": b, "conditions": conds})

record_change(changes, data, "compatibility", compat, True)


def record_change_dependencies(changes, data, form, messages):
valid_data = True
deps = set()
for d in form.get("dependencies").splitlines():
d = d.strip()
if len(d) == 0:
continue
m = _dep_pattern.match(d)
if m:
deps.add((m.group(1), m.group(2), m.group(3)))
else:
valid_data = False
messages.append("Invalid dependency: {}".format(d))
deps = sorted(deps)
deps = [{"content-type": d[0], "unique-id": d[1], "md5sum-partial": d[2]} for d in deps]
record_change(changes, data, "dependencies", deps, True)

return valid_data


def record_change_tags(changes, data, tags):
tags = tags.strip().splitlines()
tags = set(t.strip() for t in tags)
tags.discard("")
tags = sorted(tags)
record_change(changes, data, "tags", tags, True)


def record_change_descripton(changes, data, desc):
desc = "\n".join(t.rstrip() for t in desc.strip().splitlines())
record_change(changes, data, "description", desc, True)


@app.route("/package/<content_type>/<unique_id>/<upload_date>")
def version_info(content_type, unique_id, upload_date):
version = api_get(("package", content_type, unique_id, upload_date))
package = api_get(("package", content_type, unique_id))

latest = max(package.get("versions", []), default=None, key=lambda v: v.get("upload-date", ""))
if latest and latest.get("upload-date", "") == version.get("upload-date", ""):
latest = None

upgrade = package.get("replaced-by")
if upgrade and "unique-id" in upgrade:
upgrade_info, _ = api_get(("package", content_type, upgrade["unique-id"]), return_errors=True)
if upgrade_info:
upgrade.update(upgrade_info)

for dep in version.get("dependencies", []):
dep_package, _ = api_get(("package", dep.get("content-type", ""), dep.get("unique-id", "")),
return_errors=True)
if dep_package:
dep_version = list(filter(lambda v: v.get("md5sum-partial") == dep.get("md5sum-partial"),
dep_package.get("versions", [])))
dep.update(dep_package)
if len(dep_version) == 1:
dep.update(dep_version[0])
package = {
"content-type": content_type,
"unique-id" : unique_id,
"name": "Another banana content daemon",
"description": "bla bla",
"url": "",
"archived": True,
"replaced-by": {"unique-id": "ff112233", "name": "BaNaNaS 8"},
"tags": ["old", "deprecated", "better"],
"authors": [{"display-name": "anna"}, {"display-name": "berta"}]
}

latest = {
"content-type": content_type,
"unique-id" : unique_id,
"upload-date": upload_date,
"md5sum-partial": "12346678",
}

version = {
"content-type": content_type,
"unique-id" : unique_id,
"upload-date": "2020-02-29T11:11:11",
"md5sum-partial": "31415927",

"name": "Another banana content daemon",
"version": "0.1",
"description": "bla bla",
"url": "",
"tags": ["old", "deprecated", "better"],
"license": "custom",

"download-url": "https://openttd.org",
"filesize": "123456",

"dependencies": [{"content-type": "newgrf", "unique-id": "ffffffff", "md5sum-partial": "55443322", "upload-date": "2020-02-01T01:01:01", "name": "yolo", "version": "1.23"}],
"compatibility": [{"name": "master", "conditions": [">= 1.2", "< 1.5"]}]
}

return template("version_info.html", package=package, version=version, latest=latest)


@app.route("/manager/<content_type>/<unique_id>/<upload_date>")
@protected
def manager_version_info(session, content_type, unique_id, upload_date):
version = api_get(("package", content_type, unique_id, upload_date), session=session)
package = api_get(("package", content_type, unique_id), session=session)

for dep in version.get("dependencies", []):
dep_package, _ = api_get(("package", dep.get("content-type", ""), dep.get("unique-id", "")),
session=session, return_errors=True)
if dep_package:
dep_version = list(filter(lambda v: v.get("md5sum-partial") == dep.get("md5sum-partial"),
dep_package.get("versions", [])))
dep.update(dep_package)
if len(dep_version) == 1:
dep.update(dep_version[0])
package = {
"content-type": content_type,
"unique-id" : unique_id,
"name": "Another banana content daemon",
"description": "bla bla",
"url": "",
"archived": True,
"replaced-by": {"unique-id": "ff112233", "name": "BaNaNaS 8"},
"tags": ["old", "deprecated", "better"],
"authors": [{"display-name": "anna"}, {"display-name": "berta"}]
}

version = {
"content-type": content_type,
"unique-id" : unique_id,
"upload-date": "2020-02-29T11:11:11",
"md5sum-partial": "31415927",

"name": "Another banana content daemon",
"version": "0.1",
"description": "bla bla",
"url": "",
"tags": ["old", "deprecated", "better"],
"license": "custom",

"download-url": "https://openttd.org",
"filesize": "123456",

"dependencies": [{"content-type": "newgrf", "unique-id": "ffffffff", "md5sum-partial": "55443322", "upload-date": "2020-02-01T01:01:01", "name": "yolo", "version": "1.23"}],
"compatibility": [{"name": "master", "conditions": [">= 1.2", "< 1.5"]}]
}

return template("manager_version_info.html", session=session, package=package, version=version)

@@ -143,121 +110,102 @@ def manager_version_info(session, content_type, unique_id, upload_date):
@protected
def manager_version_edit(session, content_type, unique_id, upload_date):
csrf_context = ("manager_version_edit", content_type, unique_id, upload_date)
version = api_get(("package", content_type, unique_id, upload_date), session=session)
package = api_get(("package", content_type, unique_id), session=session)
messages = []

if flask.request.method == 'POST':
form = flask.request.form
valid_csrf = session.validate_csrf_token(form.get("csrf_token"), csrf_context)

valid_data = True
changes = dict()
record_change(changes, version, "name", form.get("name").strip(), True)
record_change(changes, version, "url", form.get("url").strip(), True)
record_change(changes, version, "version", form.get("version").strip())
record_change_compatibility(changes, version, form)
if not record_change_dependencies(changes, version, form, messages):
valid_data = False
record_change_tags(changes, version, form.get("tags"))
record_change_descripton(changes, version, form.get("description"))

version.update(changes)
if not valid_csrf:
messages.append("CSRF token expired. Please reconfirm your changes.")
elif valid_data and len(changes):
_, error = api_put(("package", content_type, unique_id, upload_date), json=changes,
session=session, return_errors=True)
if error:
messages.append(error)
else:
messages.append("Data updated")

package = {
"content-type": content_type,
"unique-id" : unique_id,
"name": "Another banana content daemon",
"description": "bla bla",
"url": "",
"archived": True,
"replaced-by": {"unique-id": "ff112233", "name": "BaNaNaS 8"},
"tags": ["old", "deprecated", "better"],
"authors": [{"display-name": "anna"}, {"display-name": "berta"}]
}

version = {
"content-type": content_type,
"unique-id" : unique_id,
"upload-date": "2020-02-29T11:11:11",
"md5sum-partial": "31415927",

"name": "Another banana content daemon",
"version": "0.1",
"description": "bla bla",
"url": "",
"tags": ["old", "deprecated", "better"],
"license": "custom",

"download-url": "https://openttd.org",
"filesize": "123456",

"dependencies": [{"content-type": "newgrf", "unique-id": "ffffffff", "md5sum-partial": "55443322", "upload-date": "2020-02-01T01:01:01", "name": "yolo", "version": "1.23"}],
"compatibility": [{"name": "master", "conditions": [">= 1.2", "< 1.5"]}]
}

deps_editable = True
compatibility = get_compatibility(version)

csrf_token = session.create_csrf_token(csrf_context)
return template("manager_version_edit.html", session=session, package=package, version=version,
compatibility=compatibility, deps_editable=deps_editable,
messages=messages, csrf_token=csrf_token)
csrf_token=csrf_token)


@app.route("/manager/new-package")
@protected
def manager_new_package(session):
new = api_post(("new-package",), json={}, session=session)
return redirect("manager_new_package_upload", token=new.get("upload-token", ""))
return redirect("manager_new_package_upload", token=123)


@app.route("/manager/new-package/<token>", methods=['GET', 'POST'])
@protected
def manager_new_package_upload(session, token):
csrf_context = ("manager_new_package_upload", token)
version = api_get(("new-package", token), session=session)
accept_tos = False
messages = []

if flask.request.method == 'POST':
form = flask.request.form
valid_csrf = session.validate_csrf_token(form.get("csrf_token"), csrf_context)
accept_tos = form.get("tos", "") == "accepted"

valid_data = True
changes = dict()
record_change(changes, version, "name", form.get("name").strip(), True)
record_change(changes, version, "url", form.get("url").strip(), True)
record_change(changes, version, "version", form.get("version").strip())
if form.get("license", "empty") != "empty":
record_change(changes, version, "license", form.get("license").strip())
record_change_compatibility(changes, version, form)
if not record_change_dependencies(changes, version, form, messages):
valid_data = False
record_change_tags(changes, version, form.get("tags"))
record_change_descripton(changes, version, form.get("description"))

version.update(changes)
if not valid_csrf:
messages.append("CSRF token expired. Please reconfirm your changes.")
elif valid_data and len(changes):
_, error = api_put(("new-package", token), json=changes, session=session, return_errors=True)
if error:
messages.append(error)
else:
# rerun validation
version = api_get(("new-package", token), session=session)
messages.append("Data updated")

new_files = []
for f in version.get("files", []):
if form.get("delete_{}".format(f["uuid"])) is not None:
_, error = api_delete(("new-package", token, f["uuid"]), session=session, return_errors=True)
else:
new_files.append(f)
version["files"] = new_files

if not accept_tos:
version.setdefault("errors", []).append("TOS not accepted")

if accept_tos and valid_data and form.get("publish") is not None:
_, error = api_put(("new-package", token, "publish"), json=changes, session=session, return_errors=True)
if error:
messages.append(error)
else:
content_type = version.get("content-type")
unique_id = version.get("unique-id")
redirect("manager_package_info", content_type=content_type, unique_id=unique_id)

content_type = version.get("content-type")
unique_id = version.get("unique-id")
if content_type and unique_id:
package = api_get(("package", content_type, unique_id), session=session)
else:
package = None

package = None

version = {
"content-type": "newgrf",
"unique-id" : "00FFCCDD",
"md5sum-partial": "31415927",

"name": "Another banana content daemon",
"version": "0.1",
"description": "bla bla",
"url": "",
"tags": ["old", "deprecated", "better"],
"license": "Custom",
"availability": "new-games",

"dependencies": [{"content-type": "newgrf", "unique-id": "ffffffff", "md5sum-partial": "55443322", "upload-date": "2020-02-01T01:01:01", "name": "yolo", "version": "1.23"}],
"compatibility": [{"name": "master", "conditions": [">= 1.2", "< 1.5"]}],

"files": [
{"uuid": "1", "filename": "foo.grf", "filesize": "12345"},
{"uuid": "2", "filename": "license.txt", "filesize": "2345", "errors": ["Invalid UTF-8 encoding."]},
],
"warnings": ["Don't lick pixels!"],
"errors": ["Universe stopped expanding."]
}

version.setdefault("files", []).sort(key=lambda v: v.get("filename", ""))

deps_editable = True
compatibility = get_compatibility(version)

csrf_token = session.create_csrf_token(csrf_context)
return template("manager_new_package.html", session=session, package=package, version=version,
compatibility=compatibility, licenses=_licenses, accept_tos=accept_tos, deps_editable=deps_editable,
messages=messages, csrf_token=csrf_token)
return template(
"manager_new_package.html",
session=session,
package=package,
version=version,
compatibility=compatibility,
licenses=_licenses,
accept_tos=accept_tos,
deps_editable=deps_editable,
tus_url="dummy",
upload_token="1122",
csrf_token=csrf_token,
)
@@ -0,0 +1,168 @@

.manager-table {
font-size: 12px;
table-layout: fixed;
width: 738px;
}

.manager-table th {
text-align: left;
vertical-align: top;
width: 120px;
}

.manager-table td {
border-bottom: 1px solid #FFFFFF;
border-top: 1px solid #FFFFFF;
padding: 1px 3px 1px 3px;
text-align: left;
}

.depList {
margin-top: 0px;
margin-bottom: 0px;
padding-left: 20px;
}

#bananas-table {
font-size: 12px;
width: 100%;
}

#bananas-table th {
text-align: left;
text-decoration: underline;
}

#bananas-table td {
border-bottom: 1px solid #FFFFFF;
border-top: 1px solid #FFFFFF;
padding: 1px 3px 1px 3px;
text-align: left;
vertical-align: top;
}

#bananas-table .odd {
background-color: #EEEEEE;
}
#bananas-table .even {
background-color: #FFFFFF;
}
#bananas-table tr.odd:hover, #bananas-table tr.even:hover, #bananas-table tr.odd:hover td, #bananas-table tr.even:hover td {
background-color: #CCCCCC;
border-bottom: 1px dashed #000000;
border-top: 1px dashed #000000;
}

#bananas-table .name {
width: 250px;
}
#bananas-table .type {
width: 80px;
}
#bananas-table .version {
width: 80px;
}
#bananas-table .grf {
width: 70px;
}
#bananas-table .downloads {
width: 70px;
}
#bananas-table .date {
width: 70px;
}
#bananas-table .author {
width: 70px;
}
#bananas-table .deps {
width: 70px;
}
#bananas-table .license {
width: 100px;
}

/* the package table declarations are repetitive and can possibly be consolidated, they were written in a situation where JFDI > DRY */
.package-info-table {
width: 100%;
}
.package-info-table th {
width: 150px;
vertical-align: top;
padding-top: 7px;
padding-bottom: 7px;
}
.package-info-table .tag-list {
margin: 0;
padding: 0;
}
.package-info-table .tag-list li {
display: inline-block;
padding-right: 10px;
margin-right: 10px;
border-right: solid 1px #ddd;
}
.package-info-table .author-list {
margin: 0;
padding: 0;
}
.package-info-table .author-list li {
list-style-type: none;
}

.package-edit-table th {
width: 150px;
vertical-align: top;
padding-top: 7px;
padding-bottom: 7px;
}
.package-edit-table .author-list {
margin: 0;
padding: 0;
}
.package-edit-table .author-list li {
list-style-type: none;
}

.package-upload-table th {
width: 150px;
vertical-align: top;
padding-top: 7px;
padding-bottom: 7px;
}
.package-upload-table .author-list {
margin: 0;
padding: 0;
}
.package-upload-table .author-list li {
list-style-type: none;
}

.package-version-edit-table th {
width: 150px;
vertical-align: top;
padding-top: 7px;
padding-bottom: 7px;
}
.package-version-edit-table .author-list {
margin: 0;
padding: 0;
}
.package-version-edit-table .author-list li {
list-style-type: none;
}

.package-version-info-table th {
width: 150px;
vertical-align: top;
padding-top: 7px;
padding-bottom: 7px;
}
.package-version-info-table .author-list {
margin: 0;
padding: 0;
}
.package-version-info-table .author-list li {
list-style-type: none;
}

@@ -0,0 +1,213 @@
body {
margin: 0px;
padding: 0px;
text-align: center;
}
html {
background: url("/static/img/background.png") repeat;
color: black;
font-family: "Trebuchet MS", Arial, Verdana, Sans-Serif;
font-size: 12px;
}
select {
color: black;
}
img {
border: 0px;
}
a {
color: #DD6000;
}
hr {
background: url("/static/img/hr.png") repeat-x;
border: none;
height: 2px;
width: 95%;
}
h3, .h3 {
font-weight: normal;
font-size: 16px;
margin: 0px;
text-decoration: underline;
}
h4, .h4 {
font-weight: normal;
font-size: 14px;
margin: 0px;
}
h5, .h5 {
font-weight: normal;
font-size: 12px;
margin: 0px;
}

.nowrap {
overflow: hidden;
white-space: nowrap;

/* The following elements are not part of CSS2, but are required for some
* (broken) browsers who do not understand the former definitions. */
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}

#header {
background: url("/static/img/header-bg.png") repeat;
height: 90px;
margin: 0px auto;
width: 940px;
}
#header-left {
background: url("/static/img/header-bg-left.png") repeat-y;
float: left;
height: 90px;
width: 15px;
}
#header-right {
background: url("/static/img/header-bg-right.png") repeat-y;
float: right;
height: 90px;
width: 15px;
}

#header-logo {
float: right;
width: 400px;
}

#ovh-header {
float: right;
height: 90px;
line-height: 11px;
margin-top: -2px;
margin-right: -6px;
padding: 0px;
width: 90px;
}
#openttd-logo {
background: url("/static/img/openttd-64.gif") no-repeat;
background-position: left center;
float: right;
height: 75px;
margin-top: 6px;
width: 250px;
}
#openttd-logo-text {
height: 29px;
margin: 36px 0px 0px 77px;
width: 151px;
}
#openttd-logo-text-noai {
height: 29px;
margin: 15px 0px 0px 75px;
width: 151px;
}
#openttd-logo-text-bananas {
height: 29px;
margin: 18px 0px 0px 75px;
width: 151px;
}
#openttd-logo-text-security {
height: 29px;
margin: 18px 0px 0px 75px;
width: 151px;
}
#openttd-logo-text-binaries {
height: 29px;
margin: 36px 0px 0px 77px;
width: 151px;
}
#openttd-logo-text-translator {
height: 29px;
margin: 28px 0px 0px 75px;
width: 151px;
}

#navigation {
background: url("/static/img/navigation-bg.png") repeat-x;
height: auto !important;
height: 32px;
margin: 0px auto;
min-height: 32px;
overflow: hidden;
width: 940px;
}
#navigation-left {
background: url("/static/img/navigation-bg-left.png") no-repeat;
float: left;
height: 32px;
width: 15px;
}
#navigation-right {
background: url("/static/img/navigation-bg-right.png") no-repeat;
float: right;
height: 32px;
width: 15px;
}
#navigation-bar {
float: left;
margin: 0px;
padding: 0px;
}
#navigation-bar li {
font-size: 12px;
float: left;
line-height: 32px;
list-style-type: none;
}
#navigation-bar li a {
color: #DDDDDD;
display: block;
padding: 0px 7px 0px 7px;
text-decoration: none;
}
#navigation-bar li a:hover {
background: url("/static/img/navigation-bg-hover.png") repeat-x;
color: #444444;
}
#navigation-bar li.selected {
background: url("/static/img/navigation-bg-selected.png") repeat-x;
}
#navigation-bar li.selected a {
color: #444444;
}

#content-main {
background-color: white;
margin: 7px auto;
width: 916px;
padding: 7px;
border-radius: 4px;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
}
#content-bottom-links {
float: left;
font-size: 11px;
padding: 7px 5px 0px 5px;
}
#content-bottom-copyright {
float: right;
font-size: 11px;
padding: 7px 5px 0px 5px;
}
#content {
margin: 0px 12px 0px 12px;
padding-top: 12px;
}
body > footer {
background-color: white;
margin: 12px auto 36px auto;
padding: 7px 7px 6px 7px;
overflow: hidden;
border-radius: 5px;
box-shadow: 0 0 3px 3px rgba(0, 0, 0, 0.11);
width: 916px;
}

#hr-clear {
clear: both;
}

.mono {
font-family: 'Courier New', monospace;
}
@@ -0,0 +1,57 @@

#section, #section-full {
text-align: left;
}
.section-header {
background: url("/static/img/section-bg.png") no-repeat;
color: #365800;
line-height: 38px;
height: 38px;
padding: 0px 0px 0px 10px;
text-align: left;
width: 500px;
}
.section-header h1 {
font-size: 18px;
margin-right: 20px;
margin-top: 0;
text-decoration: none;
}

.section-item {
padding-top: 10px;
}
.section-item .title {
font-size: 16px;
float: left;
padding-top: 5px;
}
.section-item .title h3, .section-list li .header, .section-item .title a {
color: #000000;
font-size: 18px;
font-weight: bold;
text-decoration: none;
}
.section-item .date {
text-align: right;
}
.section-item .user {
text-align: right;
padding-bottom: 5px;
}
.section-item .content {
border-top: 1px solid #EEEEEE;
font-size: 14px;
padding-top: 15px;
padding-bottom: 30px;
}

.section-list {
list-style-type: none;
}

.right {
float: right;
margin-bottom: 20px;
}

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,109 @@
"use strict";

var file_list = document.querySelector("ul[id='file_list']");
var tus_url = file_list.dataset.tusUrl;
var upload_token = file_list.dataset.uploadToken;

for (let file of file_list.getElementsByTagName("li")) {
let filename = file.dataset.filename;
if (filename == null) continue;

let button = file.querySelector("button");
button.addEventListener('click', function() {
remove_file(filename);
});
}

var more_files = document.querySelector("li[id='more_files']");
var file_selector = document.querySelector("input[id='file_selector']");
var button_start = document.querySelector("button[id='start_upload']");
var removed_files = document.querySelector("input[id='removed_files']");
var button_validate = document.querySelector("button[id='validate']");
var button_publish = document.querySelector("button[id='publish']");
button_start.addEventListener("click", add_upload);

var upload_queue = [];
var upload_file = null;
var upload_id = null;
var upload_instance = null;

function get_uuid() {
let url = upload_instance.url;
return url.substr(url.lastIndexOf("/") + 1);
}

function get_file(filename) {
for (let file of file_list.getElementsByTagName("li")) {
if (file.dataset.filename == filename) return file;
}
return null;
}

function remove_file(filename) {
upload_queue = upload_queue.filter(file => file.name != filename);
if (upload_instance != null && upload_file.name == filename) {
upload_instance.abort();
}
let file = get_file(filename);
if (file != null) {
let uuid = file.dataset.uuid;
if (uuid != null) {
removed_files.value = removed_files.value.concat(",", uuid);
}
file.remove();
}
}

function add_file(filename, status) {
let new_li = document.createElement("li");
new_li.dataset.filename = filename;
new_li.appendChild(document.createTextNode(filename + " "));

let new_status = document.createElement("span");
new_status.appendChild(document.createTextNode(status));
new_li.appendChild(new_status);
new_li.appendChild(document.createTextNode(" "));

let new_button = document.createElement("button");
new_button.type = "button";
new_button.addEventListener('click', function() {
remove_file(filename);
});
new_button.appendChild(document.createTextNode("Delete file"));
new_li.appendChild(new_button);

file_list.insertBefore(new_li, more_files);
}

function set_file_status(filename, uuid, status) {
let file = get_file(filename);
if (file != null) {
file.dataset.uuid = uuid;
let old_status = file.querySelector("span");
old_status.textContent = status;
}
}

function add_upload() {
if (file_selector.value == "") return;
for (let i = 0; i < file_selector.files.length; i++) {
let file = file_selector.files[i];
if (get_file(file.name) != null) continue;
upload_queue.push(file);
add_file(file.name, "queued...");
}
file_selector.value = "";
if (upload_instance == null) start_next_upload();
}

function start_next_upload() {
button_validate.disabled = upload_queue.length > 0;
button_publish.disabled = upload_queue.length > 0;
if (upload_queue.length == 0) return;

upload_file = upload_queue[0];
upload_queue.shift();

set_file_status(upload_file.name, "abcd", "(" + Math.floor(123456 / 1024) + " kB)");
start_next_upload();
}
@@ -3,39 +3,71 @@
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock %} - BaNaNaS - OpenTTD</title>
<link rel="icon" href="/static/favicon.ico" type="image/icon" />
<link rel="stylesheet" href="/static/css/base.css" />
<link rel="stylesheet" href="/static/css/page.css" />
<link rel="stylesheet" href="/static/css/bananas.css" />
</head>
<body>
<table><tr>
<td><a href="/">BaNaNaS</a></td>
<td><a href="/package/base-graphics">Base-Graphics</a><br/>
<a href="/package/base-sounds">Base-Sounds</a><br/>
<a href="/package/base-music">Base-Music</a></td>
<td><a href="/package/newgrf">NewGRFs</a></td>
<td><a href="/package/ai">AIs</a><br/>
<a href="/package/ai-library">AI-Libraries</a></td>
<td><a href="/package/game-script">Game-Scripts</a><br/>
<a href="/package/game-script-library">Game-Script-Libraries</a></td>
<td><a href="/package/scenario">Scenarios</a><br/>
<a href="/package/heightmap">Heightmaps</a></td>
<td>
{% if session and session.is_auth %}
<marquee>{{ session.display_name }}</marquee><br/>
<a href="/manager">Manager</a><br/>
<a href="/logout">Logout</a>
{% else %}
<a href="/login">Login</a>
{% endif %}
</td>
</tr></table>

<header id="header">
<div id="header-left"></div>
<div id="header-right"></div>
<div id="header-logo">
<div id="openttd-logo">
<div id="openttd-logo-text-bananas"><a href="/"><img src="/static/img/openttd-logo-bananas.png" alt="BaNaNaS"></a></div>
</div>
</div>
</header>
<nav id="navigation">
<div id="navigation-left"></div>
<div id="navigation-right"></div>
<ul id="navigation-bar">
<li class="selected"><a href="/">BaNaNaS</a></li>
<li><a href="/package/base-graphics">Base-Graphics</a></li>
<li><a href="/package/base-sounds">Base-Sounds</a></li>
<li><a href="/package/base-music">Base-Music</a></li>
<li><a href="/package/newgrf">NewGRFs</a></li>
<li><a href="/package/ai">AIs</a></li>
<li><a href="/package/ai-library">AI-Libraries</a></li>
<li><a href="/package/game-script">Game-Scripts</a></li>
<li><a href="/package/game-script-library">Game-Script-Libraries</a></li>
<li><a href="/package/scenario">Scenarios</a></li>
<li><a href="/package/heightmap">Heightmaps</a></li>
<li>
<a href="/manager">Manager</a><br/>
{% if session and session.is_auth %}
<marquee>{{ session.display_name }}</marquee><br/>
<a href="/logout">Logout</a>
{% endif %}
</li>
</ul>
</nav>
<div id="content-main">
<div id="section-full">
<header class="section-header">
{% block header %}{% endblock %}
</header>
<main class="section-item">
<div class="content">
{% if messages %}
<ul>
<ul id="system-notices">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% block content %}{% endblock %}
</div>
</main>
</div>
</div>
<footer id="footer">
<div id="content-bottom-links">
<a href="https://www.openttd.org/policy.html">Privacy Policy</a> |
</div>
<div id="content-bottom-copyright">
Copyright &copy; 2005-{{ copyyear }} OpenTTD Team
</div>
</footer>
</body>
</html>
@@ -14,38 +14,45 @@ <h3>Step 1: Terms of service</h3>
</p>

<h3>Step 2: Upload files</h3>
<ul>
<input type="hidden" name="removed_files" id="removed_files" value=""/>
<ul id="file_list"
data-tus-url="{{ tus_url }}"
data-upload-token="{{ upload_token }}">
{% for file in version["files"] %}
<li>
<li data-filename="{{ file["filename"] }}" data-uuid="{{ file["uuid"] }}">
{{ file["filename"] }} ({{ (file["filesize"] | int) // 1024 }} kB)
<button type="submit" name="delete_{{ file["uuid"] }}">Delete file</button>
<button type="button">Delete file</button>
<ul>
{% for error in file["errors"] %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
<li id="more_files">
<input type="file" multiple id="file_selector"/>
<button type="button" id="start_upload">Add</button>
</li>
</ul>

<h3>Step 3: Complete the description</h3>
Note: Some of the info is filled automatically after uploading the files.

<table>
<tr><td>Content Id</td><td>{{ version["content-type"] }}/{{ version["unique-id"] }}/{{ version["md5sum-partial"] }}</td></tr>
<tr><td>Name</td><td>
<table class="package-upload-table">
<tr><th>Content Id</th><td>{{ version["content-type"] }}/{{ version["unique-id"] }}/{{ version["md5sum-partial"] }}</td></tr>
<tr><th>Name</th><td>
Enter the name of the package <b>without version number</b>. There is a separate entry for the version.
{% if package %}Leave empty to use the name from the package.{% endif %}<br/>
<input type="text" name="name" value="{{ version["name"] }}" placeholder="{{ package["name"] }}"/>
</td></tr>
<tr><td>Project site</td><td>
<tr><th>Project site</th><td>
{% if package %}Leave empty to use the URL from the package.{% endif %}<br/>
<input type="url" name="url" value="{{ version["url"] }}" placeholder="{{ package["url"] }}"/>
</td></tr>
<tr><td>Version</td><td>
<tr><th>Version</th><td>
<input type="text" name="version" value="{{ version["version"] }}"/>
</td></tr>
<tr><td>License</td><td>
<tr><th>License</th><td>
Unsure which license to pick? <a href="https://www.tt-forums.net/viewtopic.php?f=68&amp;t=55814" target="_blank">Look here</a><br/>
<select name="license">
{% if not version["license"] %}
@@ -56,7 +63,7 @@ <h3>Step 3: Complete the description</h3>
{% endfor %}
</select>
</td></tr>
<tr><td>Compatibility</td><td>
<tr><th>Compatibility</th><td>
Enter version requirements like "&gt;= 1.2.0" or "&lt; 1.10.0".
<table>
<tr>
@@ -73,7 +80,7 @@ <h3>Step 3: Complete the description</h3>
</tr>
</table>
</td></tr>
<tr><td>Dependencies</td><td>
<tr><th>Dependencies</th><td>
{% if deps_editable %}
Enter one content-id per row. You can find the 'Content Id' on the version detail page of every content item.<br/>
<textarea name="dependencies" cols="50" rows="10">
@@ -89,7 +96,7 @@ <h3>Step 3: Complete the description</h3>
</ul>
{% endif %}
</td></tr>
<tr><td>Tags</td><td>Enter one tag per row{% if package %}, leave empty to use the tags from the package{% endif %}:<br/>
<tr><th>Tags</th><td>Enter one tag per row{% if package %}, leave empty to use the tags from the package{% endif %}:<br/>
<textarea name="tags" cols="20" rows="10" placeholder="
{%- for t in package["tags"] -%}
{{ t }}
@@ -99,7 +106,7 @@ <h3>Step 3: Complete the description</h3>
{{ t }}
{% endfor -%}
</textarea></td></tr>
<tr><td>Description</td><td>
<tr><th>Description</th><td>
{% if package %}Leave empty to use the description from the package.{% endif %}<br/>
<textarea name="description" cols="50" rows="20" placeholder="{{ package["description"] }}">
{{- version["description"] -}}
@@ -126,9 +133,11 @@ <h3>Step 4: Validate and publish</h3>
</ul>
{% endif %}

<button type="submit" name="validate">Validate</button>
<button type="submit" name="publish">Publish</button>
<button type="submit" name="validate" id="validate">Validate</button>
<button type="submit" name="publish" id="publish">Publish</button>

</form>

<script src="/static/uploader.js"></script>

{% endblock %}
@@ -1,32 +1,36 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}{{ package["name"] }}{% endblock %}</h1>
<h3>{{ package["content-type"] }}/{{ package["unique-id"] }}</h3>
{% endblock %}
{% block content %}

<!-- {#
Known issues here:
1) form inputs require labels
2) in most cases, it's difficult to justify marking up forms as tables, we'll want to rework this
#} -->
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<table>
<tr><td>Content Id</td><td>{{ package["content-type"] }}/{{ package["unique-id"] }}</td></tr>
<table class="package-edit-table">
<tr><th>Content Id</th><td>{{ package["content-type"] }}/{{ package["unique-id"] }}</td></tr>
<tr><td>Name</td><td>
<input type="text" name="name" value="{{ package["name"] }}"/>
</td></tr>
<tr><td>Project site</td><td>
<tr><th>Project site</th><td>
<input type="url" name="url" value="{{ package["url"] }}"/>
</td></tr>
<tr><td>Tags</td><td>Enter one tag per row:<br/>
<tr><th>Tags</th><td>Enter one tag per row:<br/>
<textarea name="tags" cols="20" rows="10">
{%- for t in package["tags"] -%}
{{ t }}
{% endfor -%}
</textarea></td></tr>
<tr><td>Authors</td><td><ul>
<tr><th>Authors</th><td><ul class="author-list">
{% for a in package["authors"] %}
<li>{{ a["display-name"] }}</li>
{% endfor %}
</ul></td></tr>
<tr><td>Description</td><td>
<tr><th>Description</th><td>
<textarea name="description" cols="50" rows="20">
{{- package["description"] -}}
</textarea></td></tr>
@@ -1,55 +1,70 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}{{ package["name"] }}{% endblock %}</h1>
<h3>{{ package["content-type"] }}/{{ package["unique-id"] }}</h3>
{% endblock %}
{% block content %}

<a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/edit">Edit package meta data</a>
<table>
<tr><td>Content Id</td><td>{{ package["content-type"] }}/{{ package["unique-id"] }}</td></tr>
<tr><td>Name</td><td>{{ package["name"] }}</td></tr>
<tr><td>Project site</td><td>
{% if package["url"] %}
<a href="{{ package["url"] }}" target="_blank">{{ package["url"] }}</a>
{% endif %}
</td></tr>
<tr><td>Tags</td><td><ul>
{% for t in package["tags"] %}
<li>{{ t }}</li>
{% endfor %}
</ul></td></tr>
<tr><td>Authors</td><td><ul>
{% for a in package["authors"] %}
<li>{{ a["display-name"] }}</li>
{% endfor %}
</ul></td></tr>
<tr><td>Description</td><td>
{% for l in package["description"].splitlines() %}
{{ l }}<br/>
{% endfor %}
</td></tr>
<p><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/edit">Edit package meta data</a></p>

<table class="package-info-table">
<tbody>
<tr><th>Content Id</th><td>{{ package["content-type"] }}/{{ package["unique-id"] }}</td></tr>
<tr><th>Name</th><td>{{ package["name"] }}</td></tr>
<tr><th>Project site</th><td>
{% if package["url"] %}
<a href="{{ package["url"] }}" target="_blank">{{ package["url"] }}</a>
{% endif %}
{% if not package["url"] %}
-
{% endif %}
</td></tr>
<tr><th>Tags</th><td>
<ul class="tag-list">
{% for t in package["tags"] %}
<li>{{ t }}</li>
{% endfor %}
</ul>
</td></tr>
<tr><th>Authors</th><td>
<ul class="author-list">
{% for a in package["authors"] %}
<li>{{ a["display-name"] }}</li>
{% endfor %}
</ul>
</td></tr>
<tr><th>Description</th><td>
{% for l in package.get("description", "").splitlines() %}
{{ l }}<br/>
{% endfor %}
</td></tr>
</tbody>
</table>

<table>
<tr>
<th>Version</th>
<th>Upload date</th>
<th>MD5 (partial)</th>
<th>License</th>
<th>Availability</th>
<th>Edit meta data</th>
</tr>
<hr />

<table id="bananas-table">
<thead>
<tr>
<th>Version</th>
<th>Upload date</th>
<th>MD5 (partial)</th>
<th>License</th>
<th>Availability</th>
<th>Edit meta data</th>
</tr>
</thead>
<tbody>
{% for version in package["versions"] %}
<tr>
<td><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["upload-date"] }}">{{ version["version"] }}</a></td>
<td>{{ version["upload-date"] }}</td>
<td>{{ version["md5sum-partial"] }}</td>
<td>{{ version["license"] }}</td>
<td>{{ version["availability"] }}</td>
<td><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["upload-date"] }}/edit">Edit</a></td>
</tr>
<tr>
<td><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["upload-date"] }}">{{ version["version"] }}</a></td>
<td>{{ version["upload-date"] }}</td>
<td>{{ version["md5sum-partial"] }}</td>
<td>{{ version["license"] }}</td>
<td>{{ version["availability"] }}</td>
<td><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["upload-date"] }}/edit">Edit</a></td>
</tr>
{% endfor %}
</tbody>
</table>

{% endblock %}
@@ -4,48 +4,53 @@ <h1>{% block title %}Awesome content by {{ session.display_name }}{% endblock %}
{% endblock %}
{% block content %}

<table>
<tr>
<th rowspan="2">Type</th>
<th rowspan="2">Id</th>
<th rowspan="2">Name</th>
<th colspan="3">All versions</th>
<th colspan="3">Versions for new games</th>
<th rowspan="2">Upload</th>
</tr>
<tr>
<th>Number</th>
<th>Latest version</th>
<th>Upload date</th>
<th>Number</th>
<th>Latest version</th>
<th>Upload date</th>
</tr>
{% for package in packages %}
<tr>
<td>{{ package["content-type"] }}</td>
<td>{{ package["unique-id"] }}</td>
<td><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}">{{ package["name"] }}</a></td>
<td>{{ package["num-all"] }}</td>
{% set latest = package["latest-all"] %}
{% if latest %}
<td><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ latest["upload-date"] }}">{{ latest["version"] }}</a></td>
<td>{{ latest["upload-date"] }}</td>
{% else %}
<td></td><td></td>
{% endif %}
<td>{{ package["num-newgame"] }}</td>
{% set latest = package["latest-newgame"] %}
{% if latest %}
<td><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ latest["upload-date"] }}">{{ latest["version"] }}</a></td>
<td>{{ latest["upload-date"] }}</td>
{% else %}
<td></td><td></td>
{% endif %}
<td><a href="/manager/new-package?unique-id={{ package["unique-id"] }}">Upload update</a></td>
</tr>
{% endfor %}
<table id="bananas-table">
<thead>
<tr>
<th rowspan="2">Type</th>
<th rowspan="2">Id</th>
<th rowspan="2">Name</th>
<th colspan="3">All versions</th>
<th colspan="3">Versions for new games</th>
<th rowspan="2">Upload</th>
</tr>
<tr>
<th>Number</th>
<th>Latest version</th>
<th>Upload date</th>
<th>Number</th>
<th>Latest version</th>
<th>Upload date</th>
</tr>
</thead>
<tbody>
{% for package in packages %}
<tr class="{{ loop.cycle('odd', 'even') }}">
<td>{{ package["content-type"] }}</td>
<td>{{ package["unique-id"] }}</td>
<td><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}">{{ package["name"] }}</a></td>
<td>{{ package["num-all"] }}</td>
{% set latest = package["latest-all"] %}
{% if latest %}
<td><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ latest["upload-date"] }}">{{ latest["version"] }}</a></td>
<td>{{ latest["upload-date"] }}</td>
{% else %}
<td></td><td></td>
{% endif %}
<td>{{ package["num-newgame"] }}</td>
{% set latest = package["latest-newgame"] %}
{% if latest %}
<td><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ latest["upload-date"] }}">{{ latest["version"] }}</a></td>
<td>{{ latest["upload-date"] }}</td>
{% else %}
<td></td><td></td>
{% endif %}
<td><a href="/manager/new-package?unique-id={{ package["unique-id"] }}">Upload update</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<hr />
<a href="/manager/new-package">Upload new content</a>

{% endblock %}
@@ -2,26 +2,25 @@
{% block title %}{{ version["name"] or package["name"] }} {{ version["version"] }}{% endblock %}
{% block header %}
<h1><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}">{{ version["name"] or package["name"] }}</a> {{ version["version"] }}</h1>
<h3>{{ version["content-type"] }}/{{ version["unique-id"] }}/{{ version["upload-date"] }}</h3>
{% endblock %}
{% block content %}

<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<table>
<tr><td>Content Id</td><td>{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["md5sum-partial"] }}</td></tr>
<tr><td>Name</td><td>
<table class="package-version-edit-table">
<tr><th>Content Id</th><td>{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["md5sum-partial"] }}</td></tr>
<tr><th>Name</th><td>
Leave empty to use the name from the package.<br/>
<input type="text" name="name" value="{{ version["name"] }}" placeholder="{{ package["name"] }}"/>
</td></tr>
<tr><td>Project site</td><td>
<tr><th>Project site</th><td>
Leave empty to use the URL from the package.<br/>
<input type="url" name="url" value="{{ version["url"] }}" placeholder="{{ package["url"] }}"/>
</td></tr>
<tr><td>Version</td><td>
<tr><th>Version</th><td>
<input type="text" name="version" value="{{ version["version"] }}"/>
</td></tr>
<tr><td>Compatibility</td><td>
<tr><th>Compatibility</th><td>
Enter version requirements like "&gt;= 1.2.0" or "&lt; 1.10.0".
<table>
<tr>
@@ -38,7 +37,7 @@ <h3>{{ version["content-type"] }}/{{ version["unique-id"] }}/{{ version["upload-
</tr>
</table>
</td></tr>
<tr><td>Dependencies</td><td>
<tr><th>Dependencies</th><td>
{% if deps_editable %}
Enter one content-id per row. You can find the 'Content Id' on the version detail page of every content item.<br/>
<textarea name="dependencies" cols="50" rows="10">
@@ -54,7 +53,7 @@ <h3>{{ version["content-type"] }}/{{ version["unique-id"] }}/{{ version["upload-
</ul>
{% endif %}
</td></tr>
<tr><td>Tags</td><td>Enter one tag per row, leave empty to use the tags from the package:<br/>
<tr><th>Tags</th><td>Enter one tag per row, leave empty to use the tags from the package:<br/>
<textarea name="tags" cols="20" rows="10" placeholder="
{%- for t in package["tags"] -%}
{{ t }}
@@ -64,7 +63,7 @@ <h3>{{ version["content-type"] }}/{{ version["unique-id"] }}/{{ version["upload-
{{ t }}
{% endfor -%}
</textarea></td></tr>
<tr><td>Description</td><td>
<tr><th>Description</th><td>
Leave empty to use the description from the package.<br/>
<textarea name="description" cols="50" rows="20" placeholder="{{ package["description"] }}">
{{- version["description"] -}}
@@ -2,58 +2,68 @@
{% block title %}{{ version["name"] or package["name"] }} {{ version["version"] }}{% endblock %}
{% block header %}
<h1><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}">{{ version["name"] or package["name"] }}</a> {{ version["version"] }}</h1>
<h3>{{ version["content-type"] }}/{{ version["unique-id"] }}/{{ version["upload-date"] }}</h3>
{% endblock %}
{% block content %}

<a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["upload-date"] }}/edit">Edit version meta data</a>
<table>
<tr><td>Content Id</td><td>{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["md5sum-partial"] }}</td></tr>
<tr><td>Name</td><td>{{ version["name"] or package["name"] }}</td></tr>
<tr><td>Project site</td><td>
{% set url = version["url"] or package["url"] %}
{% if url %}
<a href="{{ url }}" target="_blank">{{ url }}</a>
{% endif %}
</td></tr>
<tr><td>Version</td><td>{{ version["version"] }}</td></tr>
<tr><td>Upload date</td><td>{{ version["upload-date"] }}</td></tr>
<tr><td>MD5 (partial)</td><td>{{ version["md5sum-partial"] }}</td></tr>
<tr><td>License</td><td>{{ version["license"] }}</td></tr>
<tr><td>Download availability</td><td>{{ version["availability"] }}</td></tr>
<tr><td>Download</td>
{% if version["download-url"] %}
<td><a href="{{ version["download-url"] }}">{{ (version["filesize"] | int) // 1024 }} kB</a></td>
{% else %}
<td>Not available</td>
{% endif %}
</tr>
<tr><td>Compatibility</td><td><ul>
{% for compat in version["compatibility"] %}
<li>{{ compat["name"] }}: {{ compat["conditions"] | join(", ") }}</li>
{% endfor %}
</ul></td></tr>
<tr><td>Dependencies</td><td><ul>
{% for dep in version["dependencies"] %}
<li><a href="/package/{{ dep["content-type"] }}/{{ dep["unique-id"] }}/{{ dep["upload-date"] }}" target="_blank">{{ dep["name"] }} {{ dep["version"] }}</a></li>
{% endfor %}
</ul></td></tr>
<tr><td>Tags</td><td><ul>
{% set tags = version["tags"] or package["tags"] %}
{% for t in tags %}
<li>{{ t }}</li>
{% endfor %}
</ul></td></tr>
<tr><td>Authors</td><td><ul>
{% for a in package["authors"] %}
<li>{{ a["display-name"] }}</li>
{% endfor %}
</ul></td></tr>
<tr><td>Description</td><td>
{% for l in (version["description"] or package["description"]).splitlines() %}
{{ l }}<br/>
<p><a href="/manager/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["upload-date"] }}/edit">Edit version meta data</a></p>

<table class="package-info-table">
<tbody>
<tr><th>Content Id</th><td>{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["md5sum-partial"] }}</td></tr>
<tr><th>Name</th><td>{{ version["name"] or package["name"] }}</td></tr>
<tr><th>Project site</th><td>
{% set url = version["url"] or package["url"] %}
{% if url %}
<a href="{{ url }}" target="_blank">{{ url }}</a>
{% endif %}
</td></tr>
<tr><th>Version</th><td>{{ version["version"] }}</td></tr>
<tr><th>Upload date</th><td>{{ version["upload-date"] }}</td></tr>
<tr><th>MD5 (partial)</th><td>{{ version["md5sum-partial"] }}</td></tr>
<tr><th>License</th><td>{{ version["license"] }}</td></tr>
<tr><th>Download availability</th><td>{{ version["availability"] }}</td></tr>
<tr><th>Download</th>
{% if version["download-url"] %}
<td><a href="{{ version["download-url"] }}">{{ (version["filesize"] | int) // 1024 }} kB</a></td>
{% else %}
<td>Not available</td>
{% endif %}
</tr>
<tr><th>Compatibility</th><td>
<ul>
{% for compat in version["compatibility"] %}
<li>{{ compat["name"] }}: {{ compat["conditions"] | join(", ") }}</li>
{% endfor %}
</ul>
</td></tr>
<tr><th>Dependencies</th><td>
<ul>
{% for dep in version["dependencies"] %}
<li><a href="/package/{{ dep["content-type"] }}/{{ dep["unique-id"] }}/{{ dep["upload-date"] }}" target="_blank">{{ dep["name"] }} {{ dep["version"] }}</a></li>
{% endfor %}
</ul>
</td></tr>
<tr><th>Tags</th><td>
<ul class="tag-list">
{% set tags = version["tags"] or package["tags"] %}
{% for t in tags %}
<li>{{ t }}</li>
{% endfor %}
</ul>
</td></tr>
<tr><th>Authors</th><td>
<ul class="author-list">
{% for a in package["authors"] %}
<li>{{ a["display-name"] }}</li>
{% endfor %}
</ul>
</td></tr>
<tr><th>Description</th><td>
{% for l in (version["description"] or package["description"] or "").splitlines() %}
{{ l }}<br/>
{% endfor %}
</td></tr>
</td></tr>
</tbody>
</table>

{% endblock %}
@@ -1,64 +1,74 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}{{ package["name"] }}{% endblock %}</h1>
<h3>{{ package["content-type"] }}/{{ package["unique-id"] }}</h3>
<p>{{ package["content-type"] }}/{{ package["unique-id"] }}</p>
{% endblock %}
{% block content %}

{% if package.get("archived") %}
<p><b> This content is archived and is no longer available for new games. </b></p>
{% endif %}
{% if package.get("replaced-by") %}
<p><b> This content is deprecated. Please use <a href="/package/{{ package["content-type"] }}/{{ package["replaced-by"]["unique-id"] }}">{{ package["replaced-by"]["name"] }} ({{ package["replaced-by"]["unique-id"] }})</a> instead. </b></p>
<p><b> This content is deprecated. Please use <a href="/package/{{ package["replaced-by"]["content-type"] }}/{{ package["replaced-by"]["unique-id"] }}">{{ package["replaced-by"]["name"] }} ({{ package["replaced-by"]["unique-id"] }})</a> instead. </b></p>
{% endif %}

<table>
<tr><td>Content type</td><td>{{ package["content-type"] }}</td></tr>
<tr><td>Content Id</td><td>{{ package["unique-id"] }}</td></tr>
<tr><td>Name</td><td>{{ package["name"] }}</td></tr>
<tr><td>Project site</td><td>
{% if package["url"] %}
<a href="{{ package["url"] }}" target="_blank">{{ package["url"] }}</a>
{% endif %}
</td></tr>
<tr><td>Tags</td><td><ul>
{% for t in package["tags"] %}
<li>{{ t }}</li>
{% endfor %}
</ul></td></tr>
<tr><td>Authors</td><td><ul>
{% for a in package["authors"] %}
<li>{{ a["display-name"] }}</li>
{% endfor %}
</ul></td></tr>
<tr><td>Description</td><td>
{% for l in package["description"].splitlines() %}
{{ l }}<br/>
<tbody>
<tr><th>Content type</th><td>{{ package["content-type"] }}</td></tr>
<tr><th>Content Id</th><td>{{ package["unique-id"] }}</td></tr>
<tr><th>Name</th><td>{{ package["name"] }}</td></tr>
<tr><th>Project site</th><td>
{% if package["url"] %}
<a href="{{ package["url"] }}" target="_blank">{{ package["url"] }}</a>
{% endif %}
</td></tr>
<tr><th>Tags</th><td>
<ul>
{% for t in package["tags"] %}
<li>{{ t }}</li>
{% endfor %}
</td></tr>
</ul>
</td></tr>
<tr><th>Authors</th><td>
<ul>
{% for a in package["authors"] %}
<li>{{ a["display-name"] }}</li>
{% endfor %}
</ul>
</td></tr>
<tr><th>Description</th><td>
{% for l in package.get("description", "").splitlines() %}
{{ l }}<br/>
{% endfor %}
</td></tr>
</tbody>
</table>

<table>
<tr>
<th>Version</th>
<th>Upload date</th>
<th>MD5 (partial)</th>
<th>License</th>
<th>Download</th>
</tr>
<thead>
<tr>
<th>Version</th>
<th>Upload date</th>
<th>MD5 (partial)</th>
<th>License</th>
<th>Download</th>
</tr>
</thead>
<tbody>
{% for version in package["versions"] %}
<tr>
<td><a href="/package/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["upload-date"] }}">{{ version["version"] }}</a></td>
<td>{{ version["upload-date"] }}</td>
<td>{{ version["md5sum-partial"] }}</td>
<td>{{ version["license"] }}</td>
{% if version["download-url"] %}
<td><a href="{{ version["download-url"] }}">{{ (version["filesize"] | int) // 1024 }} kB</a></td>
{% else %}
<td>Not available</td>
{% endif %}
</tr>
<tr>
<td><a href="/package/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["upload-date"] }}">{{ version["version"] }}</a></td>
<td>{{ version["upload-date"] }}</td>
<td>{{ version["md5sum-partial"] }}</td>
<td>{{ version["license"] }}</td>
{% if version["download-url"] %}
<td><a href="{{ version["download-url"] }}">{{ (version["filesize"] | int) // 1024 }} kB</a></td>
{% else %}
<td>Not available</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>

{% endblock %}
@@ -4,39 +4,43 @@ <h1>{% block title %}{{ content_type }}{% endblock %}</h1>
{% endblock %}
{% block content %}

<table>
<tr>
<th>Id</th>
<th>Name</th>
<th>Project site</th>
<th>Latest version</th>
<th>Upload date</th>
<th>License</th>
<th>Download</th>
</tr>
{% for package in packages %}
<tr>
<td>{{ package["unique-id"] }}</td>
<td><a href="/package/{{ package["content-type"] }}/{{ package["unique-id"] }}">{{ package["name"] }}</a></td>
<td>
{% if package["url"] %}
<a href="{{ package["url"] }}" target="_blank">{{ package["url"] }}</a>
{% endif %}
</td>
{% if package["latest"] %}
<td><a href="/package/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ package["latest"]["upload-date"] }}">{{ package["latest"]["version"] }}</a></td>
<td>{{ package["latest"]["upload-date"] }}</td>
<td>{{ package["latest"]["license"] }}</td>
{% if package["latest"]["download-url"] %}
<td><a href="{{ package["latest"]["download-url"] }}">{{ (package["latest"]["filesize"] | int) // 1024 }} kB</a></td>
{% else %}
<td>Not available</td>
{% endif %}
{% else %}
<td></td><td></td><td></td><td>Not available</td>
{% endif %}
</tr>
{% endfor %}
<table id="bananas-table">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Project site</th>
<th>Latest version</th>
<th>Upload date</th>
<th>License</th>
<th>Download</th>
</tr>
</thead>
<tbody>
{% for package in packages %}
<tr class="{{ loop.cycle('odd', 'even') }}">
<td>{{ package["unique-id"] }}</td>
<td><a href="/package/{{ package["content-type"] }}/{{ package["unique-id"] }}">{{ package["name"] }}</a></td>
<td>
{% if package["url"] %}
<a href="{{ package["url"] }}" target="_blank">{{ package["url"] }}</a>
{% endif %}
</td>
{% if package["latest"] %}
<td><a href="/package/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ package["latest"]["upload-date"] }}">{{ package["latest"]["version"] }}</a></td>
<td>{{ package["latest"]["upload-date"] }}</td>
<td>{{ package["latest"]["license"] }}</td>
{% if package["latest"]["download-url"] %}
<td><a href="{{ package["latest"]["download-url"] }}">{{ (package["latest"]["filesize"] | int) // 1024 }} kB</a></td>
{% else %}
<td>Not available</td>
{% endif %}
{% else %}
<td></td><td></td><td></td><td>Not available</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>

{% endblock %}
@@ -0,0 +1,37 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Terms of Service{% endblock %}</h1>
<p>Version 1.0, 2009-01-17</p>
{% endblock %}
{% block content %}
<ol type="1">
<li>You will only upload content of which you are (one of) the original author(s).</li>
<li>You grant the OpenTTD team the rights to distribute the last version of your content from a central server. We will assign a globally unique identifier to each upload and everyone can download the content when they know that identifier.</li>
<li>You grant the OpenTTD team to distribute your latest content via our website.</li>
<li>You grant the OpenTTD team to retain older versions of your content for the purpose of loading save games with said older version.</li>
<li>You grant the OpenTTD team the rights to distribute your content from a central server when specifically asked for it by its unique identifier and MD5 checksum. The origin of the unique identifier and MD5 checksum differs per type of content:
<ol type="a">
<li>Base graphics: unique identifier is constructed from the four character short name defined in the .obg file. The MD5 checksum is the exclusive or of the MD5 checksum of the 6 GRFs that are part of the graphics pack.</li>
<li>NewGRFs: unique identifier is constructed from the GRF ID. The MD5 checksum is the MD5 checksum of the .grf file.</li>
<li>AIs and AI Libraries: unique identifier is constructed from the four character short name defined in the info.nut. The MD5 checksum is the exclusive or of the MD5 checksums of all scripting files that are part of the AI or AI Library.</li>
<li>Heightmaps and scenarios: unique identifier is automatically generated when you upload the content. The MD5 checksum is the MD5 checksum of the scenario/heightmap.</li>
</ol>
</li>
<li>You grant the OpenTTD team the rights to repackage your content before publishing it. The repackaging:
<ol type="a">
<li>keeps files called "readme", "license" and "copying" with .txt or .pdf as extension or without an extension.</li>
<li>requires a "license" or "copying" file in the package file or requires that you selected a non-custom license when uploading the package. In the latter case that license will be added to the package.</li>
<li>renames files called "copying" to "license" retaining the extension.</li>
<li>requires exactly one .grf file in NewGRF packages.</li>
<li>requires exactly one .obg file and exactly six .grf files in Base graphics packages as named in the .obg file and with the same MD5 checksums as defined in the .obg file.</li>
<li>requires .nut files in AI and AI Library packages.</li>
<li>changes newlines from .txt files to "DOS" (\r\n) newlines.</li>
<li>changes newlines from .nut and .obg files to "unix" (\n) newlines.</li>
<li>requires a "main.nut" and "info.nut" in AI packages.</li>
<li>requires a "main.nut" and "library.nut" in AI Library packages.</li>
<li>requires exactly one .scn or one .sv0 or one .ss0 file in Scenario packages.</li>
<li>requires exactly one .png or one .bmp file in Heightmap packages.</li>
</ol>
</li>
</ol>
{% endblock %}
@@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Terms of Service{% endblock %}</h1>
<p>Version 1.1, 2009-08-10</p>
{% endblock %}
{% block content %}
<ol type="1">
<li>You will only upload content of which you are (one of) the original author(s).</li>
<li>You grant the OpenTTD team the rights to distribute the last version of your content from a central server. We will assign a globally unique identifier to each upload and everyone can download the content when they know that identifier.</li>
<li>You grant the OpenTTD team to distribute your latest content via our website.</li>
<li>You grant the OpenTTD team to retain older versions of your content for the purpose of loading save games with said older version.</li>
<li>You grant the OpenTTD team the rights to distribute your content from a central server when specifically asked for it by its unique identifier and MD5 checksum. The origin of the unique identifier and MD5 checksum differs per type of content:
<ol type="a">
<li>Base graphics: unique identifier is constructed from the four character short name defined in the .obg file. The MD5 checksum is the exclusive or of the MD5 checksum of the 6 GRFs that are part of the graphics pack.</li>
<li>NewGRFs: unique identifier is constructed from the GRF ID. The MD5 checksum is the MD5 checksum of the .grf file.</li>
<li>AIs and AI Libraries: unique identifier is constructed from the four character short name defined in the info.nut. The MD5 checksum is the exclusive or of the MD5 checksums of all scripting files that are part of the AI or AI Library.</li>
<li>Heightmaps and scenarios: unique identifier is automatically generated when you upload the content. The MD5 checksum is the MD5 checksum of the scenario/heightmap.</li>
<li>Base sound: unique identifier is constructed from the four character short name defined in the .obs file. The MD5 checksum is the MD5 checksum of the cat file that is part of the sound pack.</li>
</ol>
</li>
<li>You grant the OpenTTD team the rights to repackage your content before publishing it. The repackaging:
<ol type="a">
<li>keeps files called "readme", "license" and "copying" with .txt or .pdf as extension or without an extension.</li>
<li>requires a "license" or "copying" file in the package file or requires that you selected a non-custom license when uploading the package. In the latter case that license will be added to the package.</li>
<li>renames files called "copying" to "license" retaining the extension.</li>
<li>requires exactly one .grf file in NewGRF packages.</li>
<li>requires exactly one .obg file and exactly six .grf files in Base graphics packages as named in the .obg file and with the same MD5 checksums as defined in the .obg file.</li>
<li>requires .nut files in AI and AI Library packages.</li>
<li>changes newlines from .txt files to "DOS" (\r\n) newlines.</li>
<li>changes newlines from .nut, .obg and .obs files to "unix" (\n) newlines.</li>
<li>requires a "main.nut" and "info.nut" in AI packages.</li>
<li>requires a "main.nut" and "library.nut" in AI Library packages.</li>
<li>requires exactly one .scn or one .sv0 or one .ss0 file in Scenario packages.</li>
<li>requires exactly one .png or one .bmp file in Heightmap packages.</li>
<li>requires exactly one .obs file and exactly one .cat file in Base sound packages as named in the .obs file and with the same MD5 checksum as defined in the .obs file.</li>
</ol>
</li>
</ol>
{% endblock %}
@@ -1,40 +1,40 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Terms of Service{% endblock %}</h1>
<h3>Version 1.2, 2010-01-26</h3>
<p>Version 1.2, 2010-01-26</p>
{% endblock %}
{% block content %}
<ol>
<ol type="1">
<li>You will only upload content of which you are (one of) the original author(s).</li>
<li>You grant the OpenTTD team the rights to distribute the last version of your content from a central server. We will assign a globally unique identifier to each upload and everyone can download the content when they know that identifier.</li>
<li>You grant the OpenTTD team to distribute your latest content via our website.</li>
<li>You grant the OpenTTD team to retain older versions of your content for the purpose of loading save games with said older version.</li>
<li>You grant the OpenTTD team the rights to distribute your content from a central server when specifically asked for it by its unique identifier and MD5 checksum. The origin of the unique identifier and MD5 checksum differs per type of content:
<ol>
<li style="list-style-type: lower-alpha">Base graphics: unique identifier is constructed from the four character short name defined in the .obg file. The MD5 checksum is the exclusive or of the MD5 checksum of the 6 GRFs that are part of the graphics pack.</li>
<li style="list-style-type: lower-alpha">NewGRFs: unique identifier is constructed from the GRF ID. The MD5 checksum is the MD5 checksum of the .grf file.</li>
<li style="list-style-type: lower-alpha">AIs and AI Libraries: unique identifier is constructed from the four character short name defined in the info.nut. The MD5 checksum is the exclusive or of the MD5 checksums of all scripting files that are part of the AI or AI Library.</li>
<li style="list-style-type: lower-alpha">Heightmaps and scenarios: unique identifier is automatically generated when you upload the content. The MD5 checksum is the MD5 checksum of the scenario/heightmap.</li>
<li style="list-style-type: lower-alpha">Base sound: unique identifier is constructed from the four character short name defined in the .obs file. The MD5 checksum is the MD5 checksum of the cat file that is part of the sound pack.</li>
<li style="list-style-type: lower-alpha">Base music: unique identifier is constructed from the four character short name defined in the .obm file. The MD5 checksum is the exclusive or of MD5 checksum of the music files that are part of the music pack. If they are mentioned multiple times in the .obm file they are exclusive or-ed multiple times. </li>
<ol type="a">
<li>Base graphics: unique identifier is constructed from the four character short name defined in the .obg file. The MD5 checksum is the exclusive or of the MD5 checksum of the 6 GRFs that are part of the graphics pack.</li>
<li>NewGRFs: unique identifier is constructed from the GRF ID. The MD5 checksum is the MD5 checksum of the .grf file.</li>
<li>AIs and AI Libraries: unique identifier is constructed from the four character short name defined in the info.nut. The MD5 checksum is the exclusive or of the MD5 checksums of all scripting files that are part of the AI or AI Library.</li>
<li>Heightmaps and scenarios: unique identifier is automatically generated when you upload the content. The MD5 checksum is the MD5 checksum of the scenario/heightmap.</li>
<li>Base sound: unique identifier is constructed from the four character short name defined in the .obs file. The MD5 checksum is the MD5 checksum of the cat file that is part of the sound pack.</li>
<li>Base music: unique identifier is constructed from the four character short name defined in the .obm file. The MD5 checksum is the exclusive or of MD5 checksum of the music files that are part of the music pack. If they are mentioned multiple times in the .obm file they are exclusive or-ed multiple times. </li>
</ol>
</li>
<li>You grant the OpenTTD team the rights to repackage your content before publishing it. The repackaging:
<ol>
<li style="list-style-type: lower-alpha">keeps files called "readme", "license" and "copying" with .txt or .pdf as extension or without an extension.</li>
<li style="list-style-type: lower-alpha">requires a "license" or "copying" file in the package file or requires that you selected a non-custom license when uploading the package. In the latter case that license will be added to the package.</li>
<li style="list-style-type: lower-alpha">renames files called "copying" to "license" retaining the extension.</li>
<li style="list-style-type: lower-alpha">requires exactly one .grf file in NewGRF packages.</li>
<li style="list-style-type: lower-alpha">requires exactly one .obg file and exactly six .grf files in Base graphics packages as named in the .obg file and with the same MD5 checksums as defined in the .obg file.</li>
<li style="list-style-type: lower-alpha">requires .nut files in AI and AI Library packages.</li>
<li style="list-style-type: lower-alpha">changes newlines from .txt files to "DOS" (\r\n) newlines.</li>
<li style="list-style-type: lower-alpha">changes newlines from .nut, .obg and .obs files to "unix" (\n) newlines.</li>
<li style="list-style-type: lower-alpha">requires a "main.nut" and "info.nut" in AI packages.</li>
<li style="list-style-type: lower-alpha">requires a "main.nut" and "library.nut" in AI Library packages.</li>
<li style="list-style-type: lower-alpha">requires exactly one .scn or one .sv0 or one .ss0 file in Scenario packages.</li>
<li style="list-style-type: lower-alpha">requires exactly one .png or one .bmp file in Heightmap packages.</li>
<li style="list-style-type: lower-alpha">requires exactly one .obs file and exactly one .cat file in Base sound packages as named in the .obs file and with the same MD5 checksum as defined in the .obs file.</li>
<li style="list-style-type: lower-alpha">requires exactly one .obm file and a number of music files in Base music packages as named in the .obm file and with the same MD5 checksum as defined in the .obm file.</li>
<ol type="a">
<li>keeps files called "readme", "license" and "copying" with .txt or .pdf as extension or without an extension.</li>
<li>requires a "license" or "copying" file in the package file or requires that you selected a non-custom license when uploading the package. In the latter case that license will be added to the package.</li>
<li>renames files called "copying" to "license" retaining the extension.</li>
<li>requires exactly one .grf file in NewGRF packages.</li>
<li>requires exactly one .obg file and exactly six .grf files in Base graphics packages as named in the .obg file and with the same MD5 checksums as defined in the .obg file.</li>
<li>requires .nut files in AI and AI Library packages.</li>
<li>changes newlines from .txt files to "DOS" (\r\n) newlines.</li>
<li>changes newlines from .nut, .obg and .obs files to "unix" (\n) newlines.</li>
<li>requires a "main.nut" and "info.nut" in AI packages.</li>
<li>requires a "main.nut" and "library.nut" in AI Library packages.</li>
<li>requires exactly one .scn or one .sv0 or one .ss0 file in Scenario packages.</li>
<li>requires exactly one .png or one .bmp file in Heightmap packages.</li>
<li>requires exactly one .obs file and exactly one .cat file in Base sound packages as named in the .obs file and with the same MD5 checksum as defined in the .obs file.</li>
<li>requires exactly one .obm file and a number of music files in Base music packages as named in the .obm file and with the same MD5 checksum as defined in the .obm file.</li>
</ol>
</li>
</ol>
@@ -0,0 +1,41 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Terms of Service{% endblock %}</h1>
<p>Version 1.3, 2020-04-23</p>
{% endblock %}
{% block content %}
<ol type="1">
<li>You will only upload content of which you are (one of) the original author(s).</li>
<li>You grant the OpenTTD team the rights to distribute the last version of your content from a central server. We will assign a globally unique identifier to each upload and everyone can download the content when they know that identifier.</li>
<li>You grant the OpenTTD team to distribute your latest content via our website.</li>
<li>You grant the OpenTTD team to retain older versions of your content for the purpose of loading save games with said older version.</li>
<li>You grant the OpenTTD team the rights to distribute your content from a central server when specifically asked for it by its unique identifier and MD5 checksum. The origin of the unique identifier and MD5 checksum differs per type of content:
<ol type="a">
<li>Base graphics: unique identifier is constructed from the four character short name defined in the .obg file. The MD5 checksum is the exclusive or of the MD5 checksum of the 6 GRFs that are part of the graphics pack.</li>
<li>NewGRFs: unique identifier is constructed from the GRF ID. The MD5 checksum is the MD5 checksum of the .grf file.</li>
<li>AIs and AI Libraries: unique identifier is constructed from the four character short name defined in the info.nut. The MD5 checksum is the exclusive or of the MD5 checksums of all scripting files that are part of the AI or AI Library.</li>
<li>Heightmaps and scenarios: unique identifier is automatically generated when you upload the content. The MD5 checksum is the MD5 checksum of the scenario/heightmap.</li>
<li>Base sound: unique identifier is constructed from the four character short name defined in the .obs file. The MD5 checksum is the MD5 checksum of the cat file that is part of the sound pack.</li>
<li>Base music: unique identifier is constructed from the four character short name defined in the .obm file. The MD5 checksum is the exclusive or of MD5 checksum of the music files that are part of the music pack. If they are mentioned multiple times in the .obm file they are exclusive or-ed multiple times. </li>
</ol>
</li>
<li>You grant the OpenTTD team the rights to repackage your content before publishing it. The repackaging:
<ol type="a">
<li>keeps files called "readme", "license" and "changelog" with .txt as extension.</li>
<li>requires a "license.txt" file in the package file or requires that you selected a non-custom license when uploading the package. In the latter case that license will be added to the package.</li>
<li>(deleted)</li>
<li>requires exactly one .grf file in NewGRF packages.</li>
<li>requires exactly one .obg file and exactly six .grf files in Base graphics packages as named in the .obg file and with the same MD5 checksums as defined in the .obg file.</li>
<li>requires .nut files in AI and AI Library packages.</li>
<li>(deleted)</li>
<li>(deleted)</li>
<li>requires a "main.nut" and "info.nut" in AI packages.</li>
<li>requires a "main.nut" and "library.nut" in AI Library packages.</li>
<li>requires exactly one .scn file in Scenario packages.</li>
<li>requires exactly one .png file in Heightmap packages.</li>
<li>requires exactly one .obs file and exactly one .cat file in Base sound packages as named in the .obs file and with the same MD5 checksum as defined in the .obs file.</li>
<li>requires exactly one .obm file and a number of music files in Base music packages as named in the .obm file and with the same MD5 checksum as defined in the .obm file.</li>
</ol>
</li>
</ol>
{% endblock %}
@@ -2,64 +2,74 @@
{% block title %}{{ version["name"] or package["name"] }} {{ version["version"] }}{% endblock %}
{% block header %}
<h1><a href="/package/{{ package["content-type"] }}/{{ package["unique-id"] }}">{{ version["name"] or package["name"] }}</a> {{ version["version"] }}</h1>
<h3>{{ version["content-type"] }}/{{ version["unique-id"] }}/{{ version["upload-date"] }}</h3>
{% endblock %}
{% block content %}


{% if package.get("archived") %}
<p><b> This content is archived and is no longer available for new games. </b></p>
{% endif %}
{% if package.get("replaced-by") %}
<p><b> This content is deprecated. Please use <a href="/package/{{ package["content-type"] }}/{{ package["replaced-by"]["unique-id"] }}">{{ package["replaced-by"]["name"] }} ({{ package["replaced-by"]["unique-id"] }})</a> instead. </b></p>
<p><b> This content is deprecated. Please use <a href="/package/{{ package["replaced-by"]["content-type"] }}/{{ package["replaced-by"]["unique-id"] }}">{{ package["replaced-by"]["name"] }} ({{ package["replaced-by"]["unique-id"] }})</a> instead. </b></p>
{% elif latest %}
<p><b> There is a newer version of this content available. Please use <a href="/package/{{ latest["content-type"] }}/{{ latest["unique-id"] }}/{{ latest["upload-date"] }}">version {{ latest["version"] }}</a> for new games. </b></p>
{% endif %}
<table>
<tr><td>Content Id</td><td>{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["md5sum-partial"] }}</td></tr>
<tr><td>Name</td><td>{{ version["name"] or package["name"] }}</td></tr>
<tr><td>Project site</td><td>
{% set url = version["url"] or package["url"] %}
{% if url %}
<a href="{{ url }}" target="_blank">{{ url }}</a>
{% endif %}
</td></tr>
<tr><td>Version</td><td>{{ version["version"] }}</td></tr>
<tr><td>Upload date</td><td>{{ version["upload-date"] }}</td></tr>
<tr><td>MD5 (partial)</td><td>{{ version["md5sum-partial"] }}</td></tr>
<tr><td>License</td><td>{{ version["license"] }}</td></tr>
<tr><td>Download</td>
{% if version["download-url"] %}
<td><a href="{{ version["download-url"] }}">{{ (version["filesize"] | int) // 1024 }} kB</a></td>
{% else %}
<td>Not available</td>
<p><b> There is a newer version of this content available. Please use <a href="/package/{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ latest["upload-date"] }}">version {{ latest["version"] }}</a> for new games. </b></p>
{% endif %}
</tr>
<tr><td>Compatibility</td><td><ul>
{% for compat in version["compatibility"] %}
<li>{{ compat["name"] }}: {{ compat["conditions"] | join(", ") }}</li>
{% endfor %}
</ul></td></tr>
<tr><td>Dependencies</td><td><ul>
{% for dep in version["dependencies"] %}
<li><a href="/package/{{ dep["content-type"] }}/{{ dep["unique-id"] }}/{{ dep["upload-date"] }}">{{ dep["name"] }} {{ dep["version"] }}</a></li>
{% endfor %}
</ul></td></tr>
<tr><td>Tags</td><td><ul>
{% set tags = version["tags"] or package["tags"] %}
{% for t in tags %}
<li>{{ t }}</li>
{% endfor %}
</ul></td></tr>
<tr><td>Authors</td><td><ul>
{% for a in package["authors"] %}
<li>{{ a["display-name"] }}</li>
{% endfor %}
</ul></td></tr>
<tr><td>Description</td><td>
{% for l in (version["description"] or package["description"]).splitlines() %}
<table class="package-version-info-table">
<tbody>
<tr><th>Content Id</th><td>{{ package["content-type"] }}/{{ package["unique-id"] }}/{{ version["md5sum-partial"] }}</td></tr>
<tr><th>Name</th><td>{{ version["name"] or package["name"] }}</td></tr>
<tr><th>Project site</th><td>
{% set url = version["url"] or package["url"] %}
{% if url %}
<a href="{{ url }}" target="_blank">{{ url }}</a>
{% endif %}
</td></tr>
<tr><th>Version</th><td>{{ version["version"] }}</td></tr>
<tr><th>Upload date</th><td>{{ version["upload-date"] }}</td></tr>
<tr><th>MD5 (partial)</th><td>{{ version["md5sum-partial"] }}</td></tr>
<tr><th>License</th><td>{{ version["license"] }}</td></tr>
<tr><th>Download</th>
{% if version["download-url"] %}
<td><a href="{{ version["download-url"] }}">{{ (version["filesize"] | int) // 1024 }} kB</a></td>
{% else %}
<td>Not available</td>
{% endif %}
</tr>
<tr><th>Compatibility</th><td>
<ul>
{% for compat in version["compatibility"] %}
<li>{{ compat["name"] }}: {{ compat["conditions"] | join(", ") }}</li>
{% endfor %}
</ul>
</td></tr>
<tr><th>Dependencies</th><td>
<ul>
{% for dep in version["dependencies"] %}
<li><a href="/package/{{ dep["content-type"] }}/{{ dep["unique-id"] }}/{{ dep["upload-date"] }}">{{ dep["name"] }} {{ dep["version"] }}</a></li>
{% endfor %}
</ul>
</td></tr>
<tr><th>Tags</th><td>
<ul>
{% set tags = version["tags"] or package["tags"] %}
{% for t in tags %}
<li>{{ t }}</li>
{% endfor %}
</ul>
</td></tr>
<tr><th>Authors</th><td>
<ul>
{% for a in package["authors"] %}
<li>{{ a["display-name"] }}</li>
{% endfor %}
</ul>
</td></tr>
<tr><th>Description</th><td>
{% for l in (version["description"] or package["description"] or "").splitlines() %}
{{ l }}<br/>
{% endfor %}
</td></tr>
</td></tr>
</tbody>
</table>

{% endblock %}