Initial commit

This commit is contained in:
Nathan Schneider
2025-03-23 21:44:39 -06:00
commit 521c782c42
56 changed files with 8295 additions and 0 deletions

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2025 YOUR_NAME_HERE
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,2 @@
+++
+++

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
{{- partial "head.html" . -}}
<body>
{{- partial "header.html" . -}}
<div id="content">
{{- block "main" . }}{{- end }}
</div>
{{- partial "footer.html" . -}}
</body>
</html>

View File

@ -0,0 +1,290 @@
{{ define "main" }}
<div class="builder-container">
<h1>{{ .Title }}</h1>
{{ .Content }}
<div id="protocol-builder" class="builder">
<div class="builder-main">
<div class="protocol-metadata">
<div class="metadata-field">
<label for="community-name">Community Name:</label>
<input type="text" id="community-name" name="community-name" placeholder="Enter your community name...">
</div>
<div class="metadata-field">
<label for="protocol-summary">Protocol Summary:</label>
<textarea id="protocol-summary" name="protocol-summary" placeholder="Briefly describe this dispute resolution protocol and its purpose..."></textarea>
</div>
</div>
<div class="protocol-template-selector">
<label for="protocol-template">Select a Protocol Template:</label>
<select id="protocol-template" class="protocol-template-select">
<option value="">-- Create Your Own Protocol --</option>
<!-- Template options will be populated by JavaScript -->
</select>
</div>
<div class="builder-content">
{{ range $.Site.Data.stages.stages }}
<div class="stage-section" id="stage-{{ .id }}">
<div class="stage-header" data-stage="{{ .id }}">
<div class="stage-header-content">
<h2>{{ .title }}</h2>
<p class="stage-brief">{{ .description }}</p>
</div>
<button type="button" class="toggle-btn" aria-label="Toggle section" onclick="toggleStage(this)">+</button>
</div>
<div class="stage-body">
<div class="components">
{{ $stageId := .id }}
{{ $componentFile := printf "%s" $stageId }}
{{ range index $.Site.Data.components $componentFile }}
{{ $componentId := .id }}
<div class="component-card" id="component-{{ $componentId }}">
<div class="component-header">
<div class="component-title-wrapper">
<div class="component-short-label">{{ $componentId | humanize }}</div>
<h3>{{ .title }}</h3>
</div>
</div>
<div class="component-body">
{{ range .fields }}
<div class="field" id="field-{{ .id }}">
<div class="module-selector">
<label for="module-{{ .id }}">Select a module:</label>
<select id="module-{{ .id }}" class="module-select" data-field-id="{{ .id }}" data-component-id="{{ $componentId }}">
<option value="">-- Choose a module --</option>
</select>
</div>
<textarea id="{{ .id }}" name="{{ .id }}" placeholder="{{ .placeholder }}" {{ if .required }}required{{ end }}></textarea>
</div>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
</div>
{{ end }}
</div>
<!-- Export buttons moved to sidebar -->
</div>
<div class="builder-sidebar">
<div class="sidebar-section">
<h3>Tools</h3>
<div id="sidebar-nav" class="sidebar-nav">
<h4>Jump to Section</h4>
<div class="sidebar-nav-content">
<div class="nav-tree">
{{ range $.Site.Data.stages.stages }}
<div class="nav-stage">
<a href="#stage-{{ .id }}" class="nav-stage-link">{{ .title }}</a>
<div class="nav-components">
{{ $stageId := .id }}
{{ $componentFile := printf "%s" $stageId }}
{{ range index $.Site.Data.components $componentFile }}
<a href="#component-{{ .id }}" class="nav-component-link">{{ .title }}</a>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
</div>
<div class="sidebar-toggle" style="margin-top: 1.5rem;">
<label class="toggle-switch" for="preview-toggle">
<input type="checkbox" id="preview-toggle">
<span class="toggle-slider"></span>
<span class="toggle-label">Preview Mode</span>
</label>
</div>
<div class="sidebar-info">
<p>Preview mode shows only completed elements.</p>
</div>
</div>
<div class="sidebar-section">
<h3>Export Options</h3>
<div class="sidebar-export-options">
<a href="#" id="export-md" class="sidebar-btn">Export Markdown</a>
<a href="#" id="export-pdf" class="sidebar-btn">Export PDF</a>
<a href="#" id="export-json" class="sidebar-btn">Export JSON</a>
<button id="import-btn" class="sidebar-btn">Import JSON</button>
<input type="file" id="import-json" accept=".json" style="display: none;">
</div>
</div>
</div>
</div>
</div>
<!-- Scripts are loaded in head.html -->
<script>
function toggleStage(button) {
const section = button.closest('.stage-section');
const body = section.querySelector('.stage-body');
if (body.style.display === 'block') {
body.style.display = 'none';
button.textContent = '+';
} else {
body.style.display = 'block';
button.textContent = '-';
}
// Prevent event from propagating
event.stopPropagation();
}
// Make headers also toggle sections
document.addEventListener('DOMContentLoaded', function() {
const headers = document.querySelectorAll('.stage-header-content');
headers.forEach(header => {
header.addEventListener('click', function() {
const stageHeader = this.closest('.stage-header');
const toggleButton = stageHeader.querySelector('.toggle-btn');
toggleButton.click();
});
});
// Export dropdown removed (moved to sidebar)
// Preview Mode Toggle
const previewToggle = document.getElementById('preview-toggle');
if (previewToggle) {
previewToggle.addEventListener('change', function() {
if (this.checked) {
enablePreviewMode();
} else {
disablePreviewMode();
}
});
}
function enablePreviewMode() {
// Create style element
let style = document.createElement('style');
style.id = 'preview-style';
style.innerHTML = `
.module-selector { display: none !important; }
.field:not(.has-content) { display: none !important; }
.component-card:not(.has-content) { display: none !important; }
.stage-section:not(.has-content) { display: none !important; }
.protocol-template-selector { display: none !important; }
.stage-body { display: block !important; }
textarea {
border: none !important;
background-color: transparent !important;
padding: 0 !important;
min-height: unset !important;
height: auto !important;
resize: none !important;
pointer-events: none !important;
overflow: visible !important;
white-space: pre-wrap !important;
width: 100% !important;
word-wrap: break-word !important;
}
.component-header {
background-color: transparent !important;
border-bottom: none !important;
}
.component-short-label { display: none !important; }
`;
document.head.appendChild(style);
// Mark elements with content
document.querySelectorAll('textarea').forEach(textarea => {
if (textarea.value && textarea.value.trim()) {
const field = textarea.closest('.field');
if (field) field.classList.add('has-content');
const component = textarea.closest('.component-card');
if (component) component.classList.add('has-content');
const stage = textarea.closest('.stage-section');
if (stage) stage.classList.add('has-content');
}
});
// Make fields read-only
document.querySelectorAll('textarea, #community-name, #protocol-summary').forEach(el => {
el.readOnly = true;
});
}
function disablePreviewMode() {
// Remove preview styles
const style = document.getElementById('preview-style');
if (style) {
style.parentNode.removeChild(style);
}
// Remove content markers
document.querySelectorAll('.has-content').forEach(el => {
el.classList.remove('has-content');
});
// Make fields editable again
document.querySelectorAll('textarea, #community-name, #protocol-summary').forEach(el => {
el.readOnly = false;
});
}
// Initialize the sidebar navigation
initSidebarNavigation();
});
// Initialize the sidebar navigation
function initSidebarNavigation() {
// Set up click handlers for navigation links
document.querySelectorAll('.nav-stage-link, .nav-component-link').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
// Get the target element's ID from the href
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// If it's a stage, make sure it's expanded
if (targetId.startsWith('stage-')) {
const stageBody = targetElement.querySelector('.stage-body');
const toggleBtn = targetElement.querySelector('.toggle-btn');
if (stageBody && stageBody.style.display !== 'block') {
stageBody.style.display = 'block';
if (toggleBtn) toggleBtn.textContent = '-';
}
}
// If it's a component, make sure its parent stage is expanded
if (targetId.startsWith('component-')) {
const stageSection = targetElement.closest('.stage-section');
if (stageSection) {
const stageBody = stageSection.querySelector('.stage-body');
const toggleBtn = stageSection.querySelector('.toggle-btn');
if (stageBody && stageBody.style.display !== 'block') {
stageBody.style.display = 'block';
if (toggleBtn) toggleBtn.textContent = '-';
}
}
}
// Scroll to the target with some offset to account for sticky headers
window.scrollTo({
top: targetElement.offsetTop - 20,
behavior: 'smooth'
});
}
});
});
}
</script>
{{ end }}

View File

@ -0,0 +1,36 @@
{{ define "main" }}
<div class="container">
<div class="page-header">
<h1>{{ .Title }}</h1>
<p>{{ .Description }}</p>
</div>
<div class="content">
{{ .Content }}
</div>
<div class="search-filter">
<input type="text" id="module-search" placeholder="Search modules...">
<select id="stage-filter">
<option value="">All Stages</option>
</select>
<select id="component-filter">
<option value="">All Components</option>
</select>
<select id="template-filter">
<option value="">All Templates</option>
</select>
</div>
<div id="modules-container" class="modules-container">
<!-- Modules will be loaded here via JavaScript -->
<div class="loading">Loading modules...</div>
</div>
</div>
<!-- Load the module data -->
<script src="{{ "js/data/modules.js" | relURL }}"></script>
<script src="{{ "js/data/templates.js" | relURL }}"></script>
<script src="{{ "js/data/template-mapper.js" | relURL }}"></script>
<script src="{{ "js/modules-page.js" | relURL }}"></script>
{{ end }}

View File

@ -0,0 +1,7 @@
{{ define "main" }}
<div class="content-container">
<article class="page-content">
{{ .Content }}
</article>
</div>
{{ end }}

View File

@ -0,0 +1,26 @@
{{ define "main" }}
<div class="home-content">
<div class="hero">
<h1>{{ .Title }}</h1>
<p class="lead">Create a customized dispute resolution protocol for your community</p>
<a href="/builder/" class="btn-primary">Start Building</a>
<a href="/about/" class="btn-secondary">Learn More</a>
</div>
<div class="content">
{{ .Content }}
</div>
<div class="stages-overview">
<h2>The Dispute Resolution Process</h2>
<div class="stages-grid">
{{ range $.Site.Data.stages.stages }}
<div class="stage-card">
<h3>{{ .title }}</h3>
<p>{{ .description }}</p>
</div>
{{ end }}
</div>
</div>
</div>
{{ end }}

View File

@ -0,0 +1,6 @@
<footer>
<div class="container">
<p>&copy; {{ now.Format "2006" }} Community Dispute Protocol Builder. All rights reserved.</p>
<p>Built with <a href="https://gohugo.io/" target="_blank">Hugo</a></p>
</div>
</footer>

View File

@ -0,0 +1,36 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ .Title }} | {{ .Site.Title }}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ "css/main.css" | relURL }}">
<link rel="shortcut icon" href="{{ "favicon.ico" | relURL }}" type="image/x-icon">
<!-- JS libraries for PDF export -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<!-- Custom JS for the builder -->
{{ if eq .Layout "builder" }}
<script defer src="{{ "js/data/modules.js" | relURL }}"></script>
<script defer src="{{ "js/data/templates.js" | relURL }}"></script>
<script defer src="{{ "js/data/template-mapper.js" | relURL }}"></script>
<script defer src="{{ "js/debug.js" | relURL }}"></script>
<script defer src="{{ "js/builder.js" | relURL }}"></script>
{{ end }}
<!-- Additional custom JS from site parameters -->
{{ range .Site.Params.customJS }}
{{ if not (in . "modules.js" ) }}
{{ if not (in . "templates.js" ) }}
{{ if not (in . "template-mapper.js" ) }}
{{ if not (in . "builder.js" ) }}
<script defer src="{{ . | relURL }}"></script>
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
</head>

View File

@ -0,0 +1,15 @@
<header>
<div class="container">
<div class="logo">
<a href="{{ "/" | relURL }}">{{ .Site.Title }}</a>
</div>
<nav>
<ul>
<li><a href="{{ "/" | relURL }}">Home</a></li>
<li><a href="{{ "builder/" | relURL }}">Protocol Builder</a></li>
<li><a href="{{ "modules/" | relURL }}">Modules Library</a></li>
<li><a href="{{ "about/" | relURL }}">About</a></li>
</ul>
</nav>
</div>
</header>

View File

@ -0,0 +1,21 @@
# theme.toml template for a Hugo theme
# See https://github.com/gohugoio/hugoThemes#themetoml for an example
name = "Dispute Protocol Theme"
license = "MIT"
licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
description = ""
homepage = "http://example.com/"
tags = []
features = []
min_version = "0.41.0"
[author]
name = ""
homepage = ""
# If porting an existing theme
[original]
name = ""
homepage = ""
repo = ""