Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 34195e7
Showing
10 changed files
with
405 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
name: Publish Python Package | ||
|
||
on: | ||
release: | ||
types: [created] | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
python-version: [3.6, 3.7, 3.8, 3.9] | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Set up Python ${{ matrix.python-version }} | ||
uses: actions/setup-python@v2 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
- uses: actions/cache@v2 | ||
name: Configure pip caching | ||
with: | ||
path: ~/.cache/pip | ||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} | ||
restore-keys: | | ||
${{ runner.os }}-pip- | ||
- name: Install dependencies | ||
run: | | ||
pip install -e '.[test]' | ||
- name: Run tests | ||
run: | | ||
pytest | ||
deploy: | ||
runs-on: ubuntu-latest | ||
needs: [test] | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Set up Python | ||
uses: actions/setup-python@v2 | ||
with: | ||
python-version: '3.9' | ||
- uses: actions/cache@v2 | ||
name: Configure pip caching | ||
with: | ||
path: ~/.cache/pip | ||
key: ${{ runner.os }}-publish-pip-${{ hashFiles('**/setup.py') }} | ||
restore-keys: | | ||
${{ runner.os }}-publish-pip- | ||
- name: Install dependencies | ||
run: | | ||
pip install setuptools wheel twine | ||
- name: Publish | ||
env: | ||
TWINE_USERNAME: __token__ | ||
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} | ||
run: | | ||
python setup.py sdist bdist_wheel | ||
twine upload dist/* | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
name: Test | ||
|
||
on: [push] | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
python-version: [3.6, 3.7, 3.8, 3.9] | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Set up Python ${{ matrix.python-version }} | ||
uses: actions/setup-python@v2 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
- uses: actions/cache@v2 | ||
name: Configure pip caching | ||
with: | ||
path: ~/.cache/pip | ||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} | ||
restore-keys: | | ||
${{ runner.os }}-pip- | ||
- name: Install dependencies | ||
run: | | ||
pip install -e '.[test]' | ||
- name: Run tests | ||
run: | | ||
pytest | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.venv | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
venv | ||
.eggs | ||
.pytest_cache | ||
*.egg-info | ||
.DS_Store | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# datasette-notebook | ||
|
||
[![PyPI](https://img.shields.io/pypi/v/datasette-notebook.svg)](https://pypi.org/project/datasette-notebook/) | ||
[![Changelog](https://img.shields.io/github/v/release/simonw/datasette-notebook?include_prereleases&label=changelog)](https://github.com/simonw/datasette-notebook/releases) | ||
[![Tests](https://github.com/simonw/datasette-notebook/workflows/Test/badge.svg)](https://github.com/simonw/datasette-notebook/actions?query=workflow%3ATest) | ||
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-notebook/blob/main/LICENSE) | ||
|
||
A markdown wiki and dashboarding system for Datasette | ||
|
||
This is an **experimental alpha** and everything about it is likely to change. | ||
|
||
## Installation | ||
|
||
Install this plugin in the same environment as Datasette. | ||
|
||
$ datasette install datasette-notebook | ||
|
||
## Usage | ||
|
||
With this plugin you must either run Datasette with a file called `notebook.db`: | ||
|
||
datasette notebook.db --create | ||
|
||
Here the `--create` option will create that file if it does not yet exist. | ||
|
||
Or you can use some other file name and configure that using `metadata.yml`: | ||
|
||
```yaml | ||
plugins: | ||
datasette-notebook: | ||
database: otherfile | ||
``` | ||
Then run Datasette with `otherfile.db`. | ||
|
||
Visit `/n` to create an index page. Visit `/n/name` to create a page with that name. | ||
|
||
## Development | ||
|
||
To set up this plugin locally, first checkout the code. Then create a new virtual environment: | ||
|
||
cd datasette-notebook | ||
python3 -mvenv venv | ||
source venv/bin/activate | ||
|
||
Or if you are using `pipenv`: | ||
|
||
pipenv shell | ||
|
||
Now install the dependencies and test dependencies: | ||
|
||
pip install -e '.[test]' | ||
|
||
To run the tests: | ||
|
||
pytest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
from datasette.utils.asgi import Response | ||
from datasette import hookimpl | ||
import sqlite_utils | ||
from .utils import render_markdown | ||
|
||
|
||
@hookimpl | ||
def startup(datasette): | ||
# Create tables for notebook DB if needed | ||
db_name = config_notebook(datasette) | ||
assert ( | ||
db_name in datasette.databases | ||
), "datasette-notebook needs a '{}' database to start".format(db_name) | ||
|
||
def create_tables(conn): | ||
db = sqlite_utils.Database(conn) | ||
if not db["pages"].exists(): | ||
db["pages"].create( | ||
{ | ||
"slug": str, | ||
"content": str, | ||
}, | ||
pk="slug", | ||
) | ||
|
||
async def inner(): | ||
await datasette.get_database(db_name).execute_write_fn( | ||
create_tables, block=True | ||
) | ||
|
||
return inner | ||
|
||
|
||
async def notebook(request, datasette): | ||
slug = request.url_vars.get("slug") or "" | ||
db_name = config_notebook(datasette) | ||
db = datasette.get_database(db_name) | ||
|
||
if request.method == "POST": | ||
vars = await request.post_vars() | ||
content = vars.get("content") | ||
if content: | ||
await db.execute_write( | ||
"INSERT OR REPLACE INTO pages (slug, content) VALUES(?, ?)", | ||
[slug, content], | ||
block=True, | ||
) | ||
return Response.redirect(request.path) | ||
else: | ||
return Response.html("content= is required", status=400) | ||
|
||
row = (await db.execute("select * from pages where slug = ?", [slug])).first() | ||
if row is None: | ||
# Form to create a page | ||
return Response.html( | ||
await datasette.render_template( | ||
"datasette_notebook/edit.html", | ||
{ | ||
"slug": slug, | ||
}, | ||
request=request, | ||
) | ||
) | ||
|
||
if slug == "": | ||
children = await db.execute("select * from pages where slug != ''") | ||
else: | ||
children = await db.execute( | ||
"select * from pages where slug like ?", ["{}/%".format(slug)] | ||
) | ||
|
||
return Response.html( | ||
await datasette.render_template( | ||
"datasette_notebook/view.html", | ||
{ | ||
"slug": slug, | ||
"content": row["content"], | ||
"rendered": render_markdown(row["content"]), | ||
"children": children.rows, | ||
}, | ||
request=request, | ||
) | ||
) | ||
|
||
|
||
@hookimpl | ||
def register_routes(): | ||
return [(r"^/n$", notebook), (r"^/n/(?P<slug>.*)$", notebook)] | ||
|
||
|
||
def config_notebook(datasette): | ||
config = datasette.plugin_config("datasette-notebook") or {} | ||
return config.get("database") or "notebook" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{% extends "base.html" %} | ||
|
||
{% block title %}Create page: {{ slug }}{% endblock %} | ||
|
||
{% block content %} | ||
<h1>Create page: {{ slug }}</h1> | ||
|
||
<form action="" method="POST"> | ||
<p> | ||
<input type="hidden" name="csrftoken" value="{{ csrftoken() }}"> | ||
<textarea style="width: 90%; height: 20em" name="content"></textarea> | ||
</p> | ||
<p><input type="submit" value="Save"></p> | ||
</form> | ||
|
||
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{% extends "base.html" %} | ||
|
||
{% block title %}{{ slug }}{% endblock %} | ||
|
||
{% block extra_head %} | ||
<style> | ||
.markdown ul { | ||
margin-bottom: 0.8rem; | ||
padding-left: 1.25rem; | ||
} | ||
.markdown ul li { | ||
list-style-type: disc; | ||
} | ||
summary { | ||
cursor: pointer; | ||
} | ||
</style> | ||
{% endblock %} | ||
|
||
{% block content %} | ||
<h1>{{ slug or "Index" }}</h1> | ||
<div class="markdown"> | ||
{{ rendered }} | ||
</div> | ||
|
||
{% if children %} | ||
<ul class="bullets"> | ||
{% for child in children %} | ||
<li><a href="/n/{{ child.slug }}">{{ child.slug }}</a></li> | ||
{% endfor %} | ||
</ul> | ||
{% endif %} | ||
|
||
<details style="margin-top: 2em"><summary>Edit this page</summary> | ||
<form action="" method="POST"> | ||
<p> | ||
<input type="hidden" name="csrftoken" value="{{ csrftoken() }}"> | ||
<textarea style="width: 90%; height: 20em" name="content">{{ content }}</textarea> | ||
</p> | ||
<p><input type="submit" value="Save"></p> | ||
</form> | ||
</details> | ||
|
||
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import bleach | ||
from bleach.sanitizer import Cleaner | ||
from bleach.html5lib_shim import Filter | ||
import markdown | ||
from markupsafe import Markup | ||
|
||
|
||
def render_markdown(value): | ||
attributes = {"a": ["href"], "img": ["src", "alt"]} | ||
cleaner = Cleaner( | ||
tags=[ | ||
"a", | ||
"abbr", | ||
"acronym", | ||
"b", | ||
"blockquote", | ||
"code", | ||
"em", | ||
"i", | ||
"li", | ||
"ol", | ||
"strong", | ||
"ul", | ||
"pre", | ||
"p", | ||
"h1", | ||
"h2", | ||
"h3", | ||
"h4", | ||
"h5", | ||
"h6", | ||
"img", | ||
], | ||
attributes=attributes, | ||
filters=[ImageMaxWidthFilter], | ||
) | ||
html = bleach.linkify( | ||
cleaner.clean( | ||
markdown.markdown(value, output_format="html5", extensions=["fenced_code"]) | ||
) | ||
) | ||
return Markup(html) | ||
|
||
|
||
class ImageMaxWidthFilter(Filter): | ||
"""Adds style="max-width: 100%" to any image tags""" | ||
|
||
def __iter__(self): | ||
for token in Filter.__iter__(self): | ||
if token["type"] == "EmptyTag" and token["name"] == "img": | ||
token["data"][(None, "style")] = "max-width: 100%" | ||
yield token |
Oops, something went wrong.