Skip to content

Commit

Permalink
First working version
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Dec 2, 2021
0 parents commit fa97f7d
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 0 deletions.
58 changes: 58 additions & 0 deletions .github/workflows/publish.yml
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", "3.10"]
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.10"
- 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 build
- name: Publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
python -m build
twine upload dist/*
30 changes: 30 additions & 0 deletions .github/workflows/test.yml
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", "3.10"]
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
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.venv
__pycache__/
*.py[cod]
*$py.class
venv
.eggs
.pytest_cache
*.egg-info
.DS_Store
.vscode
dist
build
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# datasette-hovercards

[![PyPI](https://img.shields.io/pypi/v/datasette-hovercards.svg)](https://pypi.org/project/datasette-hovercards/)
[![Changelog](https://img.shields.io/github/v/release/simonw/datasette-hovercards?include_prereleases&label=changelog)](https://github.com/simonw/datasette-hovercards/releases)
[![Tests](https://github.com/simonw/datasette-hovercards/workflows/Test/badge.svg)](https://github.com/simonw/datasette-hovercards/actions?query=workflow%3ATest)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-hovercards/blob/main/LICENSE)

Add preview hovercards to links in Datasette

## Installation

Install this plugin in the same environment as Datasette.

$ datasette install datasette-hovercards

## Usage

Usage instructions go here.

## Development

To set up this plugin locally, first checkout the code. Then create a new virtual environment:

cd datasette-hovercards
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
11 changes: 11 additions & 0 deletions datasette_hovercards/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from datasette import hookimpl


@hookimpl
def extra_js_urls(datasette, table):
if table:
return [
datasette.urls.static_plugins(
"datasette-hovercards", "datasette-hovercards.js"
)
]
89 changes: 89 additions & 0 deletions datasette_hovercards/static/datasette-hovercards.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
let hovercardOuterAnimation = null;
let hovercardInnerAnimation = null;
let hovercard = document.createElement("div");
hovercard.setAttribute("id", "datasette-hovercard")
hovercard.style.width = '300px';
hovercard.style.height = '200px';
hovercard.style.overflow = 'auto';
hovercard.style.backgroundColor = 'white';
hovercard.style.border = '1px solid #ccc';
hovercard.style.padding = '10px';
hovercard.style.position = 'absolute';
hovercard.style.display = 'none';
hovercard.style.boxShadow = '1px 2px 8px 2px rgba(0,0,0,0.08)';

const hovercardEscape = (s) => {
return (s || "").toString()
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
};

document.addEventListener("mouseover", async (ev) => {
const a = ev.target;
if (a.nodeName != 'A') {
return;
}
// TODO: Respect base_url and suchlike
if (a.pathname.split("/").length != 4) {
return; // Definitely not a row
}
// OK, it might be a row! Try a fetch
let row;
if (a.hovercardRowData) {
row = a.hovercardRowData;
} else {
const response = await fetch(a.pathname + ".json?_shape=array");
if (response.status == 200) {
const data = await response.json();
row = data[0];
a.hovercardRowData = row;
}
}
if (row) {
// Cancel any existing animations
if (hovercardOuterAnimation) {
clearTimeout(hovercardOuterAnimation);
}
if (hovercardInnerAnimation) {
clearTimeout(hovercardInnerAnimation);
}
hovercard.style.top = (ev.pageY + 5) + 'px';
hovercard.style.left = (ev.pageX - 15) + 'px';
let html = ['<dl>'];
for (const [key, value] of Object.entries(row)) {
html.push(`
<dt style="font-weight: bold">${hovercardEscape(key)}</dt>
<dd style="margin: 0px 0 0 0.7em;">
<span style="color: #999; font-size: 0.9em;">${hovercardEscape(value) || '&nbsp;'}</span>
</dd>
`);
}
html.push("</dl>")
hovercard.innerHTML = html.join("");
hovercard.style.display = 'block';
hovercard.style.opacity = 100;
hovercard.style.transition = 'none';
}
});

document.addEventListener("mouseout", (ev) => {
if (ev.target.id != "datasette-hovercard") {
return;
}
hovercardOuterAnimation = setTimeout(() => {
hovercard.style.transition = 'opacity 0.4s ease-in-out';
hovercard.style.opacity = 0;
hovercardInnerAnimation = setTimeout(() => {
hovercard.style.transition = 'none';
hovercard.style.display = "none";
hovercard.style.opacity = 100;
}, 800)
}, 400);
})

document.addEventListener("DOMContentLoaded", () => {
document.body.appendChild(hovercard);
});
39 changes: 39 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from setuptools import setup
import os

VERSION = "0.1"


def get_long_description():
with open(
os.path.join(os.path.dirname(os.path.abspath(__file__)), "README.md"),
encoding="utf8",
) as fp:
return fp.read()


setup(
name="datasette-hovercards",
description="Add preview hovercards to links in Datasette",
long_description=get_long_description(),
long_description_content_type="text/markdown",
author="Simon Willison",
url="https://github.com/simonw/datasette-hovercards",
project_urls={
"Issues": "https://github.com/simonw/datasette-hovercards/issues",
"CI": "https://github.com/simonw/datasette-hovercards/actions",
"Changelog": "https://github.com/simonw/datasette-hovercards/releases",
},
license="Apache License, Version 2.0",
classifiers=[
"Framework :: Datasette",
"License :: OSI Approved :: Apache Software License",
],
version=VERSION,
packages=["datasette_hovercards"],
entry_points={"datasette": ["hovercards = datasette_hovercards"]},
install_requires=["datasette"],
extras_require={"test": ["pytest", "pytest-asyncio"]},
package_data={"datasette_hovercards": ["static/*"]},
python_requires=">=3.6",
)
25 changes: 25 additions & 0 deletions tests/test_hovercards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from datasette.app import Datasette
import pytest


@pytest.mark.asyncio
async def test_static_asset():
datasette = Datasette([], memory=True)
response = await datasette.client.get(
"/-/static-plugins/datasette-hovercards/datasette-hovercards.js"
)
assert response.status_code == 200


@pytest.mark.asyncio
@pytest.mark.parametrize("path,expected", (("/db/t", True), ("/db", False)))
async def test_included_on_table_page(path, expected):
datasette = Datasette([], memory=True)
db = datasette.add_memory_database("db")
if not await db.table_exists("t"):
await db.execute_write("create table t (id integer primary key)", block=True)
response = await datasette.client.get(path)
if expected:
assert "datasette-hovercards.js" in response.text
else:
assert "datasette-hovercards.js" not in response.text

0 comments on commit fa97f7d

Please sign in to comment.