Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Build and deploy

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pages-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- uses: actions/upload-pages-artifact@v3
if: github.ref == 'refs/heads/main'
with:
path: _site

deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.idea/
node_modules/
_site/
21 changes: 19 additions & 2 deletions DEVICE_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ Copy the block below into `docs/devices/<vendor>-<model>.md` and remove the sect

## Conventions

- The frontmatter block at the top puts the device on the Supported Devices page; there is no separate list to edit.
- `title`: name shown on the page and in the list (the page has no `# heading` of its own)
- `vendor`: groups the device in the list, e.g. `LilyGO`
- `psram`: `true` when the device has external RAM
- `sdcard`: `true` when the SD card works
- `recommended`: `true` for devices recommended by the developer (⭐)
- `community`: `true` when the developer does not own the board
- `incubating`: `true` while support is still being worked on
- `installer` (optional): the device id in the [Web Installer](https://install.tactilityproject.org/), used for the "Install Tactility" button. Leave it out when the id equals the file name. Use a list of `id` / `label` pairs when there are several firmware builds (see `docs/devices/cl32.md`), or `false` to hide the button.
- Status legend (also shown on `supported-devices.md`):
- ✅ Supported
- ⚠️ Partially supported or buggy (explain in Notes)
Expand All @@ -13,14 +22,22 @@ Copy the block below into `docs/devices/<vendor>-<model>.md` and remove the sect
- Spelling: `LilyGO`, `M5Stack`, `Elecrow`, `Guition`, `Waveshare`, `Seeed Studio`, `Wi-Fi`, `USB Mass Storage`, `SD card`.
- Chip names in code formatting: `SX1262`, `ES8311`.
- Headings in sentence case: "Known issues", not "Known Issues".
- Warnings use Docsify alerts (`> [!WARNING]`, `> [!NOTE]`), not loose ⚠️ lines.
- Warnings use GitHub-style alerts (`> [!WARNING]`, `> [!NOTE]`), not loose ⚠️ lines.
- Link labels: "Product page", "Official documentation", "Store", "GitHub", "Wiki". Add the variant name when there are multiple, e.g. "Store (Keyboard)".
- Known issues: one or two sentences on what is affected and what to do about it, then a link to the source.

## Template

```markdown
# <Vendor> <Model>
---
title: "<Vendor> <Model>"
vendor: <Vendor>
psram: true
sdcard: true
recommended: false
community: false
incubating: false
---

<One sentence describing the device.>

Expand Down
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
This projects contains the content for the website at [tactilityproject.org](https://tactilityproject.org).
This project contains the content for the website at [tactilityproject.org](https://tactilityproject.org).

It is built with [Docsify](https://docsify.js.org), which installs as follows:
The pages are markdown files in `docs/`. [Eleventy](https://www.11ty.dev) turns them into static HTML.

Install the dependencies (Node.js 20 or newer):

```sh
npm install
```

Develop with a local server that reloads on changes:

```sh
npm i docsify-cli -g
npm start
```

Develop with:
Build the site into `_site/`:

```sh
docsify serve docs
npm run build
```

## Where things are

- `docs/*.md`: the pages. `docs/index.md` is the home page; its hero, screenshots and supporter logos are listed in the frontmatter.
- `docs/devices/*.md`: one page per device. The Supported Devices list is generated from the frontmatter of these files, see [DEVICE_TEMPLATE.md](DEVICE_TEMPLATE.md).
- `docs/_data/`: site name, community links and navigation.
- `docs/_includes/`: page layouts.
- `docs/assets/site.css`: the stylesheet.

Pushing to `main` builds and publishes the site with GitHub Actions (`.github/workflows/deploy.yml`).
Empty file removed docs/.nojekyll
Empty file.
7 changes: 7 additions & 0 deletions docs/404.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Page not found
permalink: /404.html
eleventyExcludeFromCollections: true
---

This page doesn't exist. Try the [home page](index.md) or the list of [supported devices](supported-devices.md).
55 changes: 0 additions & 55 deletions docs/README.md

This file was deleted.

15 changes: 15 additions & 0 deletions docs/_data/nav.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"main": [
{ "label": "Features", "url": "/features/" },
{ "label": "Install", "url": "/installing/" },
{ "label": "Devices", "url": "/devices/" },
{ "label": "FAQ", "url": "/faq/" },
{ "label": "Support", "url": "/support/" },
{ "label": "Docs", "url": "https://docs.tactilityproject.org", "external": true }
],
"footer": [
{ "label": "Licenses & Copyrights", "url": "/licenses-and-copyrights/" },
{ "label": "Developer documentation", "url": "https://docs.tactilityproject.org" },
{ "label": "GitHub", "url": "https://github.com/TactilityProject" }
]
}
13 changes: 13 additions & 0 deletions docs/_data/site.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "Tactility",
"tagline": "An operating system for ESP32 devices",
"description": "Tactility is an operating system for the ESP32 microcontroller family, with a launcher, apps, an App Hub and support for more than 50 devices.",
"url": "https://tactilityproject.org",
"repo": "https://github.com/TactilityProject",
"installer": "https://install.tactilityproject.org/",
"community": [
{ "name": "Matrix", "url": "https://matrix.to/#/#tactility:matrix.org", "logo": "/images/matrix-logo.svg" },
{ "name": "Stoat", "url": "https://stt.gg/xwRTZEpX", "logo": "/images/stoat-logo.svg" },
{ "name": "Discord", "url": "https://discord.gg/pXs2xGS6fs" }
]
}
4 changes: 4 additions & 0 deletions docs/_includes/badges.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{%- if device.psram %}<span class="badge badge-yes">External RAM</span>{% else %}<span class="badge badge-warn">No external RAM</span>{% endif %}
{%- if device.sdcard %}<span class="badge badge-yes">SD card</span>{% else %}<span class="badge badge-no">No SD card</span>{% endif %}
{%- if device.community %}<span class="badge">Community tested</span>{% endif %}
{%- if device.incubating %}<span class="badge badge-warn">Incubating</span>{% endif %}
79 changes: 79 additions & 0 deletions docs/_includes/base.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{%- set pageTitle = (title + " | " + site.name) if (title and title != site.name) else (site.name + ": " + site.tagline) %}
{%- set pageDescription = description or site.description %}
<title>{{ pageTitle }}</title>
<meta name="description" content="{{ pageDescription }}">
<meta name="color-scheme" content="dark">
<meta name="theme-color" content="#16171d">
<meta property="og:type" content="website">
<meta property="og:site_name" content="{{ site.name }}">
<meta property="og:title" content="{{ pageTitle }}">
<meta property="og:description" content="{{ pageDescription }}">
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
<link rel="canonical" href="{{ site.url }}{{ page.url }}">
<link rel="icon" href="/images/favicon.svg">
<link rel="mask-icon" href="/images/favicon-safari.svg" color="#000000">
<link rel="apple-touch-icon" href="/images/apple-touch-icon.svg">
<link rel="preload" href="/assets/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/assets/site.css">
<script>
// Links from the old site looked like /#/features or /#/devices/lilygo-tdeck
(function () {
var m = /^#\/([a-z0-9-]+(?:\/[a-z0-9-]+)?)(?:\.md)?\/?(?:\?id=([a-z0-9_-]+))?$/i.exec(location.hash);
if (!m) return;
var path = m[1] === "supported-devices" ? "devices" : m[1];
location.replace("/" + path + "/" + (m[2] ? "#" + m[2] : ""));
})();
</script>
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<header class="site-header">
<div class="wrap">
<a class="brand" href="/"><img src="/images/favicon.svg" width="28" height="28" alt="">{{ site.name }}</a>
<button class="nav-toggle" type="button" aria-controls="site-nav" aria-expanded="false" hidden>Menu</button>
<nav id="site-nav" aria-label="Main">
{%- for item in nav.main %}
<a href="{{ item.url }}"{% if not item.external and page.url.startsWith(item.url) %} aria-current="page"{% endif %}>{{ item.label }}{% if item.external %}<span aria-hidden="true"> ↗</span>{% endif %}</a>
{%- endfor %}
<a class="nav-repo" href="{{ site.repo }}">GitHub</a>
</nav>
</div>
</header>

<main id="main">
{{ content | safe }}
</main>

<footer class="site-footer">
<div class="wrap">
<ul>
{%- for item in nav.footer %}
<li><a href="{{ item.url }}">{{ item.label }}</a></li>
{%- endfor %}
</ul>
<ul>
{%- for item in site.community %}
<li><a href="{{ item.url }}">{{ item.name }}</a></li>
{%- endfor %}
</ul>
</div>
</footer>
<script>
(function () {
var button = document.querySelector(".nav-toggle");
var nav = document.getElementById("site-nav");
button.hidden = false;
nav.classList.add("is-collapsible");
button.addEventListener("click", function () {
var open = nav.classList.toggle("is-open");
button.setAttribute("aria-expanded", open);
});
})();
</script>
</body>
</html>
20 changes: 20 additions & 0 deletions docs/_includes/device.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
layout: base
---
<div class="wrap page">
<article class="prose">
<p class="breadcrumb"><a href="/devices/">Supported devices</a> <span aria-hidden="true">/</span> {{ vendor }}</p>
<h1>{{ title }}{% if recommended %} <span class="star" role="img" aria-label="Recommended" title="Recommended by the developer">⭐</span>{% endif %}</h1>
<p class="badges">
{%- set device = { recommended: recommended, psram: psram, sdcard: sdcard, community: community, incubating: incubating } %}
{% include "badges.njk" %}
</p>
<p class="actions device-install">
{%- for target in installer | installTargets(page.fileSlug) %}
<a class="btn btn-primary" href="{{ site.installer }}#{{ target.id | urlencode }}">Install Tactility{% if target.label %} ({{ target.label }}){% endif %}</a>
{%- endfor %}
</p>
{{ content | safe }}
<p class="page-note">What the status markers and badges mean is explained on the <a href="/devices/#legend">supported devices</a> page.</p>
</article>
</div>
71 changes: 71 additions & 0 deletions docs/_includes/devices.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
layout: base
---
{%- set parts = content.split("<!-- devices -->") %}
<div class="wrap page page-wide">
<div class="prose">
<h1>{{ title }}</h1>
{{ parts[0] | safe }}
</div>

<form class="device-filter" id="device-filter" hidden>
<input type="search" id="device-search" placeholder="Search {{ collections.device.length }} devices" aria-label="Search devices" autocomplete="off">
<label class="chip"><input type="checkbox" data-flag="recommended"> ⭐ Recommended</label>
<label class="chip"><input type="checkbox" data-flag="psram"> External RAM</label>
<label class="chip"><input type="checkbox" data-flag="sdcard"> SD card</label>
</form>
<p class="device-empty" id="device-empty" hidden>No devices match.</p>

{%- for group in collections.device | deviceGroups %}
<section class="device-group">
<h2>{{ group.name }} <span class="count">{{ group.devices.length }}</span></h2>
{%- if group.note %}<p class="group-note">{{ group.note }}</p>{% endif %}
<ul class="device-grid">
{%- for item in group.devices %}
{%- set device = item.data %}
<li class="device-card" data-name="{{ device.title | lower }}"
{%- if device.recommended %} data-recommended{% endif %}
{%- if device.psram %} data-psram{% endif %}
{%- if device.sdcard %} data-sdcard{% endif %}>
<a href="{{ item.url }}">{{ device.title }}{% if device.recommended %} <span class="star" role="img" aria-label="Recommended" title="Recommended by the developer">⭐</span>{% endif %}</a>
<p class="badges">{% include "badges.njk" %}</p>
</li>
{%- endfor %}
</ul>
</section>
{%- endfor %}

<div class="prose">
{{ parts[1] | safe }}
</div>
</div>
<script>
(function () {
var form = document.getElementById("device-filter");
var search = document.getElementById("device-search");
var empty = document.getElementById("device-empty");
var flags = [].slice.call(form.querySelectorAll("[data-flag]"));
var groups = [].slice.call(document.querySelectorAll(".device-group"));
form.hidden = false;
function update() {
var words = search.value.toLowerCase().split(/\s+/).filter(Boolean);
var required = flags.filter(function (f) { return f.checked; }).map(function (f) { return f.dataset.flag; });
var total = 0;
groups.forEach(function (group) {
var shown = 0;
[].forEach.call(group.querySelectorAll(".device-card"), function (card) {
var match = words.every(function (w) { return card.dataset.name.indexOf(w) !== -1; }) &&
required.every(function (flag) { return flag in card.dataset; });
card.hidden = !match;
if (match) shown++;
});
group.hidden = shown === 0;
total += shown;
});
empty.hidden = total !== 0;
}
form.addEventListener("input", update);
form.addEventListener("submit", function (event) { event.preventDefault(); });
update();
})();
</script>
Loading
Loading