Use other string diff algorithm and optimize logs page

This commit is contained in:
thiloho
2024-10-05 21:15:26 +02:00
parent 5ebd40966c
commit b53f4c4859
9 changed files with 160 additions and 71 deletions

View File

@@ -25,7 +25,7 @@
margin-inline-start: -2rem;
border-radius: 50%;
border: var(--border-primary);
border-width: 0.125rem;
border-width: 0.25rem;
border-block-start-color: var(--color-accent);
animation: spinner 500ms linear infinite;
}

View File

@@ -152,7 +152,7 @@ export const md = (markdownContent: string, showToc = true) => {
return html;
};
export const LOADING_DELAY = 500;
export const LOADING_DELAY = 250;
let loadingDelay: number;
export const enhanceForm = (options?: {

View File

@@ -31,27 +31,61 @@
clip-rule="evenodd"
></path>
</svg>
Account registration is disabled on this instance
Registration is disabled
</p>
{:else}
<form method="POST" use:enhance={enhanceForm()}>
<label>
Username:
<input type="text" name="username" minlength="3" maxlength="16" required />
</label>
<label>
Password:
<input type="password" name="password" minlength="12" maxlength="128" required />
</label>
<div class="registration-wrapper">
<form method="POST" use:enhance={enhanceForm()}>
<label>
Username:
<input type="text" name="username" minlength="3" maxlength="16" required />
</label>
<label>
Password:
<input type="password" name="password" minlength="12" maxlength="128" required />
</label>
<button type="submit">Submit</button>
</form>
<button type="submit">Submit</button>
</form>
<details>
<summary>Password requirements</summary>
<ul>
<li>Must be between 12 and 128 characters long</li>
<li>Must contain at least one lowercase letter</li>
<li>Must contain at least one uppercase letter</li>
<li>Must contain at least one number</li>
<li>Must contain at least one special character</li>
</ul>
</details>
</div>
{/if}
<style>
.registration-disabled {
display: flex;
gap: 0.5rem;
gap: var(--space-2xs);
align-items: center;
}
.registration-wrapper {
display: flex;
flex-wrap: wrap;
gap: var(--space-l);
}
.registration-wrapper > form {
inline-size: 30ch;
flex-grow: 1;
}
.registration-wrapper > details {
inline-size: 35ch;
}
@media (max-width: 700px) {
.registration-wrapper > form {
order: 1;
}
}
</style>

View File

@@ -1,13 +1,14 @@
import type { PageServerLoad } from "./$types";
import type { PageServerLoad, Actions } from "./$types";
import { API_BASE_PREFIX, apiRequest } from "$lib/server/utils";
import type { ChangeLog, User, Collab } from "$lib/db-schema";
import DiffMatchPatch from "diff-match-patch";
export const load: PageServerLoad = async ({ parent, fetch, params, url }) => {
const userFilter = url.searchParams.get("logs_filter_user");
const resourceFilter = url.searchParams.get("logs_filter_resource");
const operationFilter = url.searchParams.get("logs_filter_operation");
const currentPage = Number.parseInt(url.searchParams.get("logs_results_page") ?? "1");
const resultOffset = (currentPage - 1) * 50;
const resultOffset = (currentPage - 1) * 20;
const searchParams = new URLSearchParams();
@@ -25,7 +26,7 @@ export const load: PageServerLoad = async ({ parent, fetch, params, url }) => {
searchParams.append("operation", `eq.${operationFilter.toUpperCase()}`);
}
const constructedFetchUrl = `${baseFetchUrl}&${searchParams.toString()}&limit=50&offset=${resultOffset}`;
const constructedFetchUrl = `${baseFetchUrl}&${searchParams.toString()}&limit=20&offset=${resultOffset}`;
const changeLog: (ChangeLog & { user: { username: User["username"] } })[] = (
await apiRequest(fetch, constructedFetchUrl, "GET", { returnData: true })
@@ -61,3 +62,46 @@ export const load: PageServerLoad = async ({ parent, fetch, params, url }) => {
collaborators
};
};
export const actions: Actions = {
computeDiff: async ({ request, fetch }) => {
const data = await request.formData();
const dmp = new DiffMatchPatch();
const htmlDiff = (oldValue: string, newValue: string) => {
const diff = dmp.diff_main(oldValue, newValue);
dmp.diff_cleanupSemantic(diff);
return diff
.map(([op, text]) => {
switch (op) {
case 1:
return `<ins>${text}</ins>`;
case -1:
return `<del>${text}</del>`;
default:
return text;
}
})
.join("");
};
const log: ChangeLog = (
await apiRequest(
fetch,
`${API_BASE_PREFIX}/change_log?id=eq.${data.get("id")}&select=old_value,new_value`,
"GET",
{ headers: { Accept: "application/vnd.pgrst.object+json" }, returnData: true }
)
).data;
return {
logId: data.get("id"),
currentDiff: htmlDiff(
JSON.stringify(log.old_value, null, 2),
JSON.stringify(log.new_value, null, 2)
)
};
}
};

View File

@@ -2,36 +2,17 @@
import WebsiteEditor from "$lib/components/WebsiteEditor.svelte";
import DateTime from "$lib/components/DateTime.svelte";
import Modal from "$lib/components/Modal.svelte";
import type { PageServerData } from "./$types";
import diff from "fast-diff";
import type { PageServerData, ActionData } from "./$types";
import { page } from "$app/stores";
import { tables } from "$lib/db-schema";
import { previewContent } from "$lib/runes.svelte";
import DOMPurify from "isomorphic-dompurify";
import { enhanceForm } from "$lib/utils";
import { enhance } from "$app/forms";
import { sending } from "$lib/runes.svelte";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
const { data }: { data: PageServerData } = $props();
const htmlDiff = (oldValue: string, newValue: string) => {
return diff(oldValue, newValue)
.map(([type, value]) => {
let newString = "";
switch (type) {
case 1:
newString += `<ins>${value}</ins>`;
break;
case 0:
newString += `${value}`;
break;
case -1:
newString += `<del>${value}</del>`;
break;
}
return newString;
})
.join("");
};
const { data, form }: { data: PageServerData; form: ActionData } = $props();
let resources = $state({});
@@ -48,16 +29,18 @@
}
previewContent.value = data.home.main_content;
let logsSection: HTMLElement;
</script>
{#if sending.value}
<LoadingSpinner />
{/if}
<WebsiteEditor
id={data.website.id}
contentType={data.website.content_type}
title={data.website.title}
>
<section id="logs" bind:this={logsSection}>
<section id="logs">
<hgroup>
<h2>
<a href="#logs">Logs</a>
@@ -153,15 +136,34 @@
<hgroup>
<h3>Log changes</h3>
<p>{table_name} &mdash; {operation}</p>
<p>{table_name} &mdash; {operation} &mdash; User "{username}"</p>
</hgroup>
<pre style="white-space: pre-wrap">{@html DOMPurify.sanitize(
htmlDiff(oldValue, newValue),
{
ALLOWED_TAGS: ["ins", "del"]
}
)}</pre>
{#if old_value && new_value}
<h4>Difference</h4>
<form action="?/computeDiff" method="POST" use:enhance={enhanceForm()}>
<input type="hidden" name="id" value={id} />
<button type="submit">Compute diff</button>
</form>
{#if form?.logId === id && form?.currentDiff}
<pre style="white-space: pre-wrap">{@html DOMPurify.sanitize(
form.currentDiff,
{ ALLOWED_TAGS: ["ins", "del"] }
)}</pre>
{/if}
{/if}
{#if new_value && !old_value}
<h4>New value</h4>
<pre style="white-space: pre-wrap">{@html (DOMPurify.sanitize(newValue),
{ ALLOWED_TAGS: ["ins", "del"] })}</pre>
{/if}
{#if old_value && !new_value}
<h4>Old value</h4>
<pre style="white-space: pre-wrap">{@html (DOMPurify.sanitize(oldValue),
{ ALLOWED_TAGS: ["ins", "del"] })}</pre>
{/if}
</Modal>
</td>
</tr>
@@ -189,7 +191,7 @@
{/snippet}
<p>
{$page.url.searchParams.get("logs_results_page") ?? 1} / {Math.max(
Math.ceil(data.resultChangeLogCount / 50),
Math.ceil(data.resultChangeLogCount / 20),
1
)}
</p>
@@ -222,7 +224,7 @@
type="hidden"
name="logs_results_page"
value={Math.min(
Math.max(Math.ceil(data.resultChangeLogCount / 50), 1),
Math.max(Math.ceil(data.resultChangeLogCount / 20), 1),
Number.parseInt($page.url.searchParams.get("logs_results_page") ?? "1") + 1
)}
/>
@@ -230,20 +232,20 @@
<button
type="submit"
disabled={($page.url.searchParams.get("logs_results_page") ?? "1") ===
Math.max(Math.ceil(data.resultChangeLogCount / 50), 1).toString()}>Next</button
Math.max(Math.ceil(data.resultChangeLogCount / 20), 1).toString()}>Next</button
>
</form>
<form method="GET">
<input
type="hidden"
name="logs_results_page"
value={Math.max(Math.ceil(data.resultChangeLogCount / 50), 1)}
value={Math.max(Math.ceil(data.resultChangeLogCount / 20), 1)}
/>
{@render commonFilterInputs()}
<button
type="submit"
disabled={($page.url.searchParams.get("logs_results_page") ?? "1") ===
Math.max(Math.ceil(data.resultChangeLogCount / 50), 1).toString()}>Last</button
Math.max(Math.ceil(data.resultChangeLogCount / 20), 1).toString()}>Last</button
>
</form>
</div>

View File

@@ -21,7 +21,7 @@
let loadingDelay: number;
$effect(() => {
if ($navigating && ["link", "goto"].includes($navigating.type)) {
if ($navigating) {
loadingDelay = window.setTimeout(() => (loading = true), LOADING_DELAY);
} else {
window.clearTimeout(loadingDelay);