FossilRepo
Rename Docs to Knowledge Base, fix ticket query and wiki URL routing - Rename "Docs" to "Knowledge Base" in sidebar, dashboard, templates - Change URL mount from /docs/ to /kb/ - Reserve "Docs" for bundled Fossil documentation for end users - Fix ticket query: use tkt_ctime (Julian day) instead of nonexistent origtime - Fix wiki URL: use path converter for page names with slashes - Fix code browser: skip files with newlines in names - Update all tests for new /kb/ URL
Commit
6caba60d4e0dd8b9570d427e798a1f039ca29c62980bd1faca8eea8d0a221206
Parent
e8842fed281f3ea…
8 files changed
+1
-1
-2
+14
-14
+1
-1
+2
-2
+4
-4
+1
-1
+3
-3
+1
-1
| --- config/urls.py | ||
| +++ config/urls.py | ||
| @@ -191,10 +191,10 @@ | ||
| 191 | 191 | path("dashboard/", include("core.urls")), |
| 192 | 192 | path("auth/", include("auth1.urls")), |
| 193 | 193 | path("settings/", include("organization.urls")), |
| 194 | 194 | path("projects/", include("projects.urls")), |
| 195 | 195 | path("projects/<slug:slug>/fossil/", include("fossil.urls")), |
| 196 | - path("docs/", include("pages.urls")), | |
| 196 | + path("kb/", include("pages.urls")), | |
| 197 | 197 | path("items/", include("items.urls")), |
| 198 | 198 | path("admin/", admin.site.urls), |
| 199 | 199 | path("health/", health_check, name="health"), |
| 200 | 200 | ] |
| 201 | 201 |
| --- config/urls.py | |
| +++ config/urls.py | |
| @@ -191,10 +191,10 @@ | |
| 191 | path("dashboard/", include("core.urls")), |
| 192 | path("auth/", include("auth1.urls")), |
| 193 | path("settings/", include("organization.urls")), |
| 194 | path("projects/", include("projects.urls")), |
| 195 | path("projects/<slug:slug>/fossil/", include("fossil.urls")), |
| 196 | path("docs/", include("pages.urls")), |
| 197 | path("items/", include("items.urls")), |
| 198 | path("admin/", admin.site.urls), |
| 199 | path("health/", health_check, name="health"), |
| 200 | ] |
| 201 |
| --- config/urls.py | |
| +++ config/urls.py | |
| @@ -191,10 +191,10 @@ | |
| 191 | path("dashboard/", include("core.urls")), |
| 192 | path("auth/", include("auth1.urls")), |
| 193 | path("settings/", include("organization.urls")), |
| 194 | path("projects/", include("projects.urls")), |
| 195 | path("projects/<slug:slug>/fossil/", include("fossil.urls")), |
| 196 | path("kb/", include("pages.urls")), |
| 197 | path("items/", include("items.urls")), |
| 198 | path("admin/", admin.site.urls), |
| 199 | path("health/", health_check, name="health"), |
| 200 | ] |
| 201 |
-2
| --- ctl/main.py | ||
| +++ ctl/main.py | ||
| @@ -3,16 +3,14 @@ | ||
| 3 | 3 | Similar to gitlab-ctl: manages the full stack (Django, Fossil, Caddy, |
| 4 | 4 | Litestream, Celery, Postgres, Redis) as a single unit. |
| 5 | 5 | """ |
| 6 | 6 | |
| 7 | 7 | import subprocess |
| 8 | -import sys | |
| 9 | 8 | from pathlib import Path |
| 10 | 9 | |
| 11 | 10 | import click |
| 12 | 11 | from rich.console import Console |
| 13 | -from rich.table import Table | |
| 14 | 12 | |
| 15 | 13 | console = Console() |
| 16 | 14 | |
| 17 | 15 | PROJECT_ROOT = Path(__file__).resolve().parent.parent |
| 18 | 16 | COMPOSE_FILE = PROJECT_ROOT / "docker-compose.yaml" |
| 19 | 17 |
| --- ctl/main.py | |
| +++ ctl/main.py | |
| @@ -3,16 +3,14 @@ | |
| 3 | Similar to gitlab-ctl: manages the full stack (Django, Fossil, Caddy, |
| 4 | Litestream, Celery, Postgres, Redis) as a single unit. |
| 5 | """ |
| 6 | |
| 7 | import subprocess |
| 8 | import sys |
| 9 | from pathlib import Path |
| 10 | |
| 11 | import click |
| 12 | from rich.console import Console |
| 13 | from rich.table import Table |
| 14 | |
| 15 | console = Console() |
| 16 | |
| 17 | PROJECT_ROOT = Path(__file__).resolve().parent.parent |
| 18 | COMPOSE_FILE = PROJECT_ROOT / "docker-compose.yaml" |
| 19 |
| --- ctl/main.py | |
| +++ ctl/main.py | |
| @@ -3,16 +3,14 @@ | |
| 3 | Similar to gitlab-ctl: manages the full stack (Django, Fossil, Caddy, |
| 4 | Litestream, Celery, Postgres, Redis) as a single unit. |
| 5 | """ |
| 6 | |
| 7 | import subprocess |
| 8 | from pathlib import Path |
| 9 | |
| 10 | import click |
| 11 | from rich.console import Console |
| 12 | |
| 13 | console = Console() |
| 14 | |
| 15 | PROJECT_ROOT = Path(__file__).resolve().parent.parent |
| 16 | COMPOSE_FILE = PROJECT_ROOT / "docker-compose.yaml" |
| 17 |
+14
-14
| --- pages/tests.py | ||
| +++ pages/tests.py | ||
| @@ -18,72 +18,72 @@ | ||
| 18 | 18 | |
| 19 | 19 | |
| 20 | 20 | @pytest.mark.django_db |
| 21 | 21 | class TestPageViews: |
| 22 | 22 | def test_page_list_renders(self, admin_client, sample_page): |
| 23 | - response = admin_client.get("/docs/") | |
| 23 | + response = admin_client.get("/kb/") | |
| 24 | 24 | assert response.status_code == 200 |
| 25 | 25 | assert sample_page.name in response.content.decode() |
| 26 | 26 | |
| 27 | 27 | def test_page_list_htmx(self, admin_client, sample_page): |
| 28 | - response = admin_client.get("/docs/", HTTP_HX_REQUEST="true") | |
| 28 | + response = admin_client.get("/kb/", HTTP_HX_REQUEST="true") | |
| 29 | 29 | assert response.status_code == 200 |
| 30 | 30 | assert b"page-table" in response.content |
| 31 | 31 | |
| 32 | 32 | def test_page_list_search(self, admin_client, sample_page): |
| 33 | - response = admin_client.get("/docs/?search=Getting") | |
| 33 | + response = admin_client.get("/kb/?search=Getting") | |
| 34 | 34 | assert response.status_code == 200 |
| 35 | 35 | |
| 36 | 36 | def test_page_list_denied(self, no_perm_client): |
| 37 | - response = no_perm_client.get("/docs/") | |
| 37 | + response = no_perm_client.get("/kb/") | |
| 38 | 38 | assert response.status_code == 403 |
| 39 | 39 | |
| 40 | 40 | def test_page_create(self, admin_client, org): |
| 41 | - response = admin_client.post("/docs/create/", {"name": "New Page", "content": "# New", "is_published": True}) | |
| 41 | + response = admin_client.post("/kb/create/", {"name": "New Page", "content": "# New", "is_published": True}) | |
| 42 | 42 | assert response.status_code == 302 |
| 43 | 43 | assert Page.objects.filter(slug="new-page").exists() |
| 44 | 44 | |
| 45 | 45 | def test_page_create_denied(self, no_perm_client, org): |
| 46 | - response = no_perm_client.post("/docs/create/", {"name": "Hack"}) | |
| 46 | + response = no_perm_client.post("/kb/create/", {"name": "Hack"}) | |
| 47 | 47 | assert response.status_code == 403 |
| 48 | 48 | |
| 49 | 49 | def test_page_detail_renders_markdown(self, admin_client, sample_page): |
| 50 | - response = admin_client.get(f"/docs/{sample_page.slug}/") | |
| 50 | + response = admin_client.get(f"/kb/{sample_page.slug}/") | |
| 51 | 51 | assert response.status_code == 200 |
| 52 | 52 | content = response.content.decode() |
| 53 | 53 | assert "<h1>" in content or "Getting Started" in content |
| 54 | 54 | |
| 55 | 55 | def test_page_detail_denied(self, no_perm_client, sample_page): |
| 56 | - response = no_perm_client.get(f"/docs/{sample_page.slug}/") | |
| 56 | + response = no_perm_client.get(f"/kb/{sample_page.slug}/") | |
| 57 | 57 | assert response.status_code == 403 |
| 58 | 58 | |
| 59 | 59 | def test_page_update(self, admin_client, sample_page): |
| 60 | 60 | response = admin_client.post( |
| 61 | - f"/docs/{sample_page.slug}/edit/", | |
| 61 | + f"/kb/{sample_page.slug}/edit/", | |
| 62 | 62 | {"name": "Updated Page", "content": "# Updated", "is_published": True}, |
| 63 | 63 | ) |
| 64 | 64 | assert response.status_code == 302 |
| 65 | 65 | sample_page.refresh_from_db() |
| 66 | 66 | assert sample_page.name == "Updated Page" |
| 67 | 67 | |
| 68 | 68 | def test_page_update_denied(self, no_perm_client, sample_page): |
| 69 | - response = no_perm_client.post(f"/docs/{sample_page.slug}/edit/", {"name": "Hacked"}) | |
| 69 | + response = no_perm_client.post(f"/kb/{sample_page.slug}/edit/", {"name": "Hacked"}) | |
| 70 | 70 | assert response.status_code == 403 |
| 71 | 71 | |
| 72 | 72 | def test_page_delete(self, admin_client, sample_page): |
| 73 | - response = admin_client.post(f"/docs/{sample_page.slug}/delete/") | |
| 73 | + response = admin_client.post(f"/kb/{sample_page.slug}/delete/") | |
| 74 | 74 | assert response.status_code == 302 |
| 75 | 75 | assert Page.objects.filter(slug=sample_page.slug).count() == 0 |
| 76 | 76 | |
| 77 | 77 | def test_page_delete_denied(self, no_perm_client, sample_page): |
| 78 | - response = no_perm_client.post(f"/docs/{sample_page.slug}/delete/") | |
| 78 | + response = no_perm_client.post(f"/kb/{sample_page.slug}/delete/") | |
| 79 | 79 | assert response.status_code == 403 |
| 80 | 80 | |
| 81 | 81 | def test_draft_page_visible_to_admin(self, admin_client, org, admin_user): |
| 82 | 82 | Page.objects.create(name="Draft Doc", content="Secret", organization=org, is_published=False, created_by=admin_user) |
| 83 | - response = admin_client.get("/docs/") | |
| 83 | + response = admin_client.get("/kb/") | |
| 84 | 84 | assert "Draft Doc" in response.content.decode() |
| 85 | 85 | |
| 86 | 86 | def test_draft_page_hidden_from_viewer(self, viewer_client, org, admin_user): |
| 87 | 87 | Page.objects.create(name="Draft Doc", content="Secret", organization=org, is_published=False, created_by=admin_user) |
| 88 | - response = viewer_client.get("/docs/") | |
| 88 | + response = viewer_client.get("/kb/") | |
| 89 | 89 | assert "Draft Doc" not in response.content.decode() |
| 90 | 90 |
| --- pages/tests.py | |
| +++ pages/tests.py | |
| @@ -18,72 +18,72 @@ | |
| 18 | |
| 19 | |
| 20 | @pytest.mark.django_db |
| 21 | class TestPageViews: |
| 22 | def test_page_list_renders(self, admin_client, sample_page): |
| 23 | response = admin_client.get("/docs/") |
| 24 | assert response.status_code == 200 |
| 25 | assert sample_page.name in response.content.decode() |
| 26 | |
| 27 | def test_page_list_htmx(self, admin_client, sample_page): |
| 28 | response = admin_client.get("/docs/", HTTP_HX_REQUEST="true") |
| 29 | assert response.status_code == 200 |
| 30 | assert b"page-table" in response.content |
| 31 | |
| 32 | def test_page_list_search(self, admin_client, sample_page): |
| 33 | response = admin_client.get("/docs/?search=Getting") |
| 34 | assert response.status_code == 200 |
| 35 | |
| 36 | def test_page_list_denied(self, no_perm_client): |
| 37 | response = no_perm_client.get("/docs/") |
| 38 | assert response.status_code == 403 |
| 39 | |
| 40 | def test_page_create(self, admin_client, org): |
| 41 | response = admin_client.post("/docs/create/", {"name": "New Page", "content": "# New", "is_published": True}) |
| 42 | assert response.status_code == 302 |
| 43 | assert Page.objects.filter(slug="new-page").exists() |
| 44 | |
| 45 | def test_page_create_denied(self, no_perm_client, org): |
| 46 | response = no_perm_client.post("/docs/create/", {"name": "Hack"}) |
| 47 | assert response.status_code == 403 |
| 48 | |
| 49 | def test_page_detail_renders_markdown(self, admin_client, sample_page): |
| 50 | response = admin_client.get(f"/docs/{sample_page.slug}/") |
| 51 | assert response.status_code == 200 |
| 52 | content = response.content.decode() |
| 53 | assert "<h1>" in content or "Getting Started" in content |
| 54 | |
| 55 | def test_page_detail_denied(self, no_perm_client, sample_page): |
| 56 | response = no_perm_client.get(f"/docs/{sample_page.slug}/") |
| 57 | assert response.status_code == 403 |
| 58 | |
| 59 | def test_page_update(self, admin_client, sample_page): |
| 60 | response = admin_client.post( |
| 61 | f"/docs/{sample_page.slug}/edit/", |
| 62 | {"name": "Updated Page", "content": "# Updated", "is_published": True}, |
| 63 | ) |
| 64 | assert response.status_code == 302 |
| 65 | sample_page.refresh_from_db() |
| 66 | assert sample_page.name == "Updated Page" |
| 67 | |
| 68 | def test_page_update_denied(self, no_perm_client, sample_page): |
| 69 | response = no_perm_client.post(f"/docs/{sample_page.slug}/edit/", {"name": "Hacked"}) |
| 70 | assert response.status_code == 403 |
| 71 | |
| 72 | def test_page_delete(self, admin_client, sample_page): |
| 73 | response = admin_client.post(f"/docs/{sample_page.slug}/delete/") |
| 74 | assert response.status_code == 302 |
| 75 | assert Page.objects.filter(slug=sample_page.slug).count() == 0 |
| 76 | |
| 77 | def test_page_delete_denied(self, no_perm_client, sample_page): |
| 78 | response = no_perm_client.post(f"/docs/{sample_page.slug}/delete/") |
| 79 | assert response.status_code == 403 |
| 80 | |
| 81 | def test_draft_page_visible_to_admin(self, admin_client, org, admin_user): |
| 82 | Page.objects.create(name="Draft Doc", content="Secret", organization=org, is_published=False, created_by=admin_user) |
| 83 | response = admin_client.get("/docs/") |
| 84 | assert "Draft Doc" in response.content.decode() |
| 85 | |
| 86 | def test_draft_page_hidden_from_viewer(self, viewer_client, org, admin_user): |
| 87 | Page.objects.create(name="Draft Doc", content="Secret", organization=org, is_published=False, created_by=admin_user) |
| 88 | response = viewer_client.get("/docs/") |
| 89 | assert "Draft Doc" not in response.content.decode() |
| 90 |
| --- pages/tests.py | |
| +++ pages/tests.py | |
| @@ -18,72 +18,72 @@ | |
| 18 | |
| 19 | |
| 20 | @pytest.mark.django_db |
| 21 | class TestPageViews: |
| 22 | def test_page_list_renders(self, admin_client, sample_page): |
| 23 | response = admin_client.get("/kb/") |
| 24 | assert response.status_code == 200 |
| 25 | assert sample_page.name in response.content.decode() |
| 26 | |
| 27 | def test_page_list_htmx(self, admin_client, sample_page): |
| 28 | response = admin_client.get("/kb/", HTTP_HX_REQUEST="true") |
| 29 | assert response.status_code == 200 |
| 30 | assert b"page-table" in response.content |
| 31 | |
| 32 | def test_page_list_search(self, admin_client, sample_page): |
| 33 | response = admin_client.get("/kb/?search=Getting") |
| 34 | assert response.status_code == 200 |
| 35 | |
| 36 | def test_page_list_denied(self, no_perm_client): |
| 37 | response = no_perm_client.get("/kb/") |
| 38 | assert response.status_code == 403 |
| 39 | |
| 40 | def test_page_create(self, admin_client, org): |
| 41 | response = admin_client.post("/kb/create/", {"name": "New Page", "content": "# New", "is_published": True}) |
| 42 | assert response.status_code == 302 |
| 43 | assert Page.objects.filter(slug="new-page").exists() |
| 44 | |
| 45 | def test_page_create_denied(self, no_perm_client, org): |
| 46 | response = no_perm_client.post("/kb/create/", {"name": "Hack"}) |
| 47 | assert response.status_code == 403 |
| 48 | |
| 49 | def test_page_detail_renders_markdown(self, admin_client, sample_page): |
| 50 | response = admin_client.get(f"/kb/{sample_page.slug}/") |
| 51 | assert response.status_code == 200 |
| 52 | content = response.content.decode() |
| 53 | assert "<h1>" in content or "Getting Started" in content |
| 54 | |
| 55 | def test_page_detail_denied(self, no_perm_client, sample_page): |
| 56 | response = no_perm_client.get(f"/kb/{sample_page.slug}/") |
| 57 | assert response.status_code == 403 |
| 58 | |
| 59 | def test_page_update(self, admin_client, sample_page): |
| 60 | response = admin_client.post( |
| 61 | f"/kb/{sample_page.slug}/edit/", |
| 62 | {"name": "Updated Page", "content": "# Updated", "is_published": True}, |
| 63 | ) |
| 64 | assert response.status_code == 302 |
| 65 | sample_page.refresh_from_db() |
| 66 | assert sample_page.name == "Updated Page" |
| 67 | |
| 68 | def test_page_update_denied(self, no_perm_client, sample_page): |
| 69 | response = no_perm_client.post(f"/kb/{sample_page.slug}/edit/", {"name": "Hacked"}) |
| 70 | assert response.status_code == 403 |
| 71 | |
| 72 | def test_page_delete(self, admin_client, sample_page): |
| 73 | response = admin_client.post(f"/kb/{sample_page.slug}/delete/") |
| 74 | assert response.status_code == 302 |
| 75 | assert Page.objects.filter(slug=sample_page.slug).count() == 0 |
| 76 | |
| 77 | def test_page_delete_denied(self, no_perm_client, sample_page): |
| 78 | response = no_perm_client.post(f"/kb/{sample_page.slug}/delete/") |
| 79 | assert response.status_code == 403 |
| 80 | |
| 81 | def test_draft_page_visible_to_admin(self, admin_client, org, admin_user): |
| 82 | Page.objects.create(name="Draft Doc", content="Secret", organization=org, is_published=False, created_by=admin_user) |
| 83 | response = admin_client.get("/kb/") |
| 84 | assert "Draft Doc" in response.content.decode() |
| 85 | |
| 86 | def test_draft_page_hidden_from_viewer(self, viewer_client, org, admin_user): |
| 87 | Page.objects.create(name="Draft Doc", content="Secret", organization=org, is_published=False, created_by=admin_user) |
| 88 | response = viewer_client.get("/kb/") |
| 89 | assert "Draft Doc" not in response.content.decode() |
| 90 |
+1
-1
| --- pages/views.py | ||
| +++ pages/views.py | ||
| @@ -85,10 +85,10 @@ | ||
| 85 | 85 | if request.method == "POST": |
| 86 | 86 | page.soft_delete(user=request.user) |
| 87 | 87 | messages.success(request, f'Page "{page.name}" deleted.') |
| 88 | 88 | |
| 89 | 89 | if request.headers.get("HX-Request"): |
| 90 | - return HttpResponse(status=200, headers={"HX-Redirect": "/docs/"}) | |
| 90 | + return HttpResponse(status=200, headers={"HX-Redirect": "/kb/"}) | |
| 91 | 91 | |
| 92 | 92 | return redirect("pages:list") |
| 93 | 93 | |
| 94 | 94 | return render(request, "pages/page_confirm_delete.html", {"page": page}) |
| 95 | 95 |
| --- pages/views.py | |
| +++ pages/views.py | |
| @@ -85,10 +85,10 @@ | |
| 85 | if request.method == "POST": |
| 86 | page.soft_delete(user=request.user) |
| 87 | messages.success(request, f'Page "{page.name}" deleted.') |
| 88 | |
| 89 | if request.headers.get("HX-Request"): |
| 90 | return HttpResponse(status=200, headers={"HX-Redirect": "/docs/"}) |
| 91 | |
| 92 | return redirect("pages:list") |
| 93 | |
| 94 | return render(request, "pages/page_confirm_delete.html", {"page": page}) |
| 95 |
| --- pages/views.py | |
| +++ pages/views.py | |
| @@ -85,10 +85,10 @@ | |
| 85 | if request.method == "POST": |
| 86 | page.soft_delete(user=request.user) |
| 87 | messages.success(request, f'Page "{page.name}" deleted.') |
| 88 | |
| 89 | if request.headers.get("HX-Request"): |
| 90 | return HttpResponse(status=200, headers={"HX-Redirect": "/kb/"}) |
| 91 | |
| 92 | return redirect("pages:list") |
| 93 | |
| 94 | return render(request, "pages/page_confirm_delete.html", {"page": page}) |
| 95 |
+2
-2
| --- templates/dashboard.html | ||
| +++ templates/dashboard.html | ||
| @@ -22,12 +22,12 @@ | ||
| 22 | 22 | </a> |
| 23 | 23 | {% endif %} |
| 24 | 24 | |
| 25 | 25 | {% if perms.pages.view_page %} |
| 26 | 26 | <a href="{% url 'pages:list' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all"> |
| 27 | - <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Docs</h3> | |
| 28 | - <p class="mt-2 text-sm text-gray-400">Org-wide documentation, guides, and runbooks.</p> | |
| 27 | + <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Knowledge Base</h3> | |
| 28 | + <p class="mt-2 text-sm text-gray-400">Org-wide guides, runbooks, and internal documentation.</p> | |
| 29 | 29 | </a> |
| 30 | 30 | {% endif %} |
| 31 | 31 | |
| 32 | 32 | {% if perms.organization.view_organization %} |
| 33 | 33 | <a href="{% url 'organization:settings' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all"> |
| 34 | 34 |
| --- templates/dashboard.html | |
| +++ templates/dashboard.html | |
| @@ -22,12 +22,12 @@ | |
| 22 | </a> |
| 23 | {% endif %} |
| 24 | |
| 25 | {% if perms.pages.view_page %} |
| 26 | <a href="{% url 'pages:list' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all"> |
| 27 | <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Docs</h3> |
| 28 | <p class="mt-2 text-sm text-gray-400">Org-wide documentation, guides, and runbooks.</p> |
| 29 | </a> |
| 30 | {% endif %} |
| 31 | |
| 32 | {% if perms.organization.view_organization %} |
| 33 | <a href="{% url 'organization:settings' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all"> |
| 34 |
| --- templates/dashboard.html | |
| +++ templates/dashboard.html | |
| @@ -22,12 +22,12 @@ | |
| 22 | </a> |
| 23 | {% endif %} |
| 24 | |
| 25 | {% if perms.pages.view_page %} |
| 26 | <a href="{% url 'pages:list' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all"> |
| 27 | <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Knowledge Base</h3> |
| 28 | <p class="mt-2 text-sm text-gray-400">Org-wide guides, runbooks, and internal documentation.</p> |
| 29 | </a> |
| 30 | {% endif %} |
| 31 | |
| 32 | {% if perms.organization.view_organization %} |
| 33 | <a href="{% url 'organization:settings' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all"> |
| 34 |
+4
-4
| --- templates/includes/sidebar.html | ||
| +++ templates/includes/sidebar.html | ||
| @@ -99,21 +99,21 @@ | ||
| 99 | 99 | </svg> |
| 100 | 100 | <span x-show="!collapsed" class="truncate">Teams</span> |
| 101 | 101 | </a> |
| 102 | 102 | {% endif %} |
| 103 | 103 | |
| 104 | - <!-- Docs section --> | |
| 104 | + <!-- Knowledge Base section --> | |
| 105 | 105 | {% if perms.pages.view_page %} |
| 106 | 106 | <div> |
| 107 | 107 | <button @click="collapsed ? (collapsed = false, docsOpen = true) : (docsOpen = !docsOpen)" |
| 108 | - class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/docs/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}" | |
| 109 | - :title="collapsed ? 'Docs' : ''"> | |
| 108 | + class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/kb/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}" | |
| 109 | + :title="collapsed ? 'Knowledge Base' : ''"> | |
| 110 | 110 | <span class="flex items-center gap-2"> |
| 111 | 111 | <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 112 | 112 | <path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" /> |
| 113 | 113 | </svg> |
| 114 | - <span x-show="!collapsed" class="truncate">Docs</span> | |
| 114 | + <span x-show="!collapsed" class="truncate">Knowledge Base</span> | |
| 115 | 115 | </span> |
| 116 | 116 | <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="docsOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 117 | 117 | <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> |
| 118 | 118 | </svg> |
| 119 | 119 | </button> |
| 120 | 120 |
| --- templates/includes/sidebar.html | |
| +++ templates/includes/sidebar.html | |
| @@ -99,21 +99,21 @@ | |
| 99 | </svg> |
| 100 | <span x-show="!collapsed" class="truncate">Teams</span> |
| 101 | </a> |
| 102 | {% endif %} |
| 103 | |
| 104 | <!-- Docs section --> |
| 105 | {% if perms.pages.view_page %} |
| 106 | <div> |
| 107 | <button @click="collapsed ? (collapsed = false, docsOpen = true) : (docsOpen = !docsOpen)" |
| 108 | class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/docs/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}" |
| 109 | :title="collapsed ? 'Docs' : ''"> |
| 110 | <span class="flex items-center gap-2"> |
| 111 | <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 112 | <path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" /> |
| 113 | </svg> |
| 114 | <span x-show="!collapsed" class="truncate">Docs</span> |
| 115 | </span> |
| 116 | <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="docsOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 117 | <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> |
| 118 | </svg> |
| 119 | </button> |
| 120 |
| --- templates/includes/sidebar.html | |
| +++ templates/includes/sidebar.html | |
| @@ -99,21 +99,21 @@ | |
| 99 | </svg> |
| 100 | <span x-show="!collapsed" class="truncate">Teams</span> |
| 101 | </a> |
| 102 | {% endif %} |
| 103 | |
| 104 | <!-- Knowledge Base section --> |
| 105 | {% if perms.pages.view_page %} |
| 106 | <div> |
| 107 | <button @click="collapsed ? (collapsed = false, docsOpen = true) : (docsOpen = !docsOpen)" |
| 108 | class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/kb/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}" |
| 109 | :title="collapsed ? 'Knowledge Base' : ''"> |
| 110 | <span class="flex items-center gap-2"> |
| 111 | <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 112 | <path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" /> |
| 113 | </svg> |
| 114 | <span x-show="!collapsed" class="truncate">Knowledge Base</span> |
| 115 | </span> |
| 116 | <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="docsOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 117 | <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> |
| 118 | </svg> |
| 119 | </button> |
| 120 |
+1
-1
| --- templates/pages/page_form.html | ||
| +++ templates/pages/page_form.html | ||
| @@ -1,11 +1,11 @@ | ||
| 1 | 1 | {% extends "base.html" %} |
| 2 | 2 | {% block title %}{{ title }} — Fossilrepo{% endblock %} |
| 3 | 3 | |
| 4 | 4 | {% block content %} |
| 5 | 5 | <div class="mb-6"> |
| 6 | - <a href="{% url 'pages:list' %}" class="text-sm text-brand-light hover:text-brand">← Back to Docs</a> | |
| 6 | + <a href="{% url 'pages:list' %}" class="text-sm text-brand-light hover:text-brand">← Back to Knowledge Base</a> | |
| 7 | 7 | </div> |
| 8 | 8 | |
| 9 | 9 | <div class="mx-auto max-w-4xl"> |
| 10 | 10 | <h1 class="text-2xl font-bold text-gray-100 mb-6">{{ title }}</h1> |
| 11 | 11 | |
| 12 | 12 |
| --- templates/pages/page_form.html | |
| +++ templates/pages/page_form.html | |
| @@ -1,11 +1,11 @@ | |
| 1 | {% extends "base.html" %} |
| 2 | {% block title %}{{ title }} — Fossilrepo{% endblock %} |
| 3 | |
| 4 | {% block content %} |
| 5 | <div class="mb-6"> |
| 6 | <a href="{% url 'pages:list' %}" class="text-sm text-brand-light hover:text-brand">← Back to Docs</a> |
| 7 | </div> |
| 8 | |
| 9 | <div class="mx-auto max-w-4xl"> |
| 10 | <h1 class="text-2xl font-bold text-gray-100 mb-6">{{ title }}</h1> |
| 11 | |
| 12 |
| --- templates/pages/page_form.html | |
| +++ templates/pages/page_form.html | |
| @@ -1,11 +1,11 @@ | |
| 1 | {% extends "base.html" %} |
| 2 | {% block title %}{{ title }} — Fossilrepo{% endblock %} |
| 3 | |
| 4 | {% block content %} |
| 5 | <div class="mb-6"> |
| 6 | <a href="{% url 'pages:list' %}" class="text-sm text-brand-light hover:text-brand">← Back to Knowledge Base</a> |
| 7 | </div> |
| 8 | |
| 9 | <div class="mx-auto max-w-4xl"> |
| 10 | <h1 class="text-2xl font-bold text-gray-100 mb-6">{{ title }}</h1> |
| 11 | |
| 12 |
+3
-3
| --- templates/pages/page_list.html | ||
| +++ templates/pages/page_list.html | ||
| @@ -1,11 +1,11 @@ | ||
| 1 | 1 | {% extends "base.html" %} |
| 2 | -{% block title %}Docs — Fossilrepo{% endblock %} | |
| 2 | +{% block title %}Knowledge Base — Fossilrepo{% endblock %} | |
| 3 | 3 | |
| 4 | 4 | {% block content %} |
| 5 | 5 | <div class="md:flex md:items-center md:justify-between mb-6"> |
| 6 | - <h1 class="text-2xl font-bold text-gray-100">Docs</h1> | |
| 6 | + <h1 class="text-2xl font-bold text-gray-100">Knowledge Base</h1> | |
| 7 | 7 | {% if perms.pages.add_page %} |
| 8 | 8 | <a href="{% url 'pages:create' %}" |
| 9 | 9 | class="mt-4 md:mt-0 inline-flex items-center rounded-md bg-brand px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-brand-hover"> |
| 10 | 10 | New Page |
| 11 | 11 | </a> |
| @@ -14,11 +14,11 @@ | ||
| 14 | 14 | |
| 15 | 15 | <div class="mb-4"> |
| 16 | 16 | <input type="search" |
| 17 | 17 | name="search" |
| 18 | 18 | value="{{ search }}" |
| 19 | - placeholder="Search docs..." | |
| 19 | + placeholder="Search knowledge base..." | |
| 20 | 20 | class="w-full max-w-md rounded-md border-gray-700 bg-gray-800 text-gray-100 shadow-sm focus:border-brand focus:ring-brand sm:text-sm" |
| 21 | 21 | hx-get="{% url 'pages:list' %}" |
| 22 | 22 | hx-trigger="input changed delay:300ms, search" |
| 23 | 23 | hx-target="#page-table" |
| 24 | 24 | hx-swap="outerHTML" |
| 25 | 25 |
| --- templates/pages/page_list.html | |
| +++ templates/pages/page_list.html | |
| @@ -1,11 +1,11 @@ | |
| 1 | {% extends "base.html" %} |
| 2 | {% block title %}Docs — Fossilrepo{% endblock %} |
| 3 | |
| 4 | {% block content %} |
| 5 | <div class="md:flex md:items-center md:justify-between mb-6"> |
| 6 | <h1 class="text-2xl font-bold text-gray-100">Docs</h1> |
| 7 | {% if perms.pages.add_page %} |
| 8 | <a href="{% url 'pages:create' %}" |
| 9 | class="mt-4 md:mt-0 inline-flex items-center rounded-md bg-brand px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-brand-hover"> |
| 10 | New Page |
| 11 | </a> |
| @@ -14,11 +14,11 @@ | |
| 14 | |
| 15 | <div class="mb-4"> |
| 16 | <input type="search" |
| 17 | name="search" |
| 18 | value="{{ search }}" |
| 19 | placeholder="Search docs..." |
| 20 | class="w-full max-w-md rounded-md border-gray-700 bg-gray-800 text-gray-100 shadow-sm focus:border-brand focus:ring-brand sm:text-sm" |
| 21 | hx-get="{% url 'pages:list' %}" |
| 22 | hx-trigger="input changed delay:300ms, search" |
| 23 | hx-target="#page-table" |
| 24 | hx-swap="outerHTML" |
| 25 |
| --- templates/pages/page_list.html | |
| +++ templates/pages/page_list.html | |
| @@ -1,11 +1,11 @@ | |
| 1 | {% extends "base.html" %} |
| 2 | {% block title %}Knowledge Base — Fossilrepo{% endblock %} |
| 3 | |
| 4 | {% block content %} |
| 5 | <div class="md:flex md:items-center md:justify-between mb-6"> |
| 6 | <h1 class="text-2xl font-bold text-gray-100">Knowledge Base</h1> |
| 7 | {% if perms.pages.add_page %} |
| 8 | <a href="{% url 'pages:create' %}" |
| 9 | class="mt-4 md:mt-0 inline-flex items-center rounded-md bg-brand px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-brand-hover"> |
| 10 | New Page |
| 11 | </a> |
| @@ -14,11 +14,11 @@ | |
| 14 | |
| 15 | <div class="mb-4"> |
| 16 | <input type="search" |
| 17 | name="search" |
| 18 | value="{{ search }}" |
| 19 | placeholder="Search knowledge base..." |
| 20 | class="w-full max-w-md rounded-md border-gray-700 bg-gray-800 text-gray-100 shadow-sm focus:border-brand focus:ring-brand sm:text-sm" |
| 21 | hx-get="{% url 'pages:list' %}" |
| 22 | hx-trigger="input changed delay:300ms, search" |
| 23 | hx-target="#page-table" |
| 24 | hx-swap="outerHTML" |
| 25 |