Skip to content
Closed
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
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?logo=typescript)](https://www.typescriptlang.org/)
[![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?logo=docker)](https://github.com/CatholicOS/ontokit-web/pkgs/container/ontokit-web)

The web frontend for OntoKit - a collaborative OWL ontology curation platform.
The web frontend for OntoKit — a collaborative OWL ontology curation platform.

## Genesis

OntoKit grew out of a collaboration between two open-source projects that share a common need: making rules and laws accessible through structured, community-driven ontologies.

- **[FOLIO](https://openlegalstandard.org/)** (Free Open Legal Information Ontology) — a structured vocabulary for governmental rules and laws ([GitHub](https://github.com/alea-institute/FOLIO/))
- **[Catholic Semantic Canon](https://catholicdigitalcommons.org/)** (Catholic Digital Commons) — a structured vocabulary for the rules and laws of faith ([GitHub](https://github.com/CatholicOS/ontology-semantic-canon))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify naming consistency for Catholic Semantic Canon.

Line 10 mixes “Catholic Semantic Canon” with “Catholic Digital Commons” in parentheses, which reads like two different names for one item. Consider using one canonical name (or explicitly stating one is the hosting org/site) to reduce ambiguity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 10, The README entry mixes two names; pick a single
canonical name and indicate the other as the host/site. Update the line that
currently reads "Catholic Semantic Canon (Catholic Digital Commons) — a
structured vocabulary..." to either use "Catholic Semantic Canon — a structured
vocabulary hosted on Catholic Digital Commons
(https://catholicdigitalcommons.org/)..." or "Catholic Digital Commons —
Catholic Semantic Canon, a structured vocabulary..." so that the terms "Catholic
Semantic Canon" and "Catholic Digital Commons" are clearly identified as the
project name and the hosting organization/site respectively.


Both projects benefit from grassroots-level collaborative ontology editing — the kind of tooling that didn't exist in a modern, accessible form. That shared need is what drove OntoKit's creation.

## Features

Expand Down
44 changes: 43 additions & 1 deletion app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useState, useEffect } from "react";
import { useSession } from "next-auth/react";
import Link from "next/link";
import { ArrowLeft, Github, Trash2, Check, AlertCircle, LayoutGrid, Code, Sun, Moon, Monitor, Pencil } from "lucide-react";
import { ArrowLeft, Github, Trash2, Check, AlertCircle, LayoutGrid, Code, Sun, Moon, Monitor, Pencil, Save } from "lucide-react";
import { Header } from "@/components/layout/header";
import { Button } from "@/components/ui/button";
import {
Expand Down Expand Up @@ -329,6 +329,8 @@ function EditorPreferencesSection() {
const setTheme = useEditorModeStore((s) => s.setTheme);
const continuousEditing = useEditorModeStore((s) => s.continuousEditing);
const setContinuousEditing = useEditorModeStore((s) => s.setContinuousEditing);
const manualSave = useEditorModeStore((s) => s.manualSave);
const setManualSave = useEditorModeStore((s) => s.setManualSave);

return (
<section className="rounded-lg border border-slate-200 bg-white p-6 dark:border-slate-700 dark:bg-slate-800">
Expand Down Expand Up @@ -449,6 +451,46 @@ function EditorPreferencesSection() {
</div>
</button>
</div>

{/* Manual Save Button */}
<div className="mt-6">
<span id="manual-save-label" className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
Manual Save Button
</span>
<button
type="button"
role="switch"
aria-checked={manualSave}
aria-labelledby="manual-save-label"
onClick={() => setManualSave(!manualSave)}
className={cn(
"flex items-center gap-3 rounded-lg border p-4 text-left transition-colors w-full",
manualSave
? "border-primary-500 bg-primary-50 dark:border-primary-400 dark:bg-primary-900/20"
: "border-slate-200 hover:border-slate-300 dark:border-slate-600 dark:hover:border-slate-500",
)}
>
<Save className={cn(
"h-5 w-5 flex-shrink-0",
manualSave
? "text-primary-600 dark:text-primary-400"
: "text-slate-400",
)} />
<div>
<p className={cn(
"font-medium",
manualSave
? "text-primary-700 dark:text-primary-300"
: "text-slate-900 dark:text-white",
)}>
{manualSave ? "On" : "Off"}
</p>
<p className="mt-0.5 text-xs text-slate-500 dark:text-slate-400">
Show a Save button in the editor. Auto-save still works in the background &mdash; the button provides an explicit way to save and exit editing.
</p>
</div>
</button>
</div>
</section>
);
}
140 changes: 140 additions & 0 deletions components/editor/AutoSaveAffordanceBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"use client";

import { Cloud, Loader2, Check, AlertTriangle, Save, X } from "lucide-react";
import { cn } from "@/lib/utils";
import { useEditorModeStore } from "@/lib/stores/editorModeStore";
import type { SaveStatus } from "@/lib/hooks/useAutoSave";

interface AutoSaveAffordanceBarProps {
status: SaveStatus;
error?: string | null;
validationError?: string | null;
onRetry?: () => void;
onManualSave?: () => void;
onCancel?: () => void;
}

export function AutoSaveAffordanceBar({
status,
error,
validationError,
onRetry,
onManualSave,
onCancel,
}: AutoSaveAffordanceBarProps) {
const manualSave = useEditorModeStore((s) => s.manualSave);

const effectiveStatus = validationError ? "validationError" : status;

const saveEnabled = manualSave && effectiveStatus === "draft" && !!onManualSave;
const saveSpinning = manualSave && effectiveStatus === "saving";

return (
<div
className={cn(
"border-b px-4 py-2 transition-colors",
effectiveStatus === "error" || effectiveStatus === "validationError"
? "border-red-200 bg-red-50 dark:border-red-900/50 dark:bg-red-900/10"
: effectiveStatus === "draft"
? "border-amber-200 bg-amber-50 dark:border-amber-900/50 dark:bg-amber-900/10"
: effectiveStatus === "saved"
? "border-green-200 bg-green-50 dark:border-green-900/50 dark:bg-green-900/10"
: "border-slate-200 bg-slate-50 dark:border-slate-700 dark:bg-slate-800/50",
)}
role="status"
aria-live={effectiveStatus === "error" || effectiveStatus === "validationError" ? "assertive" : "polite"}
>
<div className="flex items-center justify-between gap-3 min-h-[1.25rem]">
{/* Left side — status */}
<div className="flex items-center gap-2 min-w-0">
{effectiveStatus === "validationError" && (
<span className="text-xs text-red-600 dark:text-red-400">{validationError}</span>
)}

{effectiveStatus === "error" && (
<>
<AlertTriangle className="h-3.5 w-3.5 shrink-0 text-red-500" />
<span className="flex-1 truncate text-xs text-red-600 dark:text-red-400">
{error || "Failed to save"}
</span>
{onRetry && (
<button
onClick={onRetry}
className="shrink-0 text-xs font-medium text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
>
Retry
</button>
)}
</>
)}

{effectiveStatus === "saving" && (
<>
<Loader2 className="h-3.5 w-3.5 animate-spin text-slate-400" />
<span className="text-xs text-slate-500 dark:text-slate-400">Saving...</span>
</>
)}

{effectiveStatus === "saved" && (
<>
<Check className="h-3.5 w-3.5 text-green-500" />
<span className="text-xs text-green-600 dark:text-green-400">Saved</span>
</>
)}

{effectiveStatus === "draft" && (
<>
<span className="h-2 w-2 shrink-0 rounded-full bg-amber-400" />
<span className="text-xs text-amber-600 dark:text-amber-400">Draft saved</span>
</>
)}

{effectiveStatus === "idle" && (
<>
<Cloud className="h-3.5 w-3.5 text-slate-400 dark:text-slate-500" />
<span
className="text-xs text-slate-400 dark:text-slate-500"
title="Changes save as a local draft on blur, and commit to git when you navigate away"
>
Auto-save on
</span>
</>
)}
</div>

{/* Right side — save + cancel buttons */}
<div className="flex shrink-0 items-center gap-2">
{manualSave && (
<button
onClick={saveEnabled ? onManualSave : undefined}
disabled={!saveEnabled}
className={cn(
"flex shrink-0 items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors",
saveEnabled
? "bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-500 dark:hover:bg-primary-600"
: "cursor-not-allowed bg-slate-200 text-slate-400 dark:bg-slate-700 dark:text-slate-500",
)}
>
{saveSpinning ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<Save className="h-3.5 w-3.5" />
)}
Save
</button>
)}
{onCancel && (
<button
onClick={onCancel}
className="flex shrink-0 items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium text-slate-600 hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-700"
title="Discard changes"
>
<X className="h-3.5 w-3.5" />
Cancel
</button>
)}
</div>
</div>
</div>
);
}
82 changes: 0 additions & 82 deletions components/editor/AutoSaveStatusBar.tsx

This file was deleted.

Loading
Loading