Compare commits
9 commits
Author | SHA1 | Date | |
---|---|---|---|
a69c474d1d |
|||
c6abf633f8 |
|||
88a5040f9c |
|||
cdb344609c |
|||
4a3155c33a |
|||
a4eebb944f |
|||
d5e9a9469f |
|||
f76e7fd97f |
|||
068c7961ae |
15 changed files with 87 additions and 51 deletions
|
@ -50,6 +50,7 @@ jobs:
|
||||||
id: e2etest
|
id: e2etest
|
||||||
run: |
|
run: |
|
||||||
pnpm run build -l silent
|
pnpm run build -l silent
|
||||||
|
npx playwright install chromium
|
||||||
pnpm run test:e2e
|
pnpm run test:e2e
|
||||||
- name: 💢 Upload E2E report
|
- name: 💢 Upload E2E report
|
||||||
if: ${{ failure() && steps.e2etest.conclusion == 'failure' }}
|
if: ${{ failure() && steps.e2etest.conclusion == 'failure' }}
|
||||||
|
@ -67,7 +68,7 @@ jobs:
|
||||||
- name: 👁️ Checkout repository
|
- name: 👁️ Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 1 # important to fetch tag logs
|
||||||
|
|
||||||
- name: 📦 pnpm install
|
- name: 📦 pnpm install
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -3,6 +3,22 @@
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.3.4](https://code.thetadev.de/HSA/Visitenbuch/compare/v0.3.3..v0.3.4) - 2024-05-16
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- Select table entries on doubleclick - ([c6abf63](https://code.thetadev.de/HSA/Visitenbuch/commit/c6abf633f8ae5e9b562dda36f9f7ab4d6adcb4e1))
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- Escape HTML for licenses file - ([f76e7fd](https://code.thetadev.de/HSA/Visitenbuch/commit/f76e7fd97f62d9b41ecbabc3334c2c1876be253d))
|
||||||
|
- Use btn-id class for all tables - ([d5e9a94](https://code.thetadev.de/HSA/Visitenbuch/commit/d5e9a9469f0c57939367141985a97d8404fd6fbe))
|
||||||
|
- Avoid global state, use context for savedFilters - ([a4eebb9](https://code.thetadev.de/HSA/Visitenbuch/commit/a4eebb944f55da8e87cc899eebada0bd3fd37aa8))
|
||||||
|
- Close autocomplete on defocus - ([4a3155c](https://code.thetadev.de/HSA/Visitenbuch/commit/4a3155c33aa354973d4e0ca3ffeab2b7fd442040))
|
||||||
|
- Remove process.on hooks (not necessary) - ([cdb3446](https://code.thetadev.de/HSA/Visitenbuch/commit/cdb344609cde80084876faea9f80e7b26b01d0f2))
|
||||||
|
- Autocomplete not closing on tab - ([88a5040](https://code.thetadev.de/HSA/Visitenbuch/commit/88a5040f9c4e19ae3efb5ad0894c8dc5b905a92e))
|
||||||
|
|
||||||
|
|
||||||
## [v0.3.3](https://code.thetadev.de/HSA/Visitenbuch/compare/v0.3.2..v0.3.3) - 2024-05-14
|
## [v0.3.3](https://code.thetadev.de/HSA/Visitenbuch/compare/v0.3.2..v0.3.3) - 2024-05-14
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "visitenbuch",
|
"name": "visitenbuch",
|
||||||
"version": "0.3.3",
|
"version": "0.3.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
|
|
@ -31,7 +31,3 @@ export const handle = sequence(
|
||||||
authorization,
|
authorization,
|
||||||
createTRPCHandle({ router, createContext }),
|
createTRPCHandle({ router, createContext }),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Allow server application to exit
|
|
||||||
process.on("SIGINT", () => process.exit()); // Ctrl+C
|
|
||||||
process.on("SIGTERM", () => process.exit()); // docker stop
|
|
||||||
|
|
|
@ -166,6 +166,14 @@
|
||||||
if (opened) {
|
if (opened) {
|
||||||
onClose(kb);
|
onClose(kb);
|
||||||
}
|
}
|
||||||
|
// select remaining item if autoselect is enabled
|
||||||
|
if (!selection) {
|
||||||
|
if (!noAutoselect1 && filteredItems.length === 1) {
|
||||||
|
selectListItem(filteredItems[0], true);
|
||||||
|
} else {
|
||||||
|
setInputValue("");
|
||||||
|
}
|
||||||
|
}
|
||||||
opened = false;
|
opened = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,43 +193,38 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(e: KeyboardEvent): void {
|
function onKeyDown(e: KeyboardEvent): void {
|
||||||
let { key } = e;
|
switch (e.key) {
|
||||||
if (key === "Tab" && e.shiftKey) key = "ShiftTab";
|
case "Tab":
|
||||||
const fnmap: Record<string, () => void> = {
|
close();
|
||||||
Tab: () => close,
|
break;
|
||||||
ShiftTab: () => close,
|
case "ArrowDown":
|
||||||
ArrowDown: () => {
|
|
||||||
open();
|
open();
|
||||||
if (highlightIndex < filteredItems.length - 1) {
|
if (highlightIndex < filteredItems.length - 1) {
|
||||||
highlightIndex++;
|
highlightIndex++;
|
||||||
highlight();
|
highlight();
|
||||||
}
|
}
|
||||||
},
|
break;
|
||||||
ArrowUp: () => {
|
case "ArrowUp":
|
||||||
open();
|
open();
|
||||||
if (highlightIndex > 0) {
|
if (highlightIndex > 0) {
|
||||||
highlightIndex--;
|
highlightIndex--;
|
||||||
highlight();
|
highlight();
|
||||||
}
|
}
|
||||||
},
|
break;
|
||||||
Escape: () => {
|
case "Escape":
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (opened) {
|
if (opened) {
|
||||||
if (inputElm) inputElm.focus();
|
if (inputElm) inputElm.focus();
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
},
|
break;
|
||||||
Backspace: () => {
|
case "Backspace":
|
||||||
if (inputValue().length === 0) {
|
if (inputValue().length === 0) {
|
||||||
onBackspace();
|
onBackspace();
|
||||||
} else if (selection) {
|
} else if (selection) {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
}
|
}
|
||||||
},
|
break;
|
||||||
};
|
|
||||||
const fn = fnmap[key];
|
|
||||||
if (typeof fn === "function") {
|
|
||||||
fn();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,16 +237,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBlur(): void {
|
|
||||||
if (!selection) {
|
|
||||||
if (!noAutoselect1 && filteredItems.length === 1) {
|
|
||||||
selectListItem(filteredItems[0], true);
|
|
||||||
} else {
|
|
||||||
setInputValue("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function highlight(): void {
|
function highlight(): void {
|
||||||
if (browser && opened) {
|
if (browser && opened) {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
|
@ -303,12 +296,11 @@
|
||||||
on:focus={open}
|
on:focus={open}
|
||||||
on:keydown={onKeyDown}
|
on:keydown={onKeyDown}
|
||||||
on:keypress={onKeyPress}
|
on:keypress={onKeyPress}
|
||||||
on:blur={onBlur}
|
|
||||||
use:floatingRef
|
use:floatingRef
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if opened && filteredItems.length > 0}
|
{#if opened && filteredItems.length > 0}
|
||||||
<div bind:this={listElm} class="autocomplete-list" use:floatingContent>
|
<div bind:this={listElm} class="autocomplete-list" tabindex="-1" use:floatingContent>
|
||||||
{#each filteredItems as item, i}
|
{#each filteredItems as item, i}
|
||||||
<div
|
<div
|
||||||
class="autocomplete-list-item"
|
class="autocomplete-list-item"
|
||||||
|
@ -344,7 +336,12 @@
|
||||||
|
|
||||||
{#if clearBtn && selection}
|
{#if clearBtn && selection}
|
||||||
<div class="absolute bottom-0 right-0 h-full flex items-center">
|
<div class="absolute bottom-0 right-0 h-full flex items-center">
|
||||||
<IconButton cls="" path={mdiClose} title="Löschen" on:click={clearSelection} />
|
<IconButton
|
||||||
|
cls=""
|
||||||
|
path={mdiClose}
|
||||||
|
tabindex={-1}
|
||||||
|
title="Löschen"
|
||||||
|
on:click={clearSelection} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,12 +8,14 @@
|
||||||
import { toastError, toastInfo } from "$lib/shared/util/toast";
|
import { toastError, toastInfo } from "$lib/shared/util/toast";
|
||||||
|
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
import Icon from "$lib/components/ui/Icon.svelte";
|
||||||
import { savedFilters } from "$lib/stores";
|
import { getSavedFilters } from "$lib/stores";
|
||||||
|
|
||||||
import Chip from "./SavedFilterChip.svelte";
|
import Chip from "./SavedFilterChip.svelte";
|
||||||
|
|
||||||
export let view: string;
|
export let view: string;
|
||||||
|
|
||||||
|
const savedFilters = getSavedFilters();
|
||||||
|
|
||||||
$: filters = $savedFilters[view] ?? [];
|
$: filters = $savedFilters[view] ?? [];
|
||||||
|
|
||||||
function getQuery(): string {
|
function getQuery(): string {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { goto } from "$app/navigation";
|
||||||
|
|
||||||
import type { RouterOutput } from "$lib/shared/trpc";
|
import type { RouterOutput } from "$lib/shared/trpc";
|
||||||
import { formatDate } from "$lib/shared/util";
|
import { formatDate } from "$lib/shared/util";
|
||||||
|
|
||||||
|
@ -39,6 +41,7 @@
|
||||||
class="transition-colors hover:bg-neutral-content/10"
|
class="transition-colors hover:bg-neutral-content/10"
|
||||||
class:done={entry.execution?.done}
|
class:done={entry.execution?.done}
|
||||||
class:priority={entry.current_version.priority}
|
class:priority={entry.current_version.priority}
|
||||||
|
on:dblclick={() => { void goto("/entry/" + entry.id); }}
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
><a
|
><a
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { goto } from "$app/navigation";
|
||||||
|
|
||||||
import { mdiFilter } from "@mdi/js";
|
import { mdiFilter } from "@mdi/js";
|
||||||
|
|
||||||
import { URL_ENTRIES } from "$lib/shared/constants";
|
import { URL_ENTRIES } from "$lib/shared/constants";
|
||||||
|
@ -34,10 +36,11 @@
|
||||||
<tr
|
<tr
|
||||||
class="transition-colors hover:bg-neutral-content/10"
|
class="transition-colors hover:bg-neutral-content/10"
|
||||||
class:p-hidden={patient.hidden}
|
class:p-hidden={patient.hidden}
|
||||||
|
on:dblclick={() => { void goto("/patient/" + patient.id); }}
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
><a
|
><a
|
||||||
class="btn btn-xs btn-primary"
|
class="btn btn-xs btn-primary btn-id"
|
||||||
aria-label="Eintrag anzeigen"
|
aria-label="Eintrag anzeigen"
|
||||||
href="/patient/{patient.id}">{patient.id}</a
|
href="/patient/{patient.id}">{patient.id}</a
|
||||||
></td
|
></td
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getContext } from "svelte";
|
||||||
import { derived, writable, type Writable } from "svelte/store";
|
import { derived, writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
import type { SavedFilter } from "$lib/shared/model";
|
import type { SavedFilter } from "$lib/shared/model";
|
||||||
|
@ -6,4 +7,6 @@ import type { SavedFilter } from "$lib/shared/model";
|
||||||
export const screenWidth = writable(0);
|
export const screenWidth = writable(0);
|
||||||
export const screenWidthSmall = derived(screenWidth, ($mainWidth) => $mainWidth < 500);
|
export const screenWidthSmall = derived(screenWidth, ($mainWidth) => $mainWidth < 500);
|
||||||
|
|
||||||
export const savedFilters: Writable<Record<string, SavedFilter[]>> = writable({});
|
// Context key: "savedFilters"
|
||||||
|
export type SavedFilters = Writable<Record<string, SavedFilter[]>>;
|
||||||
|
export const getSavedFilters: () => SavedFilters = () => getContext("savedFilters");
|
||||||
|
|
|
@ -3,16 +3,22 @@
|
||||||
import type { LayoutData } from "./$types";
|
import type { LayoutData } from "./$types";
|
||||||
|
|
||||||
import { mdiAccount, mdiHome } from "@mdi/js";
|
import { mdiAccount, mdiHome } from "@mdi/js";
|
||||||
|
import { setContext } from "svelte";
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
import { defaultFilterUrl, defaultVisitUrl } from "$lib/shared/util";
|
import { defaultFilterUrl, defaultVisitUrl } from "$lib/shared/util";
|
||||||
|
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
import Icon from "$lib/components/ui/Icon.svelte";
|
||||||
import NavLink from "$lib/components/ui/NavLink.svelte";
|
import NavLink from "$lib/components/ui/NavLink.svelte";
|
||||||
import { savedFilters } from "$lib/stores";
|
import type { SavedFilters } from "$lib/stores";
|
||||||
|
|
||||||
export let data: LayoutData;
|
export let data: LayoutData;
|
||||||
|
|
||||||
|
const savedFilters: SavedFilters = writable();
|
||||||
|
|
||||||
$: savedFilters.set(data.savedFilters);
|
$: savedFilters.set(data.savedFilters);
|
||||||
|
|
||||||
|
setContext("savedFilters", savedFilters);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="navbar-outer">
|
<div class="navbar-outer">
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
|
|
||||||
import { defaultFilterUrl } from "$lib/shared/util";
|
import { defaultFilterUrl } from "$lib/shared/util";
|
||||||
|
|
||||||
import { savedFilters } from "$lib/stores";
|
import { getSavedFilters } from "$lib/stores";
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
const savedFilters = getSavedFilters();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each data.rooms as room (room.id)}
|
{#each data.rooms as room (room.id)}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a class="btn btn-sm" href="/room/{room.id}">{room.name}</a></td>
|
<td><a class="btn btn-sm btn-id" href="/room/{room.id}">{room.name}</a></td>
|
||||||
<td>{room.station.name}</td>
|
<td>{room.station.name}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
{#each data.stations as station (station.id)}
|
{#each data.stations as station (station.id)}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn-sm" href="/station/{station.id}">{station.name}</a>
|
<a class="btn btn-sm btn-id" href="/station/{station.id}">{station.name}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -23,7 +23,8 @@ export default defineConfig({
|
||||||
createViteLicensePlugin({
|
createViteLicensePlugin({
|
||||||
additionalFiles: {
|
additionalFiles: {
|
||||||
"oss-licenses.html": (packages) => {
|
"oss-licenses.html": (packages) => {
|
||||||
let res = `<html>
|
let res = `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Visitenbuch - Lizenzen</title>
|
<title>Visitenbuch - Lizenzen</title>
|
||||||
</head>
|
</head>
|
||||||
|
@ -31,6 +32,12 @@ export default defineConfig({
|
||||||
<h1>Open-Source-Lizenzen</h1>
|
<h1>Open-Source-Lizenzen</h1>
|
||||||
<a href="./oss-licenses.json">JSON-formatted license list</a>
|
<a href="./oss-licenses.json">JSON-formatted license list</a>
|
||||||
`;
|
`;
|
||||||
|
const escapeHTML = (s: string | null) => s ? s.replaceAll("&", "&")
|
||||||
|
.replaceAll("<", "<")
|
||||||
|
.replaceAll(">", ">")
|
||||||
|
.replaceAll('"', """)
|
||||||
|
.replaceAll("'", "'") : "";
|
||||||
|
|
||||||
for (const _p of packages) {
|
for (const _p of packages) {
|
||||||
type LicenseMetaExt = LicenseMeta & {
|
type LicenseMetaExt = LicenseMeta & {
|
||||||
repository: string | null,
|
repository: string | null,
|
||||||
|
@ -54,13 +61,13 @@ export default defineConfig({
|
||||||
}
|
}
|
||||||
|
|
||||||
res += `<div class="package">\n`;
|
res += `<div class="package">\n`;
|
||||||
res += `<h3><a href="https://www.npmjs.com/package/${p.name}" target="_blank" rel="noopener noreferrer">${p.name}</a></h3>\n`;
|
res += `<h3><a href="https://www.npmjs.com/package/${escapeHTML(p.name)}" target="_blank" rel="noopener noreferrer">${escapeHTML(p.name)}</a></h3>\n`;
|
||||||
res += `<table>\n`;
|
res += `<table>\n`;
|
||||||
res += `<tr><td>Version:</td><td>${p.version}</td></tr>\n`;
|
res += `<tr><td>Version:</td><td>${escapeHTML(p.version)}</td></tr>\n`;
|
||||||
if (aut) res += `<tr><td>Author:</td><td>${aut}</td></tr>\n`;
|
if (aut) res += `<tr><td>Author:</td><td>${escapeHTML(aut)}</td></tr>\n`;
|
||||||
res += `<tr><td>License:</td><td>${p.license}</td></tr>\n`;
|
res += `<tr><td>License:</td><td>${escapeHTML(p.license)}</td></tr>\n`;
|
||||||
if (repoUrl) res += `<tr><td>Repository:</td><td><a href="${repoUrl}" target="_blank" rel="noopener noreferrer">${repoUrl}</a></td></tr>\n`;
|
if (repoUrl) res += `<tr><td>Repository:</td><td><a href="${escapeHTML(repoUrl)}" target="_blank" rel="noopener noreferrer">${escapeHTML(repoUrl)}</a></td></tr>\n`;
|
||||||
else if (rp) res += `<tr><td>Repository:</td><td>${rp}</td></tr>\n`;
|
else if (rp) res += `<tr><td>Repository:</td><td>${escapeHTML(rp)}</td></tr>\n`;
|
||||||
res += `</table>\n`;
|
res += `</table>\n`;
|
||||||
res += "</div>";
|
res += "</div>";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue