From b53f4c4859f0c679581573528edc1aa090a1d490 Mon Sep 17 00:00:00 2001 From: thiloho <123883702+thiloho@users.noreply.github.com> Date: Sat, 5 Oct 2024 21:15:26 +0200 Subject: [PATCH] Use other string diff algorithm and optimize logs page --- web-app/package-lock.json | 22 +++-- web-app/package.json | 3 +- .../src/lib/components/LoadingSpinner.svelte | 2 +- web-app/src/lib/utils.ts | 2 +- .../routes/(anonymous)/register/+page.svelte | 60 +++++++++++--- .../website/[websiteId]/logs/+page.server.ts | 50 ++++++++++- .../website/[websiteId]/logs/+page.svelte | 82 ++++++++++--------- web-app/src/routes/+layout.svelte | 2 +- web-app/template-styles/variables.css | 8 +- 9 files changed, 160 insertions(+), 71 deletions(-) diff --git a/web-app/package-lock.json b/web-app/package-lock.json index 12e611a..ac3a952 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -8,7 +8,7 @@ "name": "web-app", "version": "0.0.1", "dependencies": { - "fast-diff": "1.3.0", + "diff-match-patch": "1.0.5", "highlight.js": "11.10.0", "isomorphic-dompurify": "2.15.0", "marked": "14.1.2", @@ -20,6 +20,7 @@ "@sveltejs/adapter-node": "5.2.3", "@sveltejs/kit": "2.5.28", "@sveltejs/vite-plugin-svelte": "4.0.0-next.6", + "@types/diff-match-patch": "1.0.36", "@types/eslint": "9.6.1", "@types/eslint__js": "8.42.3", "@types/eslint-config-prettier": "6.11.3", @@ -1215,6 +1216,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/dompurify": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", @@ -2044,6 +2052,12 @@ "dev": true, "license": "MIT" }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, "node_modules/dompurify": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", @@ -2485,12 +2499,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "license": "Apache-2.0" - }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", diff --git a/web-app/package.json b/web-app/package.json index 79e6551..36386a7 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -19,6 +19,7 @@ "@sveltejs/adapter-node": "5.2.3", "@sveltejs/kit": "2.5.28", "@sveltejs/vite-plugin-svelte": "4.0.0-next.6", + "@types/diff-match-patch": "1.0.36", "@types/eslint": "9.6.1", "@types/eslint__js": "8.42.3", "@types/eslint-config-prettier": "6.11.3", @@ -38,7 +39,7 @@ }, "type": "module", "dependencies": { - "fast-diff": "1.3.0", + "diff-match-patch": "1.0.5", "highlight.js": "11.10.0", "isomorphic-dompurify": "2.15.0", "marked": "14.1.2", diff --git a/web-app/src/lib/components/LoadingSpinner.svelte b/web-app/src/lib/components/LoadingSpinner.svelte index 12ee598..e349122 100644 --- a/web-app/src/lib/components/LoadingSpinner.svelte +++ b/web-app/src/lib/components/LoadingSpinner.svelte @@ -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; } diff --git a/web-app/src/lib/utils.ts b/web-app/src/lib/utils.ts index db03f0d..25e4e90 100644 --- a/web-app/src/lib/utils.ts +++ b/web-app/src/lib/utils.ts @@ -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?: { diff --git a/web-app/src/routes/(anonymous)/register/+page.svelte b/web-app/src/routes/(anonymous)/register/+page.svelte index be38cb9..b8dcc08 100644 --- a/web-app/src/routes/(anonymous)/register/+page.svelte +++ b/web-app/src/routes/(anonymous)/register/+page.svelte @@ -31,27 +31,61 @@ clip-rule="evenodd" > - Account registration is disabled on this instance + Registration is disabled

{:else} -
- - +
+ + + - - + + + +
+ Password requirements +
    +
  • Must be between 12 and 128 characters long
  • +
  • Must contain at least one lowercase letter
  • +
  • Must contain at least one uppercase letter
  • +
  • Must contain at least one number
  • +
  • Must contain at least one special character
  • +
+
+
{/if} diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.server.ts b/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.server.ts index 059a671..5a19a0a 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.server.ts +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.server.ts @@ -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 `${text}`; + case -1: + return `${text}`; + 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) + ) + }; + } +}; diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.svelte index 6cc079e..09ba84c 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.svelte @@ -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 += `${value}`; - break; - case 0: - newString += `${value}`; - break; - case -1: - newString += `${value}`; - 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; +{#if sending.value} + +{/if} + -
+

Logs @@ -153,15 +136,34 @@

Log changes

-

{table_name} — {operation}

+

{table_name} — {operation} — User "{username}"

-
{@html DOMPurify.sanitize(
-                      htmlDiff(oldValue, newValue),
-                      {
-                        ALLOWED_TAGS: ["ins", "del"]
-                      }
-                    )}
+ {#if old_value && new_value} +

Difference

+
+ + +
+ {#if form?.logId === id && form?.currentDiff} +
{@html DOMPurify.sanitize(
+                          form.currentDiff,
+                          { ALLOWED_TAGS: ["ins", "del"] }
+                        )}
+ {/if} + {/if} + + {#if new_value && !old_value} +

New value

+
{@html (DOMPurify.sanitize(newValue),
+                      { ALLOWED_TAGS: ["ins", "del"] })}
+ {/if} + + {#if old_value && !new_value} +

Old value

+
{@html (DOMPurify.sanitize(oldValue),
+                      { ALLOWED_TAGS: ["ins", "del"] })}
+ {/if} @@ -189,7 +191,7 @@ {/snippet}

{$page.url.searchParams.get("logs_results_page") ?? 1} / {Math.max( - Math.ceil(data.resultChangeLogCount / 50), + Math.ceil(data.resultChangeLogCount / 20), 1 )}

@@ -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 @@ Next
{@render commonFilterInputs()} Last
diff --git a/web-app/src/routes/+layout.svelte b/web-app/src/routes/+layout.svelte index 0487799..ce2dd49 100644 --- a/web-app/src/routes/+layout.svelte +++ b/web-app/src/routes/+layout.svelte @@ -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); diff --git a/web-app/template-styles/variables.css b/web-app/template-styles/variables.css index 0dce067..f52f60d 100644 --- a/web-app/template-styles/variables.css +++ b/web-app/template-styles/variables.css @@ -45,7 +45,7 @@ html { --bg-secondary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 5%)); --bg-tertiary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 10%)); --bg-blurred: hsla( - var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) - 20%) + var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) - 50%) ); --color-text: hsl(var(--bg-primary-h) var(--bg-primary-s) 0%); @@ -86,7 +86,7 @@ html:has(#toggle-theme:checked) { --bg-secondary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) + 5%)); --bg-tertiary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) + 10%)); --bg-blurred: hsla( - var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) + 20%) + var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) + 50%) ); --color-text: hsl(var(--bg-primary-h) var(--bg-primary-s) 100%); @@ -128,7 +128,7 @@ html:has(#toggle-theme:checked) { --bg-secondary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) + 5%)); --bg-tertiary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) + 10%)); --bg-blurred: hsla( - var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) + 20%) + var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) + 50%) ); --color-text: hsl(var(--bg-primary-h) var(--bg-primary-s) 100%); @@ -169,7 +169,7 @@ html:has(#toggle-theme:checked) { --bg-secondary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 5%)); --bg-tertiary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 10%)); --bg-blurred: hsla( - var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) - 20%) + var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) - 50%) ); --color-text: hsl(var(--bg-primary-h) var(--bg-primary-s) 0%);