Bläddra i källkod

Merge pull request #1 from COPIM/joel

merging joel branch into main branch
joel
Simon Bowie 1 år sedan
förälder
incheckning
91b53af5f5
Inget konto är kopplat till bidragsgivarens mejladress
53 ändrade filer med 4514 tillägg och 945 borttagningar
  1. +2
    -2
      .env.template
  2. +2
    -1
      .gitignore
  3. +17
    -7
      README.md
  4. +4
    -4
      database_functions.sh
  5. +2
    -2
      docker-compose.yml
  6. +2
    -2
      nginx-conf/compendium.conf
  7. +4
    -0
      web/app/__init__.py
  8. +18
    -15
      web/app/api.py
  9. +36
    -19
      web/app/book.py
  10. +21
    -5
      web/app/main.py
  11. +37
    -6
      web/app/practice.py
  12. +36
    -0
      web/app/relationships.py
  13. +35
    -36
      web/app/resources.py
  14. +39
    -0
      web/app/search.py
  15. Binär
      web/app/static/images/favicon-no_bg.png
  16. Binär
      web/app/static/images/favicon.png
  17. +5
    -0
      web/app/static/js/alpine.min.js
  18. +1
    -0
      web/app/static/js/htmx.min.js
  19. +10
    -37
      web/app/static/js/main.js
  20. +1304
    -0
      web/app/static/package-lock.json
  21. +22
    -0
      web/app/static/package.json
  22. +6
    -0
      web/app/static/postcss.config.js
  23. +395
    -0
      web/app/static/src/main.css
  24. Binär
      web/app/static/styles/ApfelGrotezk-Brukt.woff
  25. Binär
      web/app/static/styles/ApfelGrotezk-Brukt.woff2
  26. Binär
      web/app/static/styles/ApfelGrotezk-Fett.woff
  27. Binär
      web/app/static/styles/ApfelGrotezk-Fett.woff2
  28. Binär
      web/app/static/styles/ApfelGrotezk-Regular.woff
  29. Binär
      web/app/static/styles/ApfelGrotezk-Regular.woff2
  30. Binär
      web/app/static/styles/CirrusCumulus.woff
  31. Binär
      web/app/static/styles/Dauphine-Italic.ttf
  32. Binär
      web/app/static/styles/Dauphine-Regular.ttf
  33. +1552
    -0
      web/app/static/styles/main.css
  34. Binär
      web/app/static/styles/roboto-italic.ttf
  35. Binär
      web/app/static/styles/roboto-regular.ttf
  36. +39
    -0
      web/app/static/tailwind.config.js
  37. +7
    -1
      web/app/templates/about.html
  38. +4
    -4
      web/app/templates/api.html
  39. +437
    -97
      web/app/templates/base.html
  40. +112
    -190
      web/app/templates/book.html
  41. +20
    -25
      web/app/templates/index.html
  42. +122
    -204
      web/app/templates/resource.html
  43. +123
    -203
      web/app/templates/resources.html
  44. +12
    -0
      web/app/templates/search.html
  45. +4
    -34
      web/app/templates/test.html
  46. +38
    -22
      web/app/tool.py
  47. +11
    -13
      web/content/about.md
  48. +1
    -0
      web/content/books.md
  49. +29
    -0
      web/content/colophon.md
  50. +1
    -15
      web/content/home.md
  51. +1
    -0
      web/content/practices.md
  52. +1
    -0
      web/content/tools.md
  53. +2
    -1
      web/requirements.txt

+ 2
- 2
.env.template Visa fil

@@ -5,11 +5,11 @@ FLASK_APP=app
FLASK_RUN_HOST=0.0.0.0
FLASK_DEBUG=
SECRET_KEY=
DATABASE_URL=mysql+pymysql://MYSQL_USER:MYSQL_PASSWORD@mariadb/toolkit
DATABASE_URL=mysql+pymysql://MYSQL_USER:MYSQL_PASSWORD@mariadb/compendium
SSL_SCHEME=

# MariaDB variables
MYSQL_ROOT_PASSWORD=
MYSQL_DATABASE=toolkit
MYSQL_DATABASE=compendium
MYSQL_USER=flask
MYSQL_PASSWORD=

+ 2
- 1
.gitignore Visa fil

@@ -5,4 +5,5 @@ web/app/__pycache__/
database_functions_live.sh
old_database_schema
db_exports
db_imports
db_imports
node_modules

+ 17
- 7
README.md Visa fil

@@ -1,16 +1,16 @@
One of the key deliverables for Work Package 6 of the COPIM project is "an online resource detailing opportunities for experimental book publishing". We've decided to put this together in the form of an online toolkit with details of software, practices, examples, and sensitivities that can be used for experimental publishing.
One of the key deliverables for Work Package 6 of the COPIM project is "an online resource detailing opportunities for experimental book publishing". We've decided to put this together in the form of an online compendium with details of software, practices, examples, and sensitivities that can be used for experimental publishing.

The ExPub Compendium will build on this review of tools to present a resource for a researcher, artist, or publisher looking to try experimental publishing. From our preliminary design discussions, we have plans to include not only software tools that can be used to do experimental publishing but examples of experimental publications, practices of experimental publishing, publishers who have done some form of experimental publishing, and sensitivities involved in experimental publishing.
The Experimental Publishing Compendium will build on this review of tools to present a resource for any researcher, artist, or publisher looking to try experimental publishing. This compendium includes not only software tools that can be used to do experimental publishing but practices of experimental publishing and examples of experimental publications.

## application structure

The ExPub Compendium is a Python application using the [Flask](https://flask.palletsprojects.com/en/2.2.x/) framework to render as a website and to provide functions around user authentication and login as well as retrieving data from the underlying database. The Flask framework uses a few HTML template pages to render different pages efficiently based on routes defined in Python files. These template pages use a fairly basic [Bootstrap 5](https://getbootstrap.com/) grid design which allows for responsive design using in-built CSS and HTML elements.
The Experimental Publishing Compendium is a Python application using the [Flask](https://flask.palletsprojects.com/en/2.2.x/) framework to render as a website and to provide functions around user authentication and login as well as retrieving data from the underlying database. The Flask framework uses a few HTML template pages to render different pages efficiently based on routes defined in Python files.

The database is a MariaDB SQL database with a basic structure of Resources, Relationships, and Users. Resources are divided into tools, practices, and books and the table contains fields for these various types of resources. Relationships defines the links between resources using the resource ID in the Resource table: these are rendered on the site as connections between, say, a tool and a practice. Users contains the site's users with basic details like email address and hashed password. This simple structure provides flexibility for displaying resources with filters and to illustrate the connections between various resources.

### RESTful API

The application has a simple RESTful API deployed using Marshmallow to define schemas based on the SQLAlchemy database models. It allows a simple JSON export of users (login required), tools, practices, and books.
The application has a simple RESTful API deployed using [Marshmallow](https://marshmallow.readthedocs.io/en/stable/index.html) to define schemas based on the SQLAlchemy database models. It allows a simple JSON export of users (login required), tools, practices, and books.

The API is relatively open and is available at ./api

@@ -42,9 +42,9 @@ To intially create the database and a database user:

Enter your root password as defined in .env.dev.

`CREATE DATABASE toolkit;`
`CREATE DATABASE compendium;`
`CREATE USER 'flask'@'%' IDENTIFIED BY '[PASSWORD]';`
`GRANT CREATE, INSERT, UPDATE, SELECT, DELETE ON toolkit.* TO 'flask'@'%';`
`GRANT CREATE, INSERT, UPDATE, SELECT, DELETE ON compendium.* TO 'flask'@'%';`

Restart the containers to allow Flask to build the database tables:

@@ -53,7 +53,7 @@ Restart the containers to allow Flask to build the database tables:

You can then use database_functions.sh and an SQL file to populate the database e.g.

`./database_functions -i ./db_imports/toolkit_db_20230112.sql`
`./database_functions -i ./db_imports/compendium_db_20230112.sql`

## database functions

@@ -70,3 +70,13 @@ To build the database tables run:
`docker exec -it python python`
`from app import db, create_app, models`
`db.create_all(app=create_app())`

## credits

Content in the Experimental Publishing Compendium is © 2022–2023 [COPIM](https://copim.ac.uk) and licensed under a [Creative Commons Attribution 4.0 International License (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/).

Book cover data is from the [Open Library Covers API](https://openlibrary.org/dev/docs/api/covers).

The Experimental Publishing Compendium's source code is available at [https://github.com/COPIM/expub_compendium](https://github.com/COPIM/expub_compendium) and is licensed under the [MIT License](https://github.com/COPIM/expub_compendium/blob/main/LICENSE).

The Experimental Publishing Compendium was developed by [Simon Bowie](https://simonxix.com) and designed by [Joel Galvez](https://www.joelgalvez.com/) and Martina Vanini.

+ 4
- 4
database_functions.sh Visa fil

@@ -3,7 +3,7 @@
# @creation_date: 2022-11-02
# @license: The MIT License <https://opensource.org/licenses/MIT>
# @author: Simon Bowie <ad7588@coventry.ac.uk>
# @purpose: Runs database functions for the ExPub Compendium
# @purpose: Runs database functions for the Experimental Publishing Compendium
# @acknowledgements:
# https://www.redhat.com/sysadmin/arguments-options-bash-scripts

@@ -24,7 +24,7 @@ License()
Help()
{
# Display Help
echo "This script performs database functions for the ExPub Compendium"
echo "This script performs database functions for the Experimental Publishing Compendium"
echo
echo "Syntax: database_functions.sh [-l|h|e|i|c|v|d]"
echo "options:"
@@ -72,11 +72,11 @@ Drop_table()

# set variables
CONTAINER=mariadb
DATABASE=toolkit
DATABASE=compendium
USERNAME=xxxxxxxx
PASSWORD=xxxxxxxx
EXPORT_DIRECTORY="./db_exports"
EXPORT_SQL_FILENAME=toolkit_db_
EXPORT_SQL_FILENAME=compendium_db_
EXPORT_TXT_FILENAME=$2`date +"%Y%m%d"`

# error message for no flags

+ 2
- 2
docker-compose.yml Visa fil

@@ -1,4 +1,4 @@
version: "3.9"
version: "3.0"
services:
web:
build: ./web
@@ -23,4 +23,4 @@ services:
command: '--default-authentication-plugin=mysql_native_password'

volumes:
dbdata:
dbdata:

nginx-conf/toolkit.conf → nginx-conf/compendium.conf Visa fil

@@ -1,4 +1,4 @@
upstream toolkit {
upstream compendium {
server web:5000;
}

@@ -7,7 +7,7 @@ server {
listen 80;

location / {
proxy_pass http://toolkit;
proxy_pass http://compendium;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;

+ 4
- 0
web/app/__init__.py Visa fil

@@ -85,4 +85,8 @@ def create_app():
from .api import api as api_blueprint
app.register_blueprint(api_blueprint)

# blueprint for search parts of app
from .search import search as search_blueprint
app.register_blueprint(search_blueprint)

return app

+ 18
- 15
web/app/api.py Visa fil

@@ -11,17 +11,20 @@ from flask import Blueprint, render_template, request, flash, redirect, url_for,
from flask_login import login_required, current_user
from .models import Resource, User
from .schemas import UserSchema, ToolSchema, PracticeSchema, BookSchema
from .relationships import *
import pandas as pd
import json

api = Blueprint('api', __name__)

# function to get different types from the Resource database table
def get_resource_json(type):
resources = Resource.query.filter_by(type=type)
if (type=='tool'):
def get_resource_json(resource_type):
resources = Resource.query.filter_by(type=resource_type)
if (resource_type=='tool'):
resources_schema = ToolSchema(many=True)
elif (type=='practice'):
elif (resource_type=='practice'):
resources_schema = PracticeSchema(many=True)
elif (type=='book'):
elif (resource_type=='book'):
resources_schema = BookSchema(many=True)
result = resources_schema.dump(resources)
# return all rows as a JSON array of objects
@@ -37,7 +40,7 @@ def api_page():
# route for exporting all users in database
@api.route('/api/users')
@login_required
def get_users():
def get_users_json():
users = User.query.all()
users_schema = UserSchema(many=True)
result = users_schema.dump(users)
@@ -47,21 +50,21 @@ def get_users():

# route for exporting all tools in database
@api.route('/api/tools')
def get_tools():
type = 'tool'
output = get_resource_json(type)
def get_tools_json():
resource_type = 'tool'
output = get_resource_json(resource_type)
return output

# route for exporting all practices in database
@api.route('/api/practices')
def get_practices():
type = 'practice'
output = get_resource_json(type)
def get_practices_json():
resource_type = 'practice'
output = get_resource_json(resource_type)
return output

# route for exporting all books in database
@api.route('/api/books')
def get_books():
type = 'book'
output = get_resource_json(type)
def get_books_json():
resource_type = 'book'
output = get_resource_json(resource_type)
return output

+ 36
- 19
web/app/book.py Visa fil

@@ -12,40 +12,57 @@ from .models import Resource
from .resources import *
from .relationships import *
from . import db
from sqlalchemy import text
import os
from sqlalchemy.sql import func
import markdown

book = Blueprint('book', __name__)

# route for displaying all books in database
@book.route('/books')
def get_books():
type = 'book'
books = Resource.query.filter_by(type=type)
# get introductory paragraph Markdown
with open('content/books.md', 'r') as f:
intro_text = f.read()
intro_text = markdown.markdown(intro_text)
view = request.args.get('view')
resource_type = 'book'
books_query = Resource.query.filter_by(type=resource_type).order_by(func.random())
for key in request.args.keys():
if key == 'practice':
query = 'SELECT Resource.* FROM Resource LEFT JOIN Relationship ON Resource.id=Relationship.first_resource_id WHERE Relationship.second_resource_id=' + request.args.get(key) + ' AND Resource.type="' + type + '";'
with db.engine.connect() as conn:
books = conn.execute(text(query))
else:
kwargs = {'type': type, key: request.args.get(key)}
books = Resource.query.filter_by(**kwargs)
# get filters
if key != 'view':
if (key == 'practice' and request.args.get(key) != ''):
books_1 = books_query.join(Relationship, Relationship.first_resource_id == Resource.id, isouter=True).filter(Relationship.second_resource_id==request.args.get(key))
books_2 = books_query.join(Relationship, Relationship.second_resource_id == Resource.id, isouter=True).filter(Relationship.first_resource_id==request.args.get(key))
books_query = books_1.union(books_2)
if (key != 'practice' and request.args.get(key) != ''):
kwargs = {key: request.args.get(key)}
books_query = books_query.filter_by(**kwargs)
# finalise the query
books = books_query.all()
# get number of books
count = len(books)
# reorder books by book name
books = sorted(books, key=lambda d: d.__dict__['name'].lower())
# render Markdown as HTML
for book in books:
book.description = markdown.markdown(book.description)
if view != 'list':
# append relationships to each book
append_relationships_multiple(books)
# get values for filters
# practices
practices_filter = Resource.query.filter_by(type='practice').with_entities(Resource.id, Resource.name)
practices_filter = Resource.query.filter_by(type='practice').with_entities(Resource.id, Resource.name).all()
# year
year_filter = get_filter_values('year', type)
year_filter = get_filter_values('year', resource_type)
# typology
typology_filter = get_filter_values('typology', type)
return render_template('resources.html', resources=books, type=type, practices_filter=practices_filter, year_filter=year_filter, typology_filter=typology_filter)
typology_filter = get_filter_values('typology', resource_type)
return render_template('resources.html', resources=books, type=resource_type, practices_filter=practices_filter, year_filter=year_filter, typology_filter=typology_filter, count=count, view=view, intro_text=intro_text)

# route for displaying a single book based on the ID in the database
@book.route('/books/<int:book_id>')
def show_book(book_id):
book = get_resource(book_id)
relationships = get_relationships(book_id)
book_data = get_book_data(book.isbn)
return render_template('book.html', resource=book, relationships=relationships, book=book_data)
book = get_full_resource(book_id)
return render_template('book.html', resource=book)

# route for editing a single book based on the ID in the database
@book.route('/books/<int:book_id>/edit', methods=('GET', 'POST'))

+ 21
- 5
web/app/main.py Visa fil

@@ -9,6 +9,8 @@
from flask import Blueprint, render_template
from flask_login import login_required, current_user
from .models import Resource
from .resources import *
from .relationships import *
from sqlalchemy.sql import func
import markdown

@@ -17,11 +19,22 @@ main = Blueprint('main', __name__)
# route for index page
@main.route('/')
def index():
tools = Resource.query.filter_by(type='tool').order_by(func.random()).limit(6).all()
view = request.args.get('view')
# curated list of resources to display on homepage
tool_ids = ['4','10', '34', '27']
practice_ids = ['53', '59', '65', '56']
book_ids = ['94', '72', '105', '67']
# concatenate lists of resources
resource_ids = tool_ids + practice_ids + book_ids
# get data for curated resources
curated = get_curated_resources(resource_ids)
# render Markdown as HTML
for resource in curated:
resource.description = markdown.markdown(resource.description)
with open('content/home.md', 'r') as f:
text = f.read()
text = markdown.markdown(text)
return render_template('index.html', text=text, tools=tools)
return render_template('index.html', text=text, resources=curated, view=view)

# route for profile page
@main.route('/profile')
@@ -38,6 +51,9 @@ def test():
@main.route('/about')
def about():
with open('content/about.md', 'r') as f:
text = f.read()
text = markdown.markdown(text)
return render_template('about.html', text=text)
about_text = f.read()
about_text = markdown.markdown(about_text)
with open('content/colophon.md', 'r') as f:
colophon_text = f.read()
colophon_text = markdown.markdown(colophon_text)
return render_template('about.html', about_text=about_text, colophon_text=colophon_text)

+ 37
- 6
web/app/practice.py Visa fil

@@ -13,22 +13,53 @@ from .resources import *
from .relationships import *
from . import db
import os
import markdown
from sqlalchemy.sql import func
from sqlalchemy import or_

practice = Blueprint('practice', __name__)

# route for displaying all practices in database
@practice.route('/practices')
def get_practices():
practices = Resource.query.filter_by(type='practice')
return render_template('resources.html', resources=practices, type='practice')
# get introductory paragraph Markdown
with open('content/practices.md', 'r') as f:
intro_text = f.read()
intro_text = markdown.markdown(intro_text)
view = request.args.get('view')
practices = Resource.query.filter_by(type='practice').order_by(func.random())
# temporarily removing incomplete practices from main list
practices = Resource.query.filter(
or_(
Resource.id==53,
Resource.id==56,
Resource.id==59,
Resource.id==62,
Resource.id==63,
Resource.id==65,
Resource.id==66
))
# finalise the query
practices = practices.all()
# get number of practices
count = len(practices)
# reorder practices by practice name
practices = sorted(practices, key=lambda d: d.__dict__['name'].lower())
if view != 'list':
# append relationships to each practice
append_relationships_multiple(practices)
return render_template('resources.html', resources=practices, type='practice', count=count, view=view, intro_text=intro_text)

# route for displaying a single practice based on the ID in the database
@practice.route('/practices/<int:practice_id>')
def show_practice(practice_id):
practice = get_resource(practice_id)
relationships = get_relationships(practice_id)
practice.references = replace_urls(practice.references)
return render_template('resource.html', resource=practice, relationships=relationships)
practice = get_full_resource(practice_id)
# render Markdown as HTML
practice.longDescription = markdown.markdown(practice.longDescription)
practice.experimental = markdown.markdown(practice.experimental)
practice.considerations = markdown.markdown(practice.considerations)
practice.references = markdown.markdown(practice.references)
return render_template('resource.html', resource=practice)

# route for editing a single practice based on the ID in the database
@practice.route('/practices/<int:practice_id>/edit', methods=('GET', 'POST'))

+ 36
- 0
web/app/relationships.py Visa fil

@@ -8,6 +8,7 @@
from .models import Resource
from .models import Relationship
from . import db
import markdown

# function to retrieve linked resources
def get_relationships(primary_id):
@@ -33,6 +34,41 @@ def get_relationships(primary_id):
links.extend(Resource.query.filter_by(id=primary_id).all())
return links

# function to append relationships to a resource object
def append_relationships(resource):
relationships = get_relationships(resource.id)
if relationships:
for relationship in relationships:
if relationship.type == 'tool':
if 'tools' not in resource.__dict__.keys():
resource.__dict__['tools'] = []
resource.__dict__['tools'].append(relationship)
else:
resource.__dict__['tools'].append(relationship)
elif relationship.type == 'practice':
if 'practices' not in resource.__dict__.keys():
resource.__dict__['practices'] = []
resource.__dict__['practices'].append(relationship)
else:
resource.__dict__['practices'].append(relationship)
elif relationship.type == 'book':
# render Markdown as HTML
relationship.description = markdown.markdown(relationship.description)
if 'books' not in resource.__dict__.keys():
resource.__dict__['books'] = []
resource.__dict__['books'].append(relationship)
else:
resource.__dict__['books'].append(relationship)
return resource
else:
return resource

# function to append relationships to a dictionary of resources
def append_relationships_multiple(resources):
for index, resource in enumerate(resources):
resources[index] = append_relationships(resource)
return resources

# function to add a relationship to a linked resource
def add_relationship(resource_id, linked_resource_id):
first_resource_id = resource_id

+ 35
- 36
web/app/resources.py Visa fil

@@ -11,9 +11,12 @@ from flask import Blueprint, render_template, request, flash, redirect, url_for
from .models import Resource
from werkzeug.exceptions import abort
from . import db
from .relationships import *
from isbntools.app import *
import requests
import re
from sqlalchemy.sql import func
import markdown

# function to retrieve data about a single resource from the database
def get_resource(resource_id):
@@ -22,6 +25,26 @@ def get_resource(resource_id):
abort(404)
return resource

# function to retrieve data about a resource and its relationships
def get_full_resource(resource_id):
resource = get_resource(resource_id)
resource = append_relationships(resource)
if resource.type == 'book':
# render Markdown as HTML
resource.description = markdown.markdown(resource.description)
# get additional book metadata
book_data = get_book_data(resource.isbn)
if book_data:
resource.__dict__.update(book_data)
return resource

# function to retrieve data about a curated list of resources
def get_curated_resources(resource_ids):
resources = Resource.query.filter(Resource.id.in_(resource_ids)).order_by(func.random()).all()
# append relationships to each resource
append_relationships_multiple(resources)
return resources

# function to delete a single resource
def delete_resource(resource_id):
deletion = Resource.query.get(resource_id)
@@ -51,43 +74,19 @@ def get_book_data(isbn):
book = meta(isbn)
description = {'desc': desc(isbn)}
book.update(description)
# get highest-resolution book cover possible
openl_url = 'https://covers.openlibrary.org/b/isbn/' + book['ISBN-13'] + '-L.jpg?default=false'
request = requests.get(openl_url)
if request.status_code != 200:
book.update(cover(isbn))
else:
book_cover = {'thumbnail': openl_url}
book.update(book_cover)
#book = get_book_cover(book)
return book
except:
pass

# function to replace embedded URL strings with href links
def replace_urls(input):
# Compile a regular expression to match URLs.
# This regular expression is not exhaustive and may not match all possible URLs.
# It is intended to be a starting point and can be refined and expanded as needed.
url_regex = re.compile(r'((?:https?:(?:/{1,3}|[a-z0-9%])|[a-z0-9.\-]+[.](?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)/)(?:[^\s()<>{}\[\]]+|\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\))+(?:\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\)|[^\s`!()\[\]{};:\'\".,<>?«»“”‘’])|(?:(?<!@)[a-z0-9]+(?:[.\-][a-z0-9]+)*[.](?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)\b/?(?!@)))')

# Find all URLs in the input string using the regular expression.
# This will return a list of Match objects, each of which represents a single URL in the string.
matches = url_regex.finditer(input)

# Iterate over the list of matches and replace each URL with an HTML link.
for match in matches:
# Get the full URL from the Match object.
url = match.group(0)

# Create the HTML link by wrapping the URL in an <a> tag.
# If the URL does not include a protocol (e.g. "http://" or "https://"),
# then add "http://" as the default protocol.
if not url.startswith('http'):
link = f'<a href="http://{url}">{url}</a>'
else:
link = f'<a href="{url}">{url}</a>'

# Replace the URL in the original string with the HTML link.
input = input.replace(url, link)

return input
# function to get book cover data
def get_book_cover(book):
# get highest-resolution book cover possible
openl_url = 'https://covers.openlibrary.org/b/isbn/' + book['ISBN-13'] + '-L.jpg?default=false'
request = requests.get(openl_url)
if request.status_code != 200:
book.update(cover(isbn))
else:
book_cover = {'thumbnail': openl_url}
book.update(book_cover)
return book

+ 39
- 0
web/app/search.py Visa fil

@@ -0,0 +1,39 @@
# @name: search.py
# @creation_date: 2023-04-04
# @license: The MIT License <https://opensource.org/licenses/MIT>
# @author: Simon Bowie <ad7588@coventry.ac.uk>
# @purpose: Search route for search results
# @acknowledgements:
# https://medium.com/@joseortizcosta/search-utility-with-flask-and-mysql-60bb8ee83dad

from flask import Blueprint, render_template, request, flash, redirect, url_for
from .models import Resource
from sqlalchemy import or_
import markdown

search = Blueprint('search', __name__)

#endpoint for search
@search.route('/search', methods=['GET'])
def basic_search():
if request.args.get('query') is not None:
query = request.args.get('query')
results = Resource.query.filter(
or_(
Resource.name.ilike('%' + query + '%'),
Resource.description.ilike('%' + query + '%'),
Resource.developer.ilike('%' + query + '%'),
Resource.longDescription.ilike('%' + query + '%'),
Resource.experimental.ilike('%' + query + '%'),
Resource.considerations.ilike('%' + query + '%'),
Resource.references.ilike('%' + query + '%'),
Resource.author.ilike('%' + query + '%'),
Resource.isbn.ilike('%' + query + '%'),
Resource.typology.ilike('%' + query + '%'),
)).all()
# render Markdown as HTML
for result in results:
result.description = markdown.markdown(result.description)
return render_template('search.html', results=results)
else:
return redirect(url_for('main.index'))

Binär
web/app/static/images/favicon-no_bg.png Visa fil

Before After
Width: 221  |  Height: 222  |  Size: 9.9KB

Binär
web/app/static/images/favicon.png Visa fil

Before After
Width: 221  |  Height: 222  |  Size: 4.1KB

+ 5
- 0
web/app/static/js/alpine.min.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 1
- 0
web/app/static/js/htmx.min.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 10
- 37
web/app/static/js/main.js Visa fil

@@ -1,38 +1,11 @@
/*
# @name: main.js
# @version: 0.1
# @creation_date: 2022-04-07
# @license: The MIT License <https://opensource.org/licenses/MIT>
# @author: Simon Bowie <ad7588@coventry.ac.uk>
# @purpose: JavaScript functions for various functions
# @acknowledgements:
*/
// document.addEventListener('DOMContentLoaded', e => {
// htmx.logAll();
// let as = document.querySelectorAll('a');
// as.forEach(a => {
// a.addEventListener('click', ee => {
// ee.preventDefault();
// })
// })
// })

// dynamic HTML forms based on dropdown menu
$("#resource_type").change(function() {
var $ = jQuery.noConflict();
var resource_type = $(this).val();
$(".resource_type_input").hide("fast", function() {
$("#resource_type_" + resource_type).show("slow");
});
});

// filtering resources by search
$(document).ready(function(){
$("#searchResources").on("keyup", function() {
var value = $(this).val().toLowerCase();
$("#resource-boxes #resource").filter(function() {
$(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
});
});
});

// testing a couple of ways to expand text
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
})
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'))
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl)
})
// htmx.logAll();

+ 1304
- 0
web/app/static/package-lock.json
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 22
- 0
web/app/static/package.json Visa fil

@@ -0,0 +1,22 @@
{
"name": "expub",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"postcss:watch": "postcss ./src/main.css --dir ./styles --watch --verbose"
},
"devDependencies": {
"autoprefixer": "^10.4.2",
"postcss": "^8.4.6",
"postcss-cli": "^9.1.0",
"tailwindcss": "^3.0.18"
},
"author": "",
"license": "ISC",
"dependencies": {

},
"keywords": [],
"description": ""
}

+ 6
- 0
web/app/static/postcss.config.js Visa fil

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}

+ 395
- 0
web/app/static/src/main.css Visa fil

@@ -0,0 +1,395 @@
@tailwind base;

@tailwind components;
@tailwind utilities;



@font-face {
font-family: 'ag';
src: url('ApfelGrotezk-Regular.woff') format("woff");
}

@font-face {
font-family: 'ag-brukt';
src: url('ApfelGrotezk-Brukt.woff') format("woff");
}

@font-face {
font-family: 'ag-fett';
src: url('ApfelGrotezk-Fett.woff') format("woff");
}

@font-face {
font-family: 'dauphine';
src: url('Dauphine-Regular.ttf') format("truetype");
}

@font-face {
font-family: 'dauphine';
src: url('Dauphine-Italic.ttf') format("truetype");
font-style: italic;
}

@font-face {
font-family: 'robo';
src: url('roboto-regular.ttf') format("truetype");
}

@font-face {
font-family: 'robo';
src: url('roboto-italic.ttf') format("truetype");
font-style: italic;
}

@font-face {
font-family: 'cirrus';
src: url('CirrusCumulus.woff') format("woff");
}

body {
font-family: 'dauphine';
}

strong {
font-family: 'ag-fett';
}
b,strong {
font-weight: normal !important;
}

select {
@apply p-1;
}


.book {
font-family: 'ag-fett', 'Arial Black';
}

.tool {
font-family: 'robo';
}
.practice {
font-family: 'cirrus';
}

.std-margin {
@apply p-2;
}

.cell {
@apply min-w-[14rem];
@apply overflow-hidden;
}
.cell.size-1 {
@apply min-w-[30rem];
}


.related {
@apply w-[6rem] lg:w-[12rem] lg:pl-[4rem] px-2 py-4 lg:p-5 text-right;
}

.cell-margin {
@apply p-4;
}

.block-margin {
@apply mb-8;
}

.std-padding {
@apply p-2;
}
.std-padding-x {
@apply px-2;
}

.std-grid {
@apply grid md:grid-cols-2 lg:grid-cols-3;
}

.cell {
@apply cell-margin relative;
}


.cell::before {
content: '';
position: absolute;
top: 10px;
right: 0px;
width: 2px;
height: calc(100% - 20px);
background-color: black;
}

.cell::after {
content: '';
position: absolute;
bottom: 0px;
left: 8px;
width: calc(100% - 20px);
height: 2px;
background-color: black;
}

.slash {
position: relative;
}

.slash::after {
content: '';
position: absolute;

bottom: 0px;
left: 8px;
width: calc(100% - 20px);
height: 3px;
background-color: black;
transform-origin: 100% 100%;
transform: rotate(45deg);
}

/* .meta {
transform: rotate(-3.5deg);
} */

.facts {
@apply grid lg:grid-cols-2 gap-4;
}

.facts h3 {
}

.facts > * {
@apply max-w-[65ch];
}

a:hover {
@apply opacity-90;
}
a.link:hover {
@apply opacity-100;
}


h2,h3 {
hyphens: auto;
}

h3 {
font-family: 'ag-fett';
}

.huge-title {
@apply text-4xl lg:text-5xl;
}
.big-title {
@apply text-3xl lg:text-4xl;
}
.medium-title {
@apply text-2xl lg:text-3xl;
}
.small-title {
@apply text-xl lg:text-2xl;
}
.big-text {
@apply text-xl ;
}
.small-text {
@apply text-base;
}

.indent {
@apply mx-4 my-4;
}

.text > h3 {
@apply ml-6 mt-8 ;
}

h2 + *,
h3 + * {
/* @apply ml-6 mt-1; */
}


p + p {
@apply mt-[1em];
}

.loading {
}

/* .loading .bg-white {
@apply bg-white/90;
opacity: 0;
transform: opacity 0.2s;
}

.htmx-request .bg-white {
opacity: 1;
} */

.loading .bg {
opacity: 0;
background-image: linear-gradient(to top, rgb(103, 48, 255,0.8), rgb(255, 255, 255, 0),rgb(255, 255, 255, 0));
}

.htmx-request .loading .bg {
transition: opacity 4000ms ease;
/* transition: opacity 4000ms cubic-bezier(.16, .01, .31, .99); */
opacity: 1;

}

.loading .spinner .inner {
opacity:0;
transition: opacity 0.4s ease;
width:min-content;
height:min-content;
/* height:200px; */
}
.htmx-request .loading .spinner .inner {
opacity:1;
}


#modal-content main {
opacity: 1;
}
.htmx-request #modal-content main {
/* transition: opacity 0.2s ease; */
opacity: 0;
}

.htmx-request .loading {
opacity: 1;
}

#modal-content {
transition: box-shadow 0.3s ease;
box-shadow: 0 0 4rem rgba(103, 48, 255, 0.8)
}

.htmx-request #modal-content {
box-shadow: 0 0 4rem rgba(103, 48, 255, 0.0)
}

.active {
@apply bg-expurple text-white;
/* background-image: linear-gradient(to top, purple, transparent) ; */
}

.link:hover path,
.active path {
stroke: white;
}

.link:hover rect,
.active rect {
fill: white;
}


.link:hover {
@apply bg-expurple text-white;
}

details[open]::details-content {
display: contents;
}

.text a {
/* font-family: 'ag-fett'; */
text-decoration: underline;
}

#resources.expanded .filter-header::before {
content:'- ';
}
#resources .filter-header::before {
content:'+ ';
}

.fade-right {
@apply relative;
}

.fade-right::after {
@apply absolute top-0 right-0 w-16 lg:w-40 h-full pointer-events-none;

content: '';
background-image: linear-gradient(to left,#fff 0%, #fff 5%, rgba(255,255,255,0) 100%);

}

.menuitem {
@apply px-4 py-2 link items-center;
}


/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}

/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}

/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #000000;
transition: .4s;
}

.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
}

input:checked+.slider {
background-color: #000000;
}

input:focus+.slider {
box-shadow: 0 0 1px #000000;
}

input:checked+.slider:before {
transform: translateX(26px);
}

/* Rounded sliders */
.slider.round {
border-radius: 34px;
}

.slider.round:before {
border-radius: 50%;
}

Binär
web/app/static/styles/ApfelGrotezk-Brukt.woff Visa fil


Binär
web/app/static/styles/ApfelGrotezk-Brukt.woff2 Visa fil


Binär
web/app/static/styles/ApfelGrotezk-Fett.woff Visa fil


Binär
web/app/static/styles/ApfelGrotezk-Fett.woff2 Visa fil


Binär
web/app/static/styles/ApfelGrotezk-Regular.woff Visa fil


Binär
web/app/static/styles/ApfelGrotezk-Regular.woff2 Visa fil


Binär
web/app/static/styles/CirrusCumulus.woff Visa fil


Binär
web/app/static/styles/Dauphine-Italic.ttf Visa fil


Binär
web/app/static/styles/Dauphine-Regular.ttf Visa fil


+ 1552
- 0
web/app/static/styles/main.css
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


Binär
web/app/static/styles/roboto-italic.ttf Visa fil


Binär
web/app/static/styles/roboto-regular.ttf Visa fil


+ 39
- 0
web/app/static/tailwind.config.js Visa fil

@@ -0,0 +1,39 @@
module.exports = {
mode: 'jit',
content: [
'../templates/*.html'
],
theme: {
extend: {
fontSize: {
// xs: ['0.95rem', { lineHeight: '1.35' }],
sm: ['0.95rem', { lineHeight: '1.35' }],
base: ['1.1rem', { lineHeight: '1.22' }],
lg: ['1.24rem', { lineHeight: '1.1' }],
xl: ['1.5rem', { lineHeight: '1.1' }],
'2xl': ['1.85rem', { lineHeight: '1' }],
'3xl': ['2rem', { lineHeight: '1' }],
'4xl': ['3rem', { lineHeight: '1' }],
'5xl': ['4.2rem', { lineHeight: '0.9' }],
// '6xl': ['3.75rem', { lineHeight: '1.04' }],
// '7xl': ['4.5rem', { lineHeight: '1.1' }],
// '8xl': ['6rem', { lineHeight: '1' }],
// '9xl': ['8rem', { lineHeight: '1' }],
},
colors: {
'expurple': {
DEFAULT: 'rgba(103, 48, 255,0.5)'
}
// 'ggreen': {
// light: '#67e8f9',
// DEFAULT: 'var(--primary-color)',
// dark: '#0e7490',
// },
},
}
},
variants: {
extend: {},
},
plugins: [],
}

+ 7
- 1
web/app/templates/about.html Visa fil

@@ -2,6 +2,12 @@

{% block content %}

{{ text|safe }}
<div id="about" class="cell-margin text max-w-[65ch]">
{{ about_text|safe }}
</div>

<div id="colophon" class="cell-margin text">
{{ colophon_text | safe }}
</div>

{% endblock %}

+ 4
- 4
web/app/templates/api.html Visa fil

@@ -1,18 +1,18 @@
{% block content %}

<a href="/api/users">Users</a> (login required)
<a href="{{ url_for('api.get_users_json') }}">Users</a> (login required)

<br><br>

<a href="/api/tools">Tools</a>
<a href="{{ url_for('api.get_tools_json') }}">Tools</a>

<br><br>

<a href="/api/practices">Practices</a>
<a href="{{ url_for('api.get_practices_json') }}">Practices</a>

<br><br>

<a href="/api/books">Books</a>
<a href="{{ url_for('api.get_books_json') }}">Books</a>

<br><br>


+ 437
- 97
web/app/templates/base.html Visa fil

@@ -12,119 +12,459 @@
# Boostrap select: https://stackoverflow.com/questions/67942546/bootstrap-5-select-dropdown-with-the-multiple-attribute-collapsed
-->

{% macro view_switch() %}

<div class="flex">
<a
href="{{ url_for(request.endpoint) }}?view=regular{% for key in request.args %}{% if key != 'view' %}&{{ key }}={{ request.args.get(key) }}{% endif %}{% endfor %}"
class="block link flex p-3 gap-2 {% if view != 'list' %} active {% endif %}"
>
<div class="w-5 h-5 inline-block">
<svg width="100%" height="100%" viewBox="0 0 46 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="21" height="36" fill="black" />
<rect x="27" width="19" height="14" fill="black" />
<rect x="27" y="21" width="19" height="15" fill="black" />
</svg>
</div>
Expanded
</a>
<a
href="{{ url_for(request.endpoint) }}?view=list{% for key in request.args %}{% if key != 'view' %}&{{ key }}={{ request.args.get(key) }}{% endif %}{% endfor %}"
class="block link flex p-3 gap-2 {% if view == 'list' %} active {% endif %}"
>
<div class="w-5 h-5 inline-block ">
<svg width="100%" height="100%" viewBox="0 0 38 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 2H38" stroke="black" stroke-width="2.5" />
<path d="M0 13H38" stroke="black" stroke-width="2.5" />
<path d="M0 24H38" stroke="black" stroke-width="2.5" />
<path d="M0 35H38" stroke="black" stroke-width="2.5" />
</svg>
</div>
List
</a>
</div>

{% endmacro %}

{% macro menu(type) %}
<a
class="block menuitem w-48 "
href="{{ url_for('main.index') }}{% if view == 'list' %}?view=list{% endif %}"
@click="menuOpen = false"
><span>Experimental Publishing Compendium <span class="ml-3 inline-block -rotate-12 italic text-[#f52d2d]" style="text-shadow: 0 0 0.4rem white">beta</span></span>
</a>

<a href="{{ url_for('tool.get_tools') }}{% if view == 'list' %}?view=list{% endif %}"
class="{{ 'active' if request.path == url_for('tool.get_tools') }} menuitem tool medium-title {% if type=='top' %} hidden lg:block {% else %} block {% endif %}"
@click="menuOpen = false"
>
Tools
</a>

<a href="{{ url_for('practice.get_practices') }}{% if view == 'list' %}?view=list{% endif %}"
class="{{ 'active' if request.path == url_for('practice.get_practices') }} menuitem practice medium-title {% if type=='top' %} hidden lg:block {% else %} block {% endif %}"
@click="menuOpen = false"
>
Practices
</a>

<a href="{{ url_for('book.get_books') }}{% if view == 'list' %}?view=list{% endif %}"
class="{{ 'active' if request.path == url_for('book.get_books') }} menuitem medium-title book block {% if type=='top' %} hidden lg:block {% else %} block {% endif %} "
@click="menuOpen = false"
>
Books
</a>

<a href="{{ url_for('main.about') }}{% if view == 'list' %}?view=list{% endif %}"
class="{{ 'active' if request.path == url_for('main.about') }} menuitem mr-auto {% if type=='top' %} hidden lg:block {% else %} block {% endif %}"
@click="menuOpen = false"
>
About / <br> Contact
</a>

<!-- <div class="ml-auto mr-2">
{% if current_user.is_authenticated %}
<a href="{{ url_for('create.create_resource') }}" class="block link">
Add resource
</a>
{% endif %}
{% if current_user.is_authenticated %}
<a href="{{ url_for('main.profile') }}" class="block link">
Profile
</a>
{% endif %}
{% if not current_user.is_authenticated %}
<a href="{{ url_for('auth.login') }}" class="block link">
Login
</a>
<a href="{{ url_for('auth.signup') }}" class="block link">
Sign Up
</a>
{% endif %}
{% if current_user.is_authenticated %}
<a href="{{ url_for('auth.logout') }}" class="block link">
Logout
</a>
{% endif %}
</div> -->

<form action="{{ url_for('search.basic_search') }}" class="hidden lg:block">
<label id="search-label" for="siteSearch" class="sr-only">Search</label>
<input type="text" name="query" class="text-center h-full text-base border-l-2 border-black pl-1" aria-labelledby="search-label"
placeholder="Search">
<input class="hidden" type="submit" value="Enter search" hidden />
</form>
{% endmacro %}

{% macro relationships_links(resource) %}

{% if resource.tools %}
<div class="">
<div class="px-4 mt-16 lg:mt-0">
Tools
</div>
{% for tool in resource.tools %}
<div class="cell">
{{ resource_lead(tool,size=2) }}
</div>
{% endfor %}
</div>
{% endif %}
{% if resource.books %}
<div class="">
<div class="px-4 mt-16 lg:mt-0">
Books
</div>
{% for book in resource.books %}
<div class="cell">
{{ resource_lead(book,size=2) }}
</div>
{% endfor %}
</div>
{% endif %}
{% if resource.practices %}
<div class="">
<div class="px-4 mt-16 lg:mt-0">
Practices
</div>
{% for practice in resource.practices %}
<div class="cell">
{{ resource_lead(practice,size=2) }}
</div>
{% endfor %}
</div>
{% endif %}

{% endmacro %}

{% macro resource_list(resource, loop, show_number=true) %}
<div class="border-b-2 border-black">
<a class="flex gap-4 h-full py-1 hover:opacity-60 transition-opacity cursor-pointer size-{{ size }}" {% if
resource['type']=='book' %} href="{{ url_for('book.show_book', book_id=resource['id']) }}" {% endif %} {% if
resource['type']=='tool' %} href="{{ url_for('tool.show_tool', tool_id=resource['id']) }}" {% endif %} {% if
resource['type']=='practice' %} href="{{ url_for('practice.show_practice', practice_id=resource['id']) }}" {% endif
%} hx-target="#modal-content" hx-select="main" hx-swap="innerHTML" @click="openModal()">

<div class="w-[4rem] shrink-0 lg:w-[12rem] text-center ">
{% if show_number %}
{{loop.index}} / {{count}}
{% else %}
<div class="capitalize inline-block min-w-[5rem] px-4 ">{{ resource['type'] }}</div>
{% endif %}
</div>


<div>
<span class="{{ resource.type }}">
{{ resource.name }}
</span>
{% if resource['year'] %}
{{ resource['year'] }}{% if resource['author'] %}, {{ resource['author'] }}{% endif%}
{% endif %}
</div>
</a>
</div>
{% endmacro %}

{% macro resource_with_related(resource, loop, show_number=true) %}
<div class="w-full border-b-2 border-black fade-right">
<div class="content w-full py-8 overflow-x-auto">
<div class="grid lg:grid-rows-[auto,auto,auto] grid-flow-col w-fit pr-40 " >
<div class="w-[4rem] shrink-0 lg:w-[12rem] text-center px-2 py-4 ">
{% if show_number %}
<p>{{loop.index}} / {{count}}</p>
{% else %}
<p class="capitalize">{{ resource['type'] }}</p>
{% endif %}
</div>
<div class="cell w-[20rem] lg:w-[32rem] row-start-1 lg:row-span-3">
{{ resource_lead(resource) }}
</div>
{% if resource.tools %}
<div class="grid lg:grid-rows-1 grid-flow-col w-fit h-fit">
<div class="related">
Related<br>Tools
</div>
{% for tool in resource.tools %}
<div class="cell w-[20rem] lg:w-[30rem]">
{{ resource_lead(tool,size=2) }}
</div>
{% endfor %}
</div>
{% endif %}
{% if resource.books %}
<div class="grid grid-rows-1 grid-flow-col w-fit h-fit">
<div class="related">
Related<br>Books
</div>
{% for book in resource.books %}
<div class="cell w-[20] lg:w-[35rem]">
{{ resource_lead(book,size=2) }}
</div>
{% endfor %}
</div>
{% endif %}
{% if resource.practices %}
<div class="grid grid-rows-1 grid-flow-col w-fit h-fit">
<div class="related">
Related<br>Practices
</div>
{% for practice in resource.practices %}
<div class="cell w-[20rem] lg:w-[25rem]">
{{ resource_lead(practice,size=2) }}
</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
{% endmacro %}

{% macro popup_link(title,url) %}
<a href="{{ url }}" class="nav-link">
{{ title }}
</a>
{% endmacro %}

{% macro resource_lead(resource,size=1) %}
<a
class="block h-full hover:opacity-60 transition-opacity cursor-pointer size-{{ size }}"

{% if resource['type'] == 'book' %}
href="{{ url_for('book.show_book', book_id=resource['id']) }}"
{% endif %}
{% if resource['type'] == 'tool' %}
href="{{ url_for('tool.show_tool', tool_id=resource['id']) }}"
{% endif %}
{% if resource['type'] == 'practice' %}
href="{{ url_for('practice.show_practice', practice_id=resource['id']) }}"
{% endif %}
hx-target="#modal-content"
hx-select="main"
hx-swap="innerHTML"

@click="openModal()"
>

{% if (resource.type == 'book') and (resource.references) %}
<img class="w-20 h-20 object-contain float-right m-4 grayscale rotate-[15deg]" src="{{resource.references}}" alt="cover for {{ resource.name }}">
{% endif %}
<h2 class="{{ resource['type'] }} {% if size==1 %} big-title {% else %} small-title {% endif %} mb-2">{{ resource['name'] }}</h2>
{% if resource['year'] %}
<div class="text-sm">
{{ resource['year'] }}{% if resource['author'] %}, {{ resource['author'] }}{% endif%}
</div>
{% endif %}

<div class="{% if size==1 %} big-text {% else %} small-text {% endif %} mb-[1em]">
{{ resource['description'] | truncate(150) | safe }}
</div>

</a>
{% if current_user.is_authenticated %}
<div class="">
{% if resource['type'] == 'tool' %}
<a href="{{ url_for('tool.edit_tool', tool_id=resource['id']) }}">
<span class="absolute top-0 left-0 text-xs">Edit</span>
</a>
{% elif resource['type'] == 'practice' %}
<a href="{{ url_for('practice.edit_practice', practice_id=resource['id']) }}">
<span class="absolute top-0 left-0 text-xs">Edit</span>
</a>
{% elif resource['type'] == 'book' %}
<a href="{{ url_for('book.edit_book', book_id=resource['id']) }}">
<span class="absolute top-0 left-0 text-xs">Edit</span>
</a>
{% endif %}
</div>
{% endif %}
{% endmacro %}


<!DOCTYPE html>
<html>
<html lang="en">

<head>
{{ moment.include_moment() }}
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ExPub Compendium</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link href="{{ url_for('static',filename='styles/custom.css') }}" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.14.0-beta2/css/bootstrap-select.min.css" integrity="sha512-mR/b5Y7FRsKqrYZou7uysnOdCIJib/7r5QeJMFvLNHNhtye3xJp1TdJVPLtetkukFn227nKpXD9OjUc09lx97Q==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<title>Experimental Publishing Compendium</title>
<script src="{{ url_for('static',filename='js/alpine.min.js') }}" defer></script>
<script src="{{ url_for('static',filename='js/htmx.min.js') }}"></script>
<link href="{{ url_for('static',filename='styles/main.css') }}" rel="stylesheet">
<link rel="shortcut icon" href="{{ url_for('static',filename='images/favicon-no_bg.png') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static',filename='images/favicon-no_bg.png') }}" type="image/x-icon">
<script>
// htmx.on('htmx:beforeRequest', e=> {
// console.log(e)
// })
// htmx.logAll()

function base() {
return {
menuOpen: false,
modalOpen: false,
showRelated: false,
home: '/',

hideIfBase() {
let str = document.location.toString();
str = str.replace('http://', '');
str = str.replace('https://', '');
let l = str.split('/').length - 1;
if (l < 2) {
this.modalOpen = false;
}
return l;
},
init() {
this.$watch('document.location', (value, oldValue) => {
console.log('new url', value);
});
window.addEventListener('popstate', e => {
this.hideIfBase();
});
},
hideOverlay() {
this.modalOpen = false;

// window.history.pushState({}, '', this.home);
},
openModal() {
// this.$refs.modal.innerHTML = '';
this.modalOpen = true;
let mc = document.querySelector('#modal-content');
mc.scrollTo(0, 0);
}
}
}
</script>
</head>

<body class="d-flex flex-column min-vh-100">

<header>
<!-- Fixed navbar -->
<nav class="navbar navbar-expand-md navbar-light sticky-top">
<a class="navbar-brand" href="{{ url_for('main.index')}}">ExPub Compendium</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a href="{{ url_for('main.about') }}" class="nav-link">
About
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('tool.get_tools') }}" class="nav-link">
Tools
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('practice.get_practices') }}" class="nav-link">
Practices
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('book.get_books') }}" class="nav-link">
Books
</a>
</li>
{% if current_user.is_authenticated %}
<li class="nav-item">
<a href="{{ url_for('create.create_resource') }}" class="nav-link">
Add resource
</a>
</li>
{% endif %}
{% if current_user.is_authenticated %}
<li class="nav-item">
<a href="{{ url_for('main.profile') }}" class="nav-link">
Profile
</a>
</li>
{% endif %}
{% if not current_user.is_authenticated %}
<li class="nav-item">
<a href="{{ url_for('auth.login') }}" class="nav-link">
Login
</a>
</li>
<li class="nav-item">
<a href="{{ url_for('auth.signup') }}" class="nav-link">
Sign Up
</a>
</li>
{% endif %}
{% if current_user.is_authenticated %}
<li class="nav-item">
<a href="{{ url_for('auth.logout') }}" class="nav-link">
Logout
</a>
</li>

<body
class="text-base overflow-y-scroll"
x-data="base()"
hx-boost="true"
hx-select="#all"
hx-target="#all"
hx-swap="outerHTML"
hx-indicator="body"
>
<div id="loading" class="loading pointer-events-none ">
<!-- <div class="bg-white fixed top-0 left-0 w-full h-screen z-20"></div> -->
<div class="bg fixed top-0 left-0 w-full h-screen z-30"></div>
<div class="spinner fixed top-0 left-0 w-full h-screen z-[60] flex justify-center items-center">
<div class="inner bg-black rounded-full px-8 py-6 text-white text-center">
Loading...
</div>
</div>
</div>

<div id="all">
<header class="sticky top-0 z-10 bg-white border-b-[2px] border-black flex justify-between w-full">
<nav class=" lg:flex justify-center items-stretch w-full ">
{{ menu('top') }}
</nav>
<div class="burger h-14 cursor-pointer block lg:hidden p-4" @click="menuOpen = true">
<svg width="100%" height="100%" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 4H19" stroke="black" stroke-width="1.5" />
<path d="M19 9H1" stroke="black" stroke-width="1.5" />
<path d="M1 14H19" stroke="black" stroke-width="1.5" />
</svg>

</div>
</header>

<menu
class="fixed top-0 right-0 z-20 p-8 bg-white w-[calc(100%-2rem)] max-w-[30rem] h-screen"
x-show="menuOpen"
style="box-shadow: 0 0 2rem rgb(0,0,0,0.2);"
@click.outside = "menuOpen = false"
>
{{ menu('side') }}
</menu>


<!-- Begin page content -->
<main>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-danger">
{{ messages[0] }}
</div>
{% endif %}
</ul>
</div>
</nav>
</header>

<!-- Begin page content -->
<main class="flex-shrink-0">
<div class="layer-bg w-100">
<img class="img-fluid w-100" src="{{ url_for('static',filename='images/dots-bg.png') }}" alt="bg-shape">
</div>
<div class="container-fluid">
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-danger">
{{ messages[0] }}
</div>
{% endif %}
{% endwith %}
{% block content %}
{% endblock %}
</div>
</main>
{% endwith %}
{% block content %}
{% endblock %}
</main>
</div>

<div id="modal" x-show="modalOpen" class="modal h-screen w-full fixed top-0 z-50 p-4 bg overscroll-none">
<div class="cross cursor-pointer absolute top-10 right-10 w-10 h-10" @click="hideOverlay()">
<svg width="100%" height="100%" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 2L18 18M18 2L2 18" stroke="black" stroke-width="1"></path>
</svg>
</div>
<div id="modal-content" @click.outside="hideOverlay()" x-ref="modal" class="content w-full overflow-y-auto h-full bg-white " >

</div>
</div>

<!-- Sticky footer-->
<footer class="footer py-3 mt-auto" style="background-color: #dcddde;">
<footer class="std-margin mt-20 text-sm">
<div class="container">
<span class="text-muted">© 2022–{{ moment().format('YYYY') }} <a href="https://copim.ac.uk/">COPIM</a> and licensed under a <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License (CC BY 4.0)</a>.</span>
<span class="">© 2022–{{ moment().format('YYYY') }} <a href="https://copim.ac.uk/">COPIM</a> and licensed under a <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License (CC BY 4.0)</a>.</span>
</div>
</footer>
<!-- JavaScript -->
<!-- jQuery first, then Popper JS, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.14.0-beta2/js/bootstrap-select.min.js" integrity="sha512-FHZVRMUW9FsXobt+ONiix6Z0tIkxvQfxtCSirkKc5Sb4TKHmqq1dZa8DphF0XqKb3ldLu/wgMa8mT6uXiLlRlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="{{ url_for('static',filename='js/main.js') }}"></script>
</body>


+ 112
- 190
web/app/templates/book.html Visa fil

@@ -2,213 +2,135 @@

{% block content %}

{% if book['thumbnail'] %}
<img class="img-fluid mx-auto d-block py-3" src={{ book['thumbnail'] }} alt="cover for {{ book['Title'] }}">
{% else %}
<div class="row">
<div class="col">
<h1 class="text-center">{% block title %} {{ book['Title'] or resource['name'] }} {% endblock %}</h1>
</div>
</div>
{% endif %}
{% if current_user.is_authenticated %}
<div class="row text-center py-3">
<a href="{{ url_for('book.edit_book', book_id=resource['id']) }}">
<span class="badge bg-dark">Edit</span>
</a>
</div>
{% endif %}
<div class="row">
<div class="col">
<table class="table table-hover">
<tbody>
{% if resource['typology'] %}
<tr>
<th>
Typology category:
</th>
<td>
<a href="/books?typology={{ resource['typology'] }}">{{ resource['typology'] }}</a>
</td>
</tr>
<div class="cell-margin grid mt-16 lg:grid-cols-2">
<div class="left">
<div class="mb-2">Book</div>
<div class="border-r-2 border-black pr-8">
{% if resource.references %}
<div class="float-right">
<img class="w-40 h-40 object-contain m-16 rotate-[15deg]" src={{ resource.references }} alt="cover for {{ resource['Title'] }}">
</div>
{% endif %}
<h2 class="book huge-title mb-2 max-w-[30rem]">{% block title %} {{ resource.name }} {% endblock %}</h2>
{% if resource['Year'] %}
{{ resource['Year'] }}
{% endif %}
{% if resource['Authors'] %}
<div class="">
{% if resource['Authors']|length > 1 %}
<div class="">
{% for author in resource['Authors'] %}
{{ author }}</br>
{% endfor %}
</div>
{% else %}
<div>
{% for author in resource['Authors'] %}
{{ author }}
{% endfor %}
</div>
{% endif %}
{% if resource['bookUrl'] %}
<tr>
<th>
URL:
</th>
<td>
<a href="{{ resource['bookUrl'] }}">{{ resource['bookUrl'] }}</a>
</td>
</tr>
</div>
{% endif %}
<div class="">
{% if current_user.is_authenticated %}
<div class="">
<a href="{{ url_for('book.edit_book', book_id=resource['id']) }}">
<span class="badge bg-dark">Edit</span>
</a>
</div>
{% endif %}
{% if book %}
<!-- fields for books from isbntools -->
{% if book['Title'] %}
<tr>
<th>
Title:
</th>
<td>
{{ book['Title'] }}
</td>
</tr>
{% endif %}
{% if book['Authors'] %}
<tr>
{% if book['Authors']|length > 1 %}
<th>
Authors:
</th>
<td>
{% for author in book['Authors'] %}
{{ author }}</br>
{% endfor %}
</td>
{% else %}
<th>
Author:
</th>
<td>
{% for author in book['Authors'] %}
{{ author }}
{% endfor %}
</td>
{% endif %}
</tr>
{% endif %}
{% if book['ISBN-13'] %}
<tr>
<th>
ISBN-13:
</th>
<td>
{{ book['ISBN-13'] }}
</td>
</tr>
<div class="facts ">
{% if resource['typology'] %}
<div class="">
<h3>Typology category</h3>
<a href="/books?typology={{ resource['typology'] }}">{{ resource['typology'] }}</a>
</div>
{% endif %}
{% if book['Year'] %}
<tr>
<th>
Publication year:
</th>
<td>
{{ book['Year'] }}
</td>
</tr>
{% if resource['bookUrl'] %}
<div class="">
<h3>URL</h3>
<a target="_blank" href="{{ resource['bookUrl'] }}">{{ resource['bookUrl'] }}</a>
</div>
{% endif %}
{% if book['Publisher'] %}
<tr>
<th>
Publisher:
</th>
<td>
{{ book['Publisher'] }}
</td>
</tr>
<!-- fields for books from isbntools -->
{% if resource['ISBN-13'] %}
<div class="">
<h3>
ISBN-13
</h3>
<div>
{{ resource['ISBN-13'] }}
</div>
</div>
{% endif %}
{% if book['desc'] %}
<tr>
<th>
Publisher's description:
</th>
<td>
{{ book['desc'] }}
</td>
</tr>
{% if resource['Publisher'] %}
<div class="">
<h3>
Publisher
</h3>
<div>
{{ resource['Publisher'] }}
</div>
</div>
{% endif %}
{% else %}
<!-- fields for books from database -->
{% if resource['name'] %}
<tr>
<th>
Title:
</th>
<td>
{{ resource['name'] }}
</td>
</tr>
<!-- <div class="">
<h3>Title</h3>
<div class="">{{ resource['name'] }}</div>
</div> -->
{% endif %}
{% if resource['author'] %}
<tr>
<th>
Author:
</th>
<td>
{{ resource['author'] }}
</td>
</tr>
<div class="">
<h3>Author</h3>
<div class="">{{ resource['author'] }}</div>
</div>
{% endif %}
{% if resource['year'] %}
<tr>
<th>
Publication year:
</th>
<td>
<div class="">
<h3>Publication year</h3>
<div>
{{ resource['year'] }}
</td>
</tr>
</div>
</div>
{% endif %}
{% endif %}
{% if resource['description'] %}
<tr>
<th>
Experimental aspects:
</th>
<td>
{{ resource['description'] }}
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
{% if relationships %}
<div class="row">
<div class="col">
<h2 class="text-center">Linked resources:</h2>
</div>
</div>
<div class="row">
{% for relationship in relationships %}
<div class="col-md-4 col-sm-6 py-3">
{% if relationship['type'] == 'tool' %}
<div class="card text-dark bg-tool mb-3">
<div class="card-body">
<a href="{{ url_for('tool.show_tool', tool_id=relationship['id']) }}">
<h3 class="card-title text-center text-dark">{{ relationship['name'] }}</h3>
</a>
<p class="card-text">
{{ relationship['description']|truncate(100) }}
</p>
</div>
</div>
{% elif relationship['type'] == 'practice' %}
<div class="card text-dark bg-practice mb-3">
<div class="card-body">
<a href="{{ url_for('practice.show_practice', practice_id=relationship['id']) }}">
<h3 class="card-title text-center text-dark">{{ relationship['name'] }}</h3>
</a>
<p class="card-text">
{{ relationship['description']|truncate(100) }}
</p>
{% if resource['desc'] %}
<div class="col-span-2">
<h3>Publisher's description</h3>
<div class="">{{ resource['desc'] }}</div>
</div>
{% endif %}
{% if resource['description'] %}
<div class="col-span-2">
<h3>
Experimental aspects:
</h3>
<div>
{{ resource['description']|safe }}
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% elif relationship['type'] == 'book' %}
<div class="card text-dark bg-book mb-3">
<div class="card-body">
<a href="{{ url_for('book.show_book', book_id=relationship['id']) }}">
<h3 class="card-title text-center text-dark">{{ relationship['name'] }}</h3>
</a>
<p class="card-text">
{{ relationship['description']|truncate(100) }}
</p>
<div class="right p-4 pr-8 lg:p-0">
<div class="grid lg:grid-cols-2">
{{ relationships_links(resource) }}
</div>
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}

{% endblock %}


+ 20
- 25
web/app/templates/index.html Visa fil

@@ -1,33 +1,28 @@
{% extends "base.html" %}

{% block content %}
<div class="">

{{ text|safe }}

<hr>
<p>
Browse from this selection of random tools, search or explore tools, practices, and experimental books.
</p>

<div class="row">
{% for tool in tools %}
<div class="col-md-4 col-sm-6 py-3">
<div class="card text-dark bg-info mb-3">
<div class="card-body">
<a href="{{ url_for('tool.show_tool', tool_id=tool['id']) }}">
<h3 class="card-title text-center text-dark">{{ tool['name'] }}</h3>
</a>
<p class="card-text">
{{ tool['description']|truncate(100) }}
</p>
{% if current_user.is_authenticated %}
<a href="{{ url_for('tool.edit_tool', tool_id=tool['id']) }}">
<span class="badge bg-dark">Edit</span>
</a>
{% endif %}
<div class="border-b-2 border-black grid gap-8 lg:grid-cols-[52rem,30rem] content-start">
<div class="mx-2 lg:ml-[13rem] text my-8 meta max-w-[30rem]">
{{ text|safe }}
</div>
</div>

<div class="flex gap-4 items-center min-h-[2rem] mb-8 mb-8 border-b-2 border-black ">
{{ view_switch() }}
</div>
{% endfor %}


{% if view == 'list' %}
{% for resource in resources %}
{{ resource_list(resource, loop, false) }}
{% endfor %}
{% else %}
{% for resource in resources %}
{{ resource_with_related(resource, loop, false) }}
{% endfor %}
{% endif %}

</div>
{% endblock %}
{% endblock %}

+ 122
- 204
web/app/templates/resource.html Visa fil

@@ -2,212 +2,130 @@

{% block content %}

<div class="row">
<div class="col">
<h1 class="text-center">{% block title %} {{ resource['name'] }} {% endblock %}</h1>
</div>
</div>
{% if current_user.is_authenticated %}
{% if resource['type'] == 'tool' %}
<div class="row text-center py-3">
<a href="{{ url_for('tool.edit_tool', tool_id=resource['id']) }}">
<span class="badge bg-dark">Edit</span>
</a>
</div>
{% elif resource['type'] == 'practice' %}
<div class="row text-center py-3">
<a href="{{ url_for('practice.edit_practice', practice_id=resource['id']) }}">
<span class="badge bg-dark">Edit</span>
</a>
</div>
{% endif %}
{% endif %}
<div class="row">
<div class="col">
<table class="table table-hover">
<tbody>
<tr>
<th>
Created:
</th>
<td>
{{ resource['created'].strftime("%Y-%m-%d %H:%M") }} UTC
</td>
</tr>
{% if resource['description'] %}
<tr>
<th>
Description:
</th>
<td>
{{ resource['description'] }}
</td>
</tr>
{% endif %}
<!-- fields for tools -->
{% if resource['developer'] %}
<tr>
<th>
Developer
</th>
<td>
{% if resource['developerUrl'] %}
<a href="{{ resource['developerUrl'] }}">{{ resource['developer'] }}</a>
{% else %}
{{ resource['developer'] }}
{% endif %}
</td>
</tr>
{% endif %}
{% if resource['license'] %}
<tr>
<th>
Software license:
</th>
<td>
{{ resource['license'] }}
</td>
</tr>
{% endif %}
{% if resource['scriptingLanguage'] %}
<tr>
<th>
Software language(s):
</th>
<td>
{{ resource['scriptingLanguage'] }}
</td>
</tr>
{% endif %}
{% if resource['projectUrl'] %}
<tr>
<th>
Project page:
</th>
<td>
<a href="{{ resource['projectUrl'] }}">{{ resource['projectUrl'] }}</a>
</td>
</tr>
{% endif %}
{% if resource['repositoryUrl'] %}
<tr>
<th>
Code repository:
</th>
<td>
<a href="{{ resource['repositoryUrl'] }}">{{ resource['repositoryUrl'] }}</a>
</td>
</tr>
{% endif %}
{% if resource['ingestFormats'] %}
<tr>
<th>
Import / ingest formats:
</th>
<td>
{{ resource['ingestFormats'] }}
</td>
</tr>
{% endif %}
{% if resource['outputFormats'] %}
<tr>
<th>
Output formats:
</th>
<td>
{{ resource['outputFormats'] }}
</td>
</tr>
{% endif %}
{% if resource['status'] %}
<tr>
<th>
Platform status:
</th>
<td>
{{ resource['status'] }}
</td>
</tr>
{% endif %}
<!-- fields for practices -->
{% if resource['longDescription'] %}
<tr>
<th>
Full description
</th>
<td>
{{ resource['longDescription']|safe }}
</td>
</tr>
{% endif %}
{% if resource['experimental'] %}
<tr>
<th>
Experimental uses
</th>
<td>
{{ resource['experimental']|safe }}
</td>
</tr>
{% endif %}
{% if resource['considerations'] %}
<tr>
<th>
Considerations
</th>
<td>
<p style="white-space: pre-line">{{ resource['considerations']|safe }}</p>
</td>
</tr>
{% endif %}
{% if resource['references'] %}
<tr>
<th>
References:
</th>
<td>
<p style="white-space: pre-line">{{ resource['references']|safe }}</p>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
{% if relationships %}
<div class="row">
<div class="col">
<h2 class="text-center">Linked resources:</h2>
</div>
</div>
<div class="row">
{% for relationship in relationships %}
<div class="col-md-4 col-sm-6 py-3">
{% if relationship['type'] == 'tool' %}
<div class="card text-dark bg-tool mb-3">
<div class="card-body">
<a href="{{ url_for('tool.show_tool', tool_id=relationship['id']) }}">
<h3 class="card-title text-center text-dark">{{ relationship['name'] }}</h3>
</a>
<p class="card-text">
{{ relationship['description']|truncate(100) }}
</p>
</div>
</div>
{% elif relationship['type'] == 'practice' %}
<div class="card text-dark bg-practice mb-3">
<div class="card-body">
<a href="{{ url_for('practice.show_practice', practice_id=relationship['id']) }}">
<h3 class="card-title text-center text-dark">{{ relationship['name'] }}</h3>
</a>
<p class="card-text">
{{ relationship['description']|truncate(100) }}
</p>


<div class="cell-margin grid mt-16 lg:grid-cols-2">
<div class="left ">
<div class="mb-4 capitalize">{{ resource['type'] }}</div>
<div class="lg:border-r-2 lg:border-black lg:pr-8">
<h2 class="block-margin huge-title {{ resource['type'] }}">
{% block title %}
{% autoescape false %}
{{ resource['name'] | replace(" ","<br>") }}
{% endautoescape %}
{% endblock %}
</h2>
{% if current_user.is_authenticated %}
{% if resource['type'] == 'tool' %}
<div class="row text-center py-3">
<a href="{{ url_for('tool.edit_tool', tool_id=resource['id']) }}">
<span class="">Edit</span>
</a>
</div>
{% elif resource['type'] == 'practice' %}
<div class="row text-center py-3">
<a href="{{ url_for('practice.edit_practice', practice_id=resource['id']) }}">
<span class="">Edit</span>
</a>
</div>
{% endif %}
{% endif %}
<div class="facts">
{% if resource['description'] %}
<div class="">
<h3>Description</h3>
{{ resource['description'] }}
</div>
{% endif %}
<!-- fields for tools -->
{% if resource['developer'] %}
<div class="">
<h3>Developer</h3>
{% if resource['developerUrl'] %}
<a href="{{ resource['developerUrl'] }}">{{ resource['developer'] }}</a>
{% else %}
{{ resource['developer'] }}
{% endif %}
</div>
{% endif %}
{% if resource['license'] %}
<div class="">
<h3>Software license</h3>
{{ resource['license'] }}
</div>
{% endif %}
{% if resource['scriptingLanguage'] %}
<div class="">
<h3>Software language(s)</h3>
{{ resource['scriptingLanguage'] }}
</div>
{% endif %}
{% if resource['projectUrl'] %}
<div class="">
<h3>Project page</h3>
<a href="{{ resource['projectUrl'] }}">{{ resource['projectUrl'] }}</a>
</div>
{% endif %}
{% if resource['repositoryUrl'] %}
<div class="">
<h3>Code repository</h3>
<a href="{{ resource['repositoryUrl'] }}">{{ resource['repositoryUrl'] }}</a>
</div>
{% endif %}
{% if resource['ingestFormats'] %}
<div class="">
<h3>Import / ingest formats</h3>
{{ resource['ingestFormats'] }}
</div>
{% endif %}
{% if resource['outputFormats'] %}
<div class="">
<h3>Output formats</h3>
{{ resource['outputFormats'] }}
</div>
{% endif %}
{% if resource['status'] %}
<div class="">
<h3>Platform status</h3>
{{ resource['status'] }}
</div>
{% endif %}
<!-- fields for practices -->
{% if resource['longDescription'] %}
<div class="col-span-2">
<h3>Full description</h3>
{{ resource['longDescription']|safe }}
</div>
{% endif %}
{% if resource['experimental'] %}
<div class="col-span-2">
<h3>Experimental uses</h3>
{{ resource['experimental']|safe }}
</div>
{% endif %}
{% if resource['considerations'] %}
<div class="col-span-2">
<h3>Considerations</h3>
<p>{{ resource['considerations']|safe }}</p>
</div>
{% endif %}
{% if resource['references'] %}
<div class="col-span-2">
<h3> Further reading </h3>
<p>{{ resource['references']|safe }}</p>
</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
<div class="right p-4 pr-8 lg:p-0 ">
<div class="grid lg:grid-cols-2">
{{ relationships_links(resource) }}
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endblock %}

+ 123
- 203
web/app/templates/resources.html Visa fil

@@ -1,210 +1,130 @@
{% extends 'base.html' %}

{% block content %}
<!-- <div class="cell-margin">
<div class="mb-8">
<h2>
{% block title %}
{% autoescape false %}
{{ type + 's' | replace(" ","<br>") }}
{% endautoescape %}
{% endblock %}
</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget viverra magna. Nam in ante ultricies
purus feugiat vestibulum et ac erat. Donec in sagittis ante. Maecenas non mauris et eros commodo fringilla.
Integer accumsan ullamcorper diam, non rhoncus tellus molestie ut. Maecenas finibus pretium dolor ac sagittis.
</p>
</div>
</div> -->

<div class="row">
<div class="col-12 text-center">
<h1>{% block title %}
{{ type|capitalize + 's' }}
{% endblock %}
</h1>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget viverra magna. Nam in ante ultricies purus feugiat vestibulum et ac erat. Donec in sagittis ante. Maecenas non mauris et eros commodo fringilla. Integer accumsan ullamcorper diam, non rhoncus tellus molestie ut. Maecenas finibus pretium dolor ac sagittis.
</p>
</div>

{% macro filter_dropdown(id, filter, plural='') %}
<select
name="{{ id }}"
>
<option
value=""
{% if request.args.get(id, '' )=='' %} selected {% endif %}
>
{% if plural != '' %}
{{ plural }}
{% else %}
<span class="capitalize bg-red-400">{{ id }}s</span>
{% endif%}
</option>
{% for thing in filter %}
<option
value="{{ thing[0] }}"
{% if request.args.get(id)==thing[0]|string %} selected {% endif %}
>
{{ thing[1] }}
</option>
{% endfor %}
</select>
{% endmacro%}

{% macro filter_dropdown_nokey(id, filter, plural='') %}
<select
name="{{ id }}"
>
<option
value=""
{% if request.args.get(id, '' ) == '' %} selected {% endif %}
>
{% if plural != '' %}
{{ plural }}
{% else %}
<span class="capitalize">{{ id }}s</span>
{% endif%}
</option>
{% for thing in filter %}
<option
value="{{ thing }}"
{% if request.args.get(id)==thing|string %} selected {% endif %}
>
{{ thing }}
</option>
{% endfor %}
</select>
{% endmacro%}

<div class="border-b-2 border-black grid lg:grid-cols-[52rem,30rem] content-start">
<div class="mx-2 lg:ml-[13rem] text my-8 meta lg:max-w-[30rem]">
{{ intro_text|safe }}
</div>
</div>
<div class="row">
<div class="col-md-4 col-lg-3 py-3">
<div class="search-filter-sidebar">
<input class="form-control" id="searchResources" type="text" placeholder="filter by search...">
<div class="row">
{% if practices_filter %}
<div class="col-sm-6 d-md-block d-sm-none d-none">
<div class="filter-title">Practices</div>
<ul class="filter-items">
{% for practice in practices_filter %}
<li {% if request.args.get('practice') == practice[0]|string %} class="fw-bold"{% endif %}>
<a href="/{{type + 's'}}?practice={{practice[0]}}">{{ practice[1] }}</a>
</li>
{% endfor %}
</ul>
</div>
<div class="d-md-none accordion">
<div class="accordion-item">
<div class="filter-title accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#filter-practice">Practices</div>
<div class="accordion-body">
<ul id="filter-practice" class="collapse filter-items">
{% for practice in practices_filter %}
<li {% if request.args.get('practice') == practice[0]|string %} class="fw-bold"{% endif %}>
<a href="/{{type + 's'}}?practice={{practice[0]}}">{{ practice[1] }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endif %}
{% if year_filter %}
<div class="col-sm-6 d-md-block d-sm-none d-none">
<div class="filter-title">Year</div>
<ul class="filter-items">
{% for year in year_filter %}
<li {% if request.args.get('year') == year %} class="fw-bold"{% endif %}>
<a href="/books?year={{year}}">{{ year }}</a>
</li>
{% endfor %}
</ul>
</div>
<div class="d-md-none accordion">
<div class="accordion-item">
<div class="filter-title accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#filter-year">Year</div>
<div class="accordion-body">
<ul id="filter-year" class="collapse filter-items">
{% for year in year_filter %}
<li {% if request.args.get('year') == year %} class="fw-bold"{% endif %}>
<a href="/books?year={{year}}">{{ year }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endif %}
</div>
<div class="row">
{% if typology_filter %}
<div class="col-sm-6 d-md-block d-sm-none d-none">
<div class="filter-title">Typology category</div>
<ul class="filter-items">
{% for typology in typology_filter %}
<li {% if request.args.get('typology') == typology %} class="fw-bold"{% endif %}>
<a href="/books?typology={{typology}}">{{ typology }}</a>
</li>
{% endfor %}
</ul>
</div>
<div class="d-md-none accordion">
<div class="accordion-item">
<div class="filter-title accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#filter-typology">Typology category</div>
<div class="accordion-body">
<ul id="filter-typology" class="collapse filter-items">
{% for typology in typology_filter %}
<li {% if request.args.get('typology') == typology %} class="fw-bold"{% endif %}>
<a href="/books?typology={{typology}}">{{ typology }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endif %}
{% if languages_filter %}
<div class="col-sm-6 d-md-block d-sm-none d-none">
<div class="filter-title">Scripting languages</div>
<ul class="filter-items">
{% for language in languages_filter %}
<li {% if request.args.get('scriptingLanguage') == language %} class="fw-bold"{% endif %}>
<a href="/tools?scriptingLanguage={{language}}">{{ language }}</a>
</li>
{% endfor %}
</ul>
</div>
<div class="d-md-none accordion">
<div class="accordion-item">
<div class="filter-title accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#filter-language">Scripting language</div>
<div class="accordion-body">
<ul id="filter-language" class="collapse filter-items">
{% for language in languages_filter %}
<li {% if request.args.get('scriptingLanguage') == language %} class="fw-bold"{% endif %}>
<a href="/tools?scriptingLanguage={{language}}">{{ language }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endif %}
{% if licenses_filter %}
<div class="col-sm-6 d-md-block d-sm-none d-none">
<div class="filter-title">License</div>
<ul class="filter-items">
{% for license in licenses_filter %}
<li {% if request.args.get('license') == license %} class="fw-bold"{% endif %}>
<a href="/tools?license={{license}}">{{ license }}</a>
</li>
{% endfor %}
</ul>
</div>
<div class="d-md-none accordion">
<div class="accordion-item">
<div class="filter-title accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#filter-license">License</div>
<div class="accordion-body">
<ul id="filter-license" class="collapse filter-items">
{% for license in licenses_filter %}
<li {% if request.args.get('license') == license %} class="fw-bold"{% endif %}>
<a href="/tools?license={{license}}">{{ license }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endif %}
</div>
</div>

<form
action="{{ url_for(request.endpoint )}}"
method="GET"

hx-trigger="change"
hx-push-url="true"

>
<input type="hidden" name="view" value="{{ view }}">
<div class="flex flex-wrap gap-4 items-center min-h-[2rem] mb-8 border-b-2 border-black ">
{{ view_switch() }}
{% if practices_filter%}
{{ filter_dropdown('practice', practices_filter, 'Practices') }}
{% endif %}
{% if year_filter %}
{{ filter_dropdown_nokey('year', year_filter, 'Year') }}
{% endif %}
{% if typology_filter %}
{{ filter_dropdown_nokey('typology', typology_filter, 'Typologies') }}
{% endif %}
{% if languages_filter %}
{{ filter_dropdown_nokey('scriptingLanguage', languages_filter, 'Scripting languages') }}
{% endif %}
{% if licenses_filter %}
{{ filter_dropdown_nokey('license', licenses_filter, 'Licenses') }}
{% endif %}
{% if status_filter %}
{{ filter_dropdown_nokey('status', status_filter, 'Maintenance status') }}
{% endif %}
<a href="{{ url_for(request.endpoint) }}?view={{ view }}">Reset</a>
</div>
<div class="col-md-8 col-lg-9">
<div id="resource-boxes" class="row">
{% for resource in resources %}
<div id="resource" class="col-md-4 col-sm-6 py-3">
<div class="card text-dark bg-{{type}} mb-3">
<div class="card-body">
{% if resource['type'] == 'tool' %}
<a href="{{ url_for('tool.show_tool', tool_id=resource['id']) }}">
<h3 class="card-title text-center text-dark">{{ resource['name'] }}</h3>
</a>
<p class="card-text">
{{ resource['description']|truncate(100) }}
</p>
{% elif resource['type'] == 'practice' %}
<a href="{{ url_for('practice.show_practice', practice_id=resource['id']) }}">
<h3 class="card-title text-center text-dark">{{ resource['name'] }}</h3>
</a>
<p class="card-text">
{{ resource['description']|truncate(100) }}
</p>
{% elif resource['type'] == 'book' %}
<a href="{{ url_for('book.show_book', book_id=resource['id']) }}">
<h3 class="card-title text-center text-dark">{{ resource['name'] }}</h3>
</a>
<p class="card-text text-center">
{{ resource['author']|truncate(100) }}
</p>
{% endif %}
{% if current_user.is_authenticated %}
{% if resource['type'] == 'tool' %}
<a href="{{ url_for('tool.edit_tool', tool_id=resource['id']) }}">
<span class="badge bg-dark">Edit</span>
</a>
{% elif resource['type'] == 'practice' %}
<a href="{{ url_for('practice.edit_practice', practice_id=resource['id']) }}">
<span class="badge bg-dark">Edit</span>
</a>
{% elif resource['type'] == 'book' %}
<a href="{{ url_for('book.edit_book', book_id=resource['id']) }}">
<span class="badge bg-dark">Edit</span>
</a>
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
<div>
{% if view == 'list' %}
{% for resource in resources %}
{{ resource_list(resource, loop) }}
{% endfor %}
{% else %}
{% for resource in resources %}
{{ resource_with_related(resource, loop) }}
{% endfor %}
{% endif %}
</div>
</div>
{% endblock %}

</form>

{% endblock %}

+ 12
- 0
web/app/templates/search.html Visa fil

@@ -0,0 +1,12 @@
{% extends 'base.html' %}

{% block content %}
<div class="grid lg:grid-cols-3">
{% for result in results %}
<div class="cell">
{{ resource_lead(result) }}
</div>
{% endfor %}
</div>

{% endblock %}

+ 4
- 34
web/app/templates/test.html Visa fil

@@ -1,41 +1,11 @@
{% extends 'base.html' %}

{% block content %}
<style>

.collapse_text {
padding: 0 18px;
background-color: #f1f1f1;
}
{{ resource.__dict__ }}

</style>

<h2>Collapsible text</h2>

<b>HackMD</b>'s online writing environment supports practices of <a data-bs-toggle="collapse" href="#collapse_editing" role="button" aria-expanded="false" aria-controls="collapse_editing">collaborative editing</a>, <a data-bs-toggle="collapse" href="#collapse_writing" role="button" aria-expanded="false" aria-controls="collapse_writing">collaborative writing</a>, and <a data-bs-toggle="collapse" href="#collapse_versioning" role="button" aria-expanded="false" aria-controls="collapse_versioning">versioning</a>.

<div class="collapse collapse_text" id="collapse_editing">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

<div class="collapse collapse_text" id="collapse_writing">
<p>Exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

<div class="collapse collapse_text" id="collapse_versioning">
<p>Versioning text goes here.</p>
</div>

<br><br>

<h2>Tooltips</h2>

<b>HackMD</b>'s online writing environment supports practices of <a href="#" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.">collaborative editing</a>, <a href="#" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.">collaborative writing</a>, and <a href="#" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Versioning text goes here.">versioning</a>.

<br><br>

<h2>Popover</h2>

<b>HackMD</b>'s online writing environment supports practices of <a href="#" data-bs-toggle="popover" title="collaborative editing description" data-bs-content="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.">collaborative editing</a>, <a href="#" data-bs-toggle="popover" title="collaborative writing description" data-bs-content="Exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.">collaborative writing</a>, and <a href="#" data-bs-toggle="popover" title="versioning description" data-bs-content="Versioning text goes here.">collaborative editing</a>versioning</a>.
{% for book in resource.books %}
{{ book.name }}
{% endfor %}

{% endblock %}

+ 38
- 22
web/app/tool.py Visa fil

@@ -12,43 +12,59 @@ from .models import Resource
from .resources import *
from .relationships import *
from . import db
from sqlalchemy import text
import os
from sqlalchemy.sql import func
import markdown

tool = Blueprint('tool', __name__)

# route for displaying all tools in database
@tool.route('/tools')
def get_tools():
type = 'tool'
tools = Resource.query.filter_by(type=type)
# get introductory paragraph Markdown
with open('content/tools.md', 'r') as f:
intro_text = f.read()
intro_text = markdown.markdown(intro_text)
view = request.args.get('view')
resource_type = 'tool'
tools_query = Resource.query.filter_by(type=resource_type).order_by(func.random())
for key in request.args.keys():
if key == 'practice':
query = 'SELECT Resource.* FROM Resource LEFT JOIN Relationship ON Resource.id=Relationship.first_resource_id WHERE Relationship.second_resource_id=' + request.args.get(key) + ' AND Resource.type="' + type + '";'
with db.engine.connect() as conn:
tools = conn.execute(text(query))
elif key == 'scriptingLanguage':
regex = request.args.get(key) + "$|" + request.args.get(key) + "\s\/"
tools = Resource.query.filter_by(type=type).filter(Resource.scriptingLanguage.regexp_match(regex))
else:
kwargs = {'type': type, key: request.args.get(key)}
tools = Resource.query.filter_by(**kwargs)
# get filters
if key != 'view':
if (key == 'practice' and request.args.get(key) != ''):
tools_1 = tools_query.join(Relationship, Relationship.first_resource_id == Resource.id, isouter=True).filter(Relationship.second_resource_id==request.args.get(key))
tools_2 = tools_query.join(Relationship, Relationship.second_resource_id == Resource.id, isouter=True).filter(Relationship.first_resource_id==request.args.get(key))
tools_query = tools_1.union(tools_2)
if (key == 'scriptingLanguage' and request.args.get(key) != ''):
regex = request.args.get(key) + "$|" + request.args.get(key) + "\s\/"
tools_query = tools_query.filter(Resource.scriptingLanguage.regexp_match(regex))
if ((key != 'practice' and key != 'scriptingLanguage') and request.args.get(key) != ''):
kwargs = {key: request.args.get(key)}
tools_query = tools_query.filter_by(**kwargs)
# finalise the query
tools = tools_query.all()
# get number of tools
count = len(tools)
# reorder tools by tools name
tools = sorted(tools, key=lambda d: d.__dict__['name'].lower())
if view != 'list':
# append relationships to each tool
append_relationships_multiple(tools)
# get values for filters
# practices
practices_filter = Resource.query.filter_by(type='practice').with_entities(Resource.id, Resource.name)
#FOR LATER: SELECT Resource.name, second.name FROM Resource LEFT JOIN Relationship ON Resource.id=Relationship.first_resource_id LEFT JOIN Resource second ON Relationship.second_resource_id=second.id;
practices_filter = Resource.query.filter_by(type='practice').with_entities(Resource.id, Resource.name).all()
# license
licenses_filter = get_filter_values('license', type)
licenses_filter = get_filter_values('license', resource_type)
# language
languages_filter = get_filter_values('scriptingLanguage', type)
return render_template('resources.html', resources=tools, type=type, practices_filter=practices_filter, licenses_filter=licenses_filter, languages_filter=languages_filter)
languages_filter = get_filter_values('scriptingLanguage', resource_type)
# status
status_filter = get_filter_values('status', resource_type)
return render_template('resources.html', resources=tools, type=resource_type, practices_filter=practices_filter, licenses_filter=licenses_filter, languages_filter=languages_filter, status_filter=status_filter, count=count, view=view, intro_text=intro_text)

# route for displaying a single tool based on the ID in the database
@tool.route('/tools/<int:tool_id>')
def show_tool(tool_id):
tool = get_resource(tool_id)
relationships = get_relationships(tool_id)
return render_template('resource.html', resource=tool, relationships=relationships)
tool = get_full_resource(tool_id)
return render_template('resource.html', resource=tool)

# route for editing a single tool based on the ID in the database
@tool.route('/tools/<int:tool_id>/edit', methods=('GET', 'POST'))

+ 11
- 13
web/content/about.md Visa fil

@@ -1,27 +1,25 @@
## Books Contain Multitudes: A Scholarly Compendium for Experimental Book Publishing.
## Books Contain Multitudes: A Scholarly Compendium for Experimental Book Publishing.

Scholars, publishers, infrastructure providers, developers, and librarians, are increasingly experimenting with alternative tools, practices, and formats for scholarly publishing. This field of work has been growing with the emergence of digital and open access publishing, yet those who want to experiment with the forms, practices, and systems of academic publishing still struggle to find appropriate publishers, tools, examples, and communities that can support their ventures. Others might simply become disillusioned, put off perhaps by the apparent disconnect between the desire to experiment with forms, media and formats of scholarly books and the unfamiliarity amongst scholars and publishers with the tools, technologies, and publishing and funding opportunities needed to do so. Furthermore, monograph publishing in the humanities has been embedded in an individual and competitive academic culture that, as Samuel Moore (2019) underlines, has a strong ordering effect on both academics and the way in which they publish: “projects are undertaken with specific publication formats in mind; journal choice is frequently determined by how well regarded they are by assessment panels; and there is an informal hierarchy of certain kinds of academic publication, from the monograph at the top down to co-authored works and book chapters in edited volumes towards the bottom” (41).
Scholars, publishers, infrastructure providers, developers, and librarians, are increasingly experimenting with alternative tools, practices, and formats for scholarly publishing. This field of work has been growing with the emergence of digital and open access publishing, yet those who want to experiment with the forms, practices, and systems of academic publishing still struggle to find appropriate publishers, tools, examples, and communities that can support their ventures. Others might simply become disillusioned, put off perhaps by the apparent disconnect between the desire to experiment with the forms, media, and formats of scholarly books and the unfamiliarity amongst scholars and publishers with the tools, technologies, and funding opportunities needed to do so. Furthermore, monograph publishing in the humanities has been embedded in an individual and competitive academic culture that, as Samuel Moore (2019) underlines, has a strong ordering effect on both academics and the way in which they publish: “projects are undertaken with specific publication formats in mind; journal choice is frequently determined by how well regarded they are by assessment panels; and there is an informal hierarchy of certain kinds of academic publication, from the monograph at the top down to co-authored works and book chapters in edited volumes towards the bottom” (41).

Having worked on experimental book publishing as part of a community of researchers, authors, developers, editors, librarians and publishers collaborating in the framework of the COPIM project, we compiled this compendium to address this gap. The compendium is based on and expands from two reports we developed within COPIM's Experimental Publishing Group. We want the compendium to act as a guide and reference for both experienced practitioners and those just setting out to experiment with the form, content, and practices of scholarly bookmaking. The compendium includes experiments with the form and format of the scholarly book; with the various (multi)media we can publish long-form research in; and with how people produce, disseminate, consume, review, reuse, interact with and form communities around books. Far from being merely a formal exercise, **experimental publishing as we conceive it here also reimagines the relationalities that constitute scholarly writing, research, and publishing.** Books, after all, validate what counts as research and materialise how scholarly knowledge production is organised. The linked entries in this compendium may inspire speculations on the future of the book and the humanities more in general and encourage publishers and authors to explore publications beyond the standard printed codex format.
Having worked on experimental book publishing as part of a community of researchers, authors, developers, editors, librarians and publishers collaborating in the framework of COPIM — the Community-led Open Publication Infrastructures for Monographs project, we compiled this compendium to address this gap. The compendium is based on and expands from [two](https://copim.pubpub.org/books-contain-multitudes-exploring-experimental-publishing-2022-update) [reports](https://copim.pubpub.org/promoting-and-nurturing-interactions-with-open-access-books-strategies-for-publishers-and-authors) we developed within COPIM's Experimental Publishing Group. We want the compendium to act as a guide and reference for both experienced practitioners and those just setting out to experiment with the forms, content, and practices of scholarly bookmaking. The compendium includes experiments with the form and format of the scholarly book; with the various (multi)media we can publish long-form research in; and with how people produce, disseminate, consume, review, reuse, interact with and form communities around books. Far from being merely a formal exercise, **experimental publishing as we conceive it here also reimagines the relationalities that constitute scholarly writing, research, and publishing**. Books, after all, validate what counts as research and materialise how scholarly knowledge production is organised. The linked entries in this compendium may inspire speculations on the future of the book and the humanities more in general and encourage publishers and authors to explore publications beyond the standard printed codex format.

This compendium has been compiled by Janneke Adema, Simon Bowie, Gary Hall, Rebekka Kiesewetter, Julien McHardy, and Tobias Steiner. Having experimented with books for many years in various constellations we came together in COPIM’s Experimental Publishing Group for a period of three-and-a-half-years. During this time we collaborated with authors, tool and platform providers, designers, publishers, librarians, and developers, working together to create a number of pilot projects and research reports. This compendium gathers insights from the process of producing these resources, reflecting on our practice and that of our collaborators. Given that experimental publishing is continually-emergent and diverse, and that our understanding will always be partial, contradictory, and situated we hope that this compendium will evolve with those who interact with it, with the communities we created around our publishing experiments, and the communities that are still to come. In this introduction, we are sharing how this compendium came about and how we hope to further develop it, amongst others by inviting amendments by all who carry it forward by interacting with it, and by ongoing maintenance and (re-)use.

### Books are Political

Our starting premise is that scholarly bookmaking is political (Adema and Hall, 2015). Far from coming at the end of academic work, books contain multitudes, manifesting the entire field of scholarly knowledge production. How research is conceptualised, funded, conducted, analysed, articulated, valued and shared affects the shape and form of academic books. Experimenting with academic books, in turn, is to experiment with how academic labour and research are organised. From this starting point, we propose that experimenting with academic books is never just a formal exercise. Publishing is *one* site where scholars might intervene and reconfigure the knowledge production process. The idea that seemingly technical practices and infrastructures are political is a core insight of Science and Technology Studies (STS). Still, until recently, the critical study of knowledge infrastructures has long refrained from reflecting on our own research practices, to look inward to explore how academic publishing infrastructures shape academic realities. Many scholar-led publishing intitiatives take up this challenge. Involving scholars in the critical remaking of academic publishing emphasises that publishing is neither merely a service nor an end product, but an integral part of how research gets done. There cannot be *one* uniform 'best' way to organise academic publishing. Acknowledging - together with e.g. Haraway (1988) and Chan et al. (2019) - the contextual and situated nature of knowledge production, and moving away from notions of 'best' practice, we recognise that different research endeavours and academic communities might require a variety of forms of, and approaches to publishing, which is where COPIM — the Community-led Open Publication Infrastructures for Monographs project — comes in.
Our starting premise is that scholarly book publishing is political (Adema and Hall, 2015). Far from coming at the end of academic work, books contain multitudes, manifesting the entire field of scholarly knowledge production. How research is conceptualised, funded, conducted, analysed, articulated, valued, and shared affects the shape and form of academic books. Experimenting with academic books, in turn, is to experiment with how academic labour and research are organised. From this starting point, we propose that experimenting with academic books is never just a formal exercise. Publishing is *one* site where scholars might intervene and reconfigure the knowledge production process. The idea that seemingly technical practices and infrastructures are political is a core insight of Science and Technology Studies (STS). Still, until recently, the critical study of knowledge infrastructures has long refrained from reflecting on our own research practices, to look inward to explore how academic publishing infrastructures shape academic realities. Many scholar-led publishing initiatives have taken up this challenge, and have often stood at the vanguard of more experimental forms of publishing (Adema and Stone, 2017). Being involved as scholars in the critical remaking of academic publishing emphasises that publishing is neither merely a service nor an end product, but an integral part of how research gets done. There cannot be *one* uniform 'best' way to organise academic publishing. Acknowledging - together with e.g. Haraway (1988) and Chan et al. (2019) - the contextual and situated nature of knowledge production, and moving away from notions of 'best' practice, we recognise that different research endeavours and academic communities might require a variety of forms of, and approaches to publishing, which is where the COPIM project has come in.

As commercial consolidation continues to threaten to monopolise and homogenise the emerging scholarly Open Access publishing landscape, COPIM's mission was to gather researchers, publishers, libraries and infrastructure providers to develop community-owned ***alternatives to established infrastructure*** that can support and scale a diverse publishing landscapes. Open *infrastructures*, we proposed, can provide an alternative to platform capitalism that extracts value by monopolising access. Under the banner of Scaling Small, COPIM worked towards a diverse publishing landscape prioritising community ownership, collective production and governance, scholar-led publishing, and sharing resources and open infrastructures amongst various institutions guided by the project's core value of Bibliophilia (love and care for books). COPIM’s work packages were largely dedicated to serious infrastructure building, except for the experimental publishing group, which grants the question of how experimental publishing contributes to the ambition to establish infrastructures that allow diverse *small* initiatives to proliferate at *scale*?
As commercial consolidation continues to threaten to monopolise and homogenise the scholarly open access publishing landscape, COPIM's mission was to gather researchers, publishers, libraries, and infrastructure providers to develop community-owned ***alternatives to established infrastructure*** that can support and scale a diverse publishing landscapes. Open *infrastructures*, we proposed, can provide an alternative to platform capitalism that extracts value by monopolising access. Under the banner of *scaling small* (Adema and Moore, 2021), COPIM worked towards a diverse publishing landscape prioritising community ownership, collective production and governance, scholar-led publishing, and sharing resources and open infrastructures amongst various institutions guided by the project's core value of [bibliophilia](https://www.copim.ac.uk/about-us/mission/) (love and care for books). COPIM’s work packages were largely dedicated to serious infrastructure building. The experimental publishing group, in this context, brought together publishers, technologists, researchers, and designers to devise strategies to promote experimental book publishing and the reuse of, and engagement with, open access books. We have been examining ways to align existing open source software, tools, and workflows for experimental publishing with the workflow of open access book publishers, as well as with the infrastructures for scholar-led OA publishing that COPIM has created.

The image of diverse publishing landscapes or interrelated publishing ecosystems helped position COPIM’s work. These ecological metaphors evoke publishing as a relational enterprise, more or less explicitly valuing diversity over monoculture. Staying with metaphors of lively, specific and abundant interdependences allows us to sketch our understanding of experimental publishing’s place in scholarly knowledge production. Speaking of publishing ecologies reiterates our point that scholarly publishing cannot be separated from how it is funded, conceptualised, written, valued, reviewed, rewarded, read and taught. Scholarly publishing ecologies reflect and materialise the broader scholarly landscape. In this ecological view, scholarly books are not containers of knowledge but relational nodes that materialise what does and doesn’t count as valuable practices, sites, labour, and subjects of knowledge. In this metaphor, scholarly works, like all specimens, coevolve with the environment they inhabit and shape. In contrast to ecological metaphors, where knowledge circulates in diverse, partially connected patterns, schematic understandings of academic knowledge production frequently liken the flow of knowledge to managed water. For example, grant applications and project timetables tend to imagine scholarly publishing at the end of a pipeline. How institutions such as libraries, universities, publishers, funders, and intellectual property regimes organise knowledge production tends to reinforce the notion of a manageable flow from funding to research question to investigation to publication to evaluation to more funding. The metaphor of channelled flow and the premises of contained stages provides structure. The notion that valuable knowledge may be channeled, gives publishing a place and a form: the book coming at the end of the pipe. But, you see it coming, where there are pipes, there are blockages and spillage. Coming back to experimental publishing and ecological metaphors of flow, new forms of publication might streamline or spill over into relational circulations that have more in common with moors, swamps, and wetlands than pipes. Either way, we posit that experimental publishing is one of the sites where the shape of scholarly landscapes, and their relationship to other ecologies of knowledge and power is negotiated and materialised in practice. How we *do* publish matters. Experimenting with scholarly books is to experiment with scholarly modes of knowledge production. This labour, like other experimental practice, takes place at the growing edges and in the cracks of established practices, where by steady erosion, underground commotion or capital-intense incubation forms of writing, making, sharing, reviewing, discovering, reading and cataloging books come into being that will question, and occasionally change what counts as scholarly work.
The vision of a diverse publishing landscape or an interrelated publishing ecosystem has helped position COPIM’s work. These ecological metaphors evoke publishing as a relational enterprise, more or less explicitly valuing diversity over monoculture. Staying with metaphors of lively, specific, and abundant interdependences allows us to sketch our understanding of experimental publishing’s place in scholarly knowledge production. Speaking of publishing ecologies reiterates our point that scholarly publishing cannot be separated from how it is funded, conceptualised, written, valued, reviewed, rewarded, read and taught. Scholarly publishing ecologies reflect and materialise the broader scholarly landscape. In this ecological view, scholarly books are not containers of knowledge but relational nodes that materialise what does and doesn’t count as valuable practices, sites, labour, and subjects of knowledge. In this metaphor, scholarly works, like all specimens, coevolve with the environment they inhabit and shape. In contrast to ecological metaphors, where knowledge circulates in diverse, partially connected patterns, schematic understandings of academic knowledge production frequently liken the flow of knowledge to managed water. For example, grant applications and project timetables tend to imagine scholarly publishing at the end of a pipeline. How institutions such as libraries, universities, publishers, funders, and intellectual property regimes organise knowledge production tends to reinforce the notion of a manageable flow from funding to research question to investigation to publication to evaluation to more funding. The metaphor of channelled flow and the premises of contained stages provides structure. The notion that valuable knowledge may be channelled, gives publishing a place and a form: the book coming at the end of the pipe-line. But where there are pipes, there are blockages and spillage. Coming back to experimental publishing and ecological metaphors of flow, new forms of publication might streamline or spill over into relational circulations that have more in common with moors, swamps, and wetlands than pipes. Either way, we posit that experimental publishing is one of the sites where the shape of scholarly landscapes, and their relationship to other ecologies of knowledge and power is negotiated and materialised in practice. How we *do* publishing matters. Experimenting with scholarly books is to experiment with scholarly modes of knowledge production. This labour, like other experimental practices, takes place at the growing edges and in the cracks of established practices, where by steady erosion, underground commotion, or capital-intense incubation, forms of writing, making, sharing, reviewing, discovering, reading and cataloguing books come into being that question, and occasionally change what counts as scholarly work.

The compendium gathers and links tools, examples and practices with a focus on free and open-source software, platforms and digital publishing tools that presses and authors can either use freely and/or further adapt themselves to their workflows. The compendium also proposes a typology of experimental publishing. This typology is a starting point for exploration, rather than a fixed classification. It doesn’t provide clear definitions of the various experiments undertaken within scholarly book publishing—if only because many of the examples will defy categories. This mapping provides a snapshot, a temporary overview and analysis, one that will hopefully be updated, revised, and reused in different contexts. In this respect analysing experimental publishing—perhaps more than established forms of publishing—requires a continuous re-mapping due to the nature of its speculative and emergent form, where any map will need to be repeatedly redrawn if we want to analyse experimental publishing’s material-discursive practices. At the same time, we are aware of the performative character of our analyses (i.e., how any classification we suggest will provide further authority and weight to that classification), which will inherently be a factor in the stabilising, fixing, and freezing of these practices and knowledge relations, including as part of the mapping or typology that we provide here.
The compendium gathers and links tools, examples, and practices with a focus on free and open-source software, platforms, and digital publishing tools that presses and authors can either use freely and/or further adapt themselves to their workflows. The compendium also proposes a typology of experimental books. This typology is a starting point for exploration, rather than a fixed classification. It doesn’t provide any clear-cut definitions of the various experiments undertaken within scholarly book publishing—if only because many of the examples will defy categorisation. This mapping provides a snapshot, a temporary overview and analysis, one that will hopefully be updated, revised, and reused in different contexts. In this respect analysing experimental publishing—perhaps more than established forms of publishing—requires a continuous re-mapping due to the nature of its speculative and emergent form, where any map will need to be repeatedly redrawn if we want to analyse experimental publishing’s material-discursive practices. At the same time, we are aware of the performative character of our analyses (i.e., how any classification we suggest will provide further authority and weight to that classification), which will inherently be a factor in the stabilising, fixing, and freezing of these practices and knowledge relations, including as part of the mapping or typology that we provide here.

By keeping this mapping open, both for updates and further uptake by the community, we hope we can prevent a too stringently fixing-down of the speculative character of these experiments, where instead we want to emphasise that its political nature lies in the book continuing ‘to be able to serve ‘‘new ends’’ as a medium through which politics itself can be rethought’ (Adema and Hall 2013; Drucker, 2004). Indeed, experimental publishing can be seen as an attempt to keep ‘open the politics of knowledge and communication in a context where these are being closed down’. Following this line of thinking, instead of defining what makes an experimental book or what constitutes experimental publishing, we position it here in relation to certain practices and contexts instead. For example, what becomes clear when examining experimental book publishing within academia is that it is (historically) positioned across three interconnected discourses: the codex format, digital publishing, and openness.
By keeping this mapping open, both for updates and further uptake by the community, we hope we can prevent a too stringently fixing-down of the speculative character of these experiments, where instead we want to emphasise that its political nature lies in the book continuing ‘to be able to serve ‘‘new ends’’ as a medium through which politics itself can be rethought’ (Adema and Hall 2013; Drucker, 2004). Indeed, experimental publishing can be seen as an attempt to keep ‘open the politics of knowledge and communication in a context where these are being closed down’. Following this line of thinking, instead of defining what makes an experimental book or what constitutes experimental publishing, we position it here in relation to certain practices and contexts instead, as we have tried to do here in this compendium.

As outlined above, experimental forms and practices of publishing open up and explore questions around modalities, linearity, workflow, and the relationalities of publishing; they examine established practices that many scholarly communities have taken for granted and repeated within conventional forms of publishing—where they have become solidified in standard print- and codex-based publishing forms and practices. This especially concerns discussions about what constitutes a publication, or at what point scholarship is formally ‘published’ (the current consensus is that a book is published once it is peer reviewed and published by a reputable press). Unlike Elliott and others, who argue for clear definitions to distinguish experimental publishing, we resist clear definitions, simply because the form and value of experimentation is research-, field- and discourse-specific. Fixed definitions risks closing down conversations that different scholarly communities have to determine their own (what we hope are contingent and continuously reviewed) understandings of what constitutes a book or a publication. In this compendium, we speak of experimental publishing and experimental books to refer to a mode of publishing that might also be called multimodal, screen-based, or interactive. We prefer experimental publishing because it can encompass multi-modal, interactive, and screen-based works, without restricting which media forms or practices count as experimental. Experimental publishing thus invites discussion about the form and intend of publishing experiments.
As outlined above, experimental forms and practices of publishing open up and explore questions around modalities, linearity, workflow, and the relationalities of publishing; they examine established practices that many scholarly communities have taken for granted and repeated within conventional forms of publishing—where they have become solidified in standard print- and codex-based publishing forms and practices. This especially concerns discussions about what constitutes a publication, or at what point scholarship is formally ‘published’ (the current consensus being that a book is published once it is peer reviewed and published by a reputable press). We resist clear definitions of what constitutes an (experimental) book, simply because the form and value of experimentation is research-, field- and discourse-specific. Fixed definitions risks closing down conversations that different scholarly communities have to determine their own (what we hope are contingent and continuously reviewed) understandings of what constitutes a book or a publication. In this compendium, we speak of experimental publishing and experimental books to refer to a mode of publishing that might also be called multimodal, screen-based, or interactive. We prefer experimental publishing because it can encompass multi-modal, interactive, and screen-based works, without restricting which media forms or practices count as experimental. Experimental publishing thus invites discussion about the form and intend of publishing experiments.

We, to collapse our editorial politics for a minute, favour the technofeminist dreamscape of biodiverse swamps and moors where more collective, inclusive, embodied, situated and caring modes of knowledge production may emerge. Pushing for diverse publishing landscapes, we recognise that this dream is limited and particular. The notion that changes in publishing effect the entire scholarly landscape applies equally to pipe dreams of scholarly efficiency and to ways of reimagining scholarly publishing that lies in our blindspot, beyond our horizon. While we know where we stand, we hope that this compendium is useful for all who are working to expand scholarly publishing, working to promote and give visibility to the rich and diverse forms of digital scholarship, and multimodal and interactive research out there and inspire ongoing experimentation. The compendium will be funded for X years by X. During this time at least the compendium will remain under active development. Our hope is that it will help build relationships between software and tool providers, publishers, librarians, researchers, and authors, working with and strengthening communities of expertise around experimental books.
We, to collapse our editorial politics for a minute, favour the technofeminist visions of biodiverse swamps and moors where more collective, inclusive, embodied, situated and caring modes of knowledge production may emerge. Pushing for diverse publishing landscapes, we recognise that this vision remains limited and particular. The notion that changes in publishing effect the entire scholarly landscape applies equally to pipe dreams of scholarly efficiency and to ways of reimagining scholarly publishing that lies in our blindspot, beyond our horizon. While we know where we stand, we hope that this compendium is useful for all who are working to expand scholarly publishing, to promote and give visibility to the rich and diverse forms of digital scholarship, and all the multimodal and interactive research out there, to inspire ongoing experimentation. The compendium will be released in a beta form in first instance and will remain under active development for the next few years, due to generous funding from the [Research England Development (RED) Fund](https://re.ukri.org/funding/our-funds-overview/research-england-development-red-fund/) and [Arcadia](https://www.arcadiafund.org.uk/), a charitable fund of Lisbet Rausing and Peter Baldwin. Our hope is that it will help build relationships between software and tool providers, publishers, librarians, researchers, and authors, working with and strengthening communities of expertise around experimental books. But we also want to expand, strengthen, and maintain the compendium together with this community to both govern and oversee it.

If you are a publisher, developer, initiative, library, institution or author wanting to contribute to the further development of the compendium please get in touch with [compendium@openbookcollective.org](mailto:compendium@openbookcollective.org).

The compendium grew out of the Books Contain Multitudes: Exploring Experimental Publishing Report. (2022 update). Community-Led Open Publication Infrastructures for Monographs (COPIM). [https://doi.org/10.21428/785a6451.1792b84f](https://doi.org/10.21428/785a6451.1792b84f) & [https://doi.org/10.5281/zenodo.6545475](https://doi.org/10.5281/zenodo.6545475).
**If you are a scholar, publisher, developer, library, institution or designer wanting to contribute to the further development of the compendium please get in touch with [julien@matteringpress.org](mailto:julien@matteringpress.org).**

+ 1
- 0
web/content/books.md Visa fil

@@ -0,0 +1 @@
Experimental books undo, critique, reinvent, and expand the processes and norms of scholarly publishing.

+ 29
- 0
web/content/colophon.md Visa fil

@@ -0,0 +1,29 @@
## Beta 1.0 (2023)

Version 1.0 has been curated by Janneke Adema, Julien McHardy, and Simon Bowie. Future versions will be overseen, curated, and maintained by an Editorial Board (members TBC).

Back-end coding by [Simon Bowie](https://simonxix.com), front-end coding by [Joel Galvez](https://www.joelgalvez.com/), design by Joel Galvez & Martina Vanini.

Special thanks to Gary Hall, Rebekka Kiesewetter, Marcell Mars, Toby Steiner, and Samuel Moore, and everyone who has provided feedback on our research or shared suggestions of examples to feature, including the participants of COPIM’s experimental publishing workshop, and Nicolás Arata, Dominique Babini, Maria Fernanda Pampin, Sebastian Nordhoff, Abel Packer, and Armanda Ramalho, and Agatha Morka.

Our appreciation also goes out to the [Next Generation Library Publishing Project](https://educopia.org/next-generation-library-publishing/) for sharing an early catalogue-in-progress version of [SComCat](https://www.scomcat.net/) with us, which formed one of the inspirations behind the Compendium.
___

Copyright © 2023 [COPIM](https://copim.ac.uk/).

Design © 2023 [Joel Galvez](https://joelgalvez.com/selected).

Licensed under a [Creative Commons Attribution 4.0 International License (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/).

All source code is available on GitHub at [https://github.com/COPIM/expub_compendium](https://github.com/COPIM/expub_compendium) under an [MIT License](https://github.com/COPIM/expub_compendium/blob/main/LICENSE).
___

The compendium grew out of the following two reports:

Adema, J., Bowie, S., Mars, M., and T. Steiner (2022) *Books Contain Multitudes: Exploring Experimental Publishing (2022 update)*. Community-Led Open Publication Infrastructures for Monographs (COPIM). doi: [10.21428/785a6451.1792b84f](https://doi.org/10.21428/785a6451.1792b84f) & [10.5281/zenodo.6545475](https://doi.org/10.5281/zenodo.6545475).

Adema, J., Moore, S., and T. Steiner (2021) *Promoting and Nurturing Interactions with Open Access Books: Strategies for Publishers and Authors*. Community-Led Open Publication Infrastructures for Monographs (COPIM). doi: [10.21428/785a6451.2d6f4263](https://doi.org/10.21428/785a6451.2d6f4263) and [10.5281/zenodo.5572413](https://doi.org/10.5281/zenodo.5572413)

___

COPIM and the Experimental Publishing Compendium are supported by the [Research England Development (RED) Fund](https://re.ukri.org/funding/our-funds-overview/research-england-development-red-fund/) and by [Arcadia](https://www.arcadiafund.org.uk), a charitable fund of Lisbet Rausing and Peter Baldwin.

+ 1
- 15
web/content/home.md Visa fil

@@ -1,15 +1 @@
# ExPub Compendium

The Experimental Publishing Compendium is for authors, designers, publishers, institutions and technologist who challenge, push and redefine the shape, form and rationale of scholarly works. The compendium offers a catalogues of tools, practices, and books to inspire experimental scholarly works.

## How to use the compendium

The compendium catalogues potential ingredients for the making of experimental publications.

- Under [Tools](/tools) you’ll find mostly software that supports experimental publication from collective writing to the annotation and remix of published texts.
- [Practices](/practices) provide inspiration for experimental book making.
- [Books](/books) provides examples of experimental books and those who publish them.

Each item is cross linked so that a practice will take you to relevant tools or examples and vice versa.

The selection is tentative, reflecting the knowledge and biases of the contributors of current and previous versions. If you want to submit or edit anything in the catalogue please do so by doing XXthisXXX.
The Experimental Publishing Compendium is a guide and reference for scholars, publishers, developers, librarians, and designers who want to challenge, push and redefine the shape, form and rationale of scholarly books. The compendium brings together tools, practices, and books to promote the publication of experimental scholarly works. [Read more](/about)

+ 1
- 0
web/content/practices.md Visa fil

@@ -0,0 +1 @@
Sorting the compendium by practice (annotating, collaborative writing, forking, remixing, reviewing, translating, versioning) provides inspiration on how-to make experimental books.

+ 1
- 0
web/content/tools.md Visa fil

@@ -0,0 +1 @@
Open source software tools and platforms enable experimentation with scholarly books: changing how people, collate, write, assemble, review, publish, share, reuse, and read long-form texts.

+ 2
- 1
web/requirements.txt Visa fil

@@ -8,4 +8,5 @@ markdown
isbntools
requests
marshmallow
flask-marshmallow
flask-marshmallow
pandas

Laddar…
Avbryt
Spara