Removed the Module Library, no longer necessary

This commit is contained in:
Nathan Schneider
2025-05-07 22:21:30 -06:00
parent adf155e954
commit 41aeffa81b
17 changed files with 94 additions and 1323 deletions

View File

@ -1,8 +0,0 @@
---
title: "Modules Library"
description: "Browse all available modules for the Dispute Protocol Builder"
layout: "modules"
draft: false
---
This page displays all available modules in the Dispute Protocol Builder, organized by Stage and Component. You can see which Templates use each module.

View File

@ -1,27 +0,0 @@
- id: participant-goal-assessment
title: "Participant Goal Assessment"
componentId: dispute_assessment
fieldId: disputeAssessmentText
content: |
The dispute is assessed by focusing on the goals of all participants:
1. Each participant articulates what they hope to achieve through the process
2. Participants identify areas of common ground and shared interests
3. The facilitator helps frame the dispute in terms of compatible and competing goals
4. Participants rate the importance of different goals to help prioritize
5. Assessment continues throughout the process as goals may evolve
6. Resolution options are evaluated against the stated goals of participants
- id: fact-assessment
title: "Fact Assessment"
componentId: dispute_assessment
fieldId: disputeAssessmentText
content: |
The dispute is assessed through a structured fact-finding process:
1. Facilitators interview all parties separately to gather initial accounts
2. Documentary evidence and other materials are collected and cataloged
3. Areas of factual agreement and disagreement are identified
4. Where possible, neutral verification of disputed facts is sought
5. A written summary of established facts is created and shared with participants
6. The dispute assessment is updated as new information becomes available

View File

@ -1,41 +0,0 @@
- id: police-report
title: "Refer to Police Report"
componentId: delegation_options
fieldId: delegationOptionsText
content: |
In cases where this process is inadequate, disputes may be referred to law enforcement:
1. Situations involving immediate danger or criminal behavior are referred to police
2. The community does not attempt to handle issues of criminal law internally
3. Support is offered to community members who need to file police reports
4. Community members may request an advocate to accompany them
5. The community maintains relationships with local law enforcement liaisons
6. Community process may resume after legal proceedings if appropriate
- id: internal-process
title: "Refer to Another Internal Process"
componentId: delegation_options
fieldId: delegationOptionsText
content: |
In cases where this process is inadequate, disputes may be referred to other internal processes:
1. The community maintains several conflict resolution pathways for different situations
2. Disputes involving multiple community groups are referred to the inter-group resolution committee
3. Disputes requiring specialized knowledge may be referred to relevant working groups
4. Cases requiring more structured intervention may be referred to the elder council
5. Mediation services are available as an alternative to the standard process
6. The community maintains clear guidelines for which process is appropriate for different situations
- id: external-process
title: "Refer to External Process"
componentId: delegation_options
fieldId: delegationOptionsText
content: |
In cases where this process is inadequate, disputes may be referred to external resolution services:
1. The community maintains partnerships with professional mediation services
2. Complex cases may be referred to specialized conflict resolution organizations
3. Cases involving legal questions may be referred to legal aid services
4. The community maintains a fund to help members access external services when needed
5. A list of recommended external resources is maintained and regularly updated
6. The community liaison helps ensure smooth handoff to external processes

View File

@ -1,21 +0,0 @@
- id: written-filing
title: Written Filing Process
componentId: filing
fieldId: filingProcess
content: |
1. Complainant fills out a standard form describing the dispute
2. Form includes sections for: parties involved, description of the issue, desired outcome
3. Submission can be made via email, website, or physical drop-off
4. Complainant receives confirmation of receipt
5. Submission is reviewed for completeness within 48 hours
- id: verbal-filing
title: Verbal Filing Process
componentId: filing
fieldId: filingProcess
content: |
1. Complainant schedules a meeting with a designated intake person
2. During the meeting, the intake person documents the dispute details
3. The intake person reviews the documented information with the complainant
4. Once approved, the complaint is officially filed
5. Complainant receives a copy of the documented complaint

View File

@ -1,104 +0,0 @@
- id: incident-report-form
title: "Incident Report Form"
componentId: process_start
fieldId: processStartText
content: |
The dispute process begins when a community member fills out an incident report form. This form includes:
1. Details about the parties involved in the dispute
2. A description of what happened, including dates and times
3. Any relevant evidence or documentation
4. A description of the outcome the person is seeking
The form can be submitted electronically through our community portal or as a paper form to the designated community coordinator.
- id: strong-confidentiality
title: "Strong Confidentiality"
componentId: information_access
fieldId: informationAccessText
content: |
Our community practices strong confidentiality in dispute processes:
1. Only the designated facilitator(s) and directly involved parties have access to full information
2. All participants must sign confidentiality agreements
3. Records are kept secure and are accessible only to the dispute resolution committee
4. Only general statistics and anonymized outcomes may be shared with the broader community
5. Breaches of confidentiality may result in removal from the process
- id: strong-transparency
title: "Strong Transparency"
componentId: information_access
fieldId: informationAccessText
content: |
Our community practices strong transparency in dispute processes:
1. Basic information about active disputes is available to all community members
2. Proceedings are documented and records are available for community review
3. Regular updates on the status of disputes are shared at community meetings
4. Only personal identifying information and sensitive details are redacted
5. All decisions and their rationales are published internally
- id: notification-message
title: "Notification Message"
componentId: participant_inclusion
fieldId: participantInclusionText
content: |
Additional participants are brought into the process through a formal notification message:
1. The facilitator sends a written notification to all identified relevant parties
2. The notification includes the nature of the dispute, the process that will be followed, and their role
3. Recipients are given information about how to respond and participate
4. The notification includes resources to help participants understand the process
5. Participants have 7 days to acknowledge receipt and confirm their participation
- id: summons
title: "Summons"
componentId: participant_inclusion
fieldId: participantInclusionText
content: |
Additional participants are brought into the process through a formal summons:
1. The dispute committee issues an official summons to all identified relevant parties
2. The summons clearly states that participation is required according to community agreements
3. It specifies the date, time, and method of required appearance
4. The summons outlines potential consequences of non-participation
5. Delivery of the summons is documented to ensure proper notification
- id: voluntary-participation
title: "Voluntary Participation"
componentId: participation_requirement
fieldId: participationRequirementText
content: |
Participation in our dispute resolution process is entirely voluntary:
1. All parties must consent to participate in the process
2. Any party may withdraw from the process at any time
3. No negative consequences result from declining to participate
4. Alternative means of resolution are suggested for those who decline
5. The community respects individuals' autonomy in choosing whether to engage
- id: required-participation
title: "Required Participation"
componentId: participation_requirement
fieldId: participationRequirementText
content: |
Participation in our dispute resolution process is required for all community members:
1. By joining our community, members agree to participate in the dispute process when necessary
2. Participation is mandatory for all named parties in a dispute
3. Failure to participate may result in consequences as outlined in our community agreement
4. Only in exceptional circumstances may exemptions be granted
5. Repeated refusal to participate may result in review of community membership
- id: stake-based-participation
title: "Stake-based Participation"
componentId: participation_commitments
fieldId: participationCommitmentsText
content: |
Participants in the dispute process must place a meaningful stake into the process:
1. Each participant contributes a small financial deposit (adjusted based on ability to pay)
2. Deposits are returned when parties fulfill all process commitments
3. Participants commit a specified amount of time to the process
4. Participants agree to adhere to all ground rules and procedural guidelines
5. All parties agree to implement the resolution in good faith

View File

@ -1,21 +0,0 @@
- id: direct-notification
title: Direct Notification Process
componentId: notification
fieldId: notificationMethod
content: |
1. All parties are notified in writing within 72 hours of a filed complaint
2. Notification includes a copy of the complaint and next steps
3. Parties confirm receipt of notification
4. If no confirmation is received within 48 hours, a secondary contact method is used
5. All notifications maintain privacy and confidentiality standards
- id: facilitated-notification
title: Facilitated Notification Process
componentId: notification
fieldId: notificationMethod
content: |
1. A neutral facilitator contacts all parties individually
2. The facilitator explains the process and shares the complaint details
3. Parties have an opportunity to ask questions about the process
4. The facilitator documents that notification has occurred
5. Follow-up written summary is provided to all parties

View File

@ -1,33 +0,0 @@
- id: community-circle
title: Community Circle Model
componentId: participants
fieldId: participantsRoles
content: |
1. Facilitator: Guides the process, ensures fairness, and helps maintain focus
2. Affected Parties: Those directly impacted by the dispute
3. Support Persons: Friends, family, or advocates who support affected parties
4. Community Members: Representatives who bring wider perspective
5. Resource People: Those with relevant expertise to inform the process
- id: mediation-model
title: Mediation Model
componentId: participants
fieldId: participantsRoles
content: |
1. Mediator: Neutral third party who facilitates the process
2. Disputants: Primary parties involved in the conflict
3. Advocates: Optional support persons who may speak on behalf of disputants
4. Witnesses: Those who provide information relevant to the dispute
5. Implementers: Those responsible for helping carry out any agreements
- id: council-model
title: Council Model
componentId: participants
fieldId: participantsRoles
content: |
1. Council Members: A designated group responsible for hearing disputes
2. Complainant: Person bringing the dispute
3. Respondent: Person responding to the complaint
4. Witnesses: Those providing testimony or evidence
5. Advisors: Those who support the council with expertise
6. Community Observers: Those who may witness proceedings for transparency

View File

@ -1,32 +0,0 @@
- id: restorative
title: Restorative Justice Principles
componentId: principles
fieldId: principlesText
content: |
1. Focus on harm and needs of those affected
2. Address obligations resulting from harm
3. Use inclusive, collaborative processes
4. Involve all stakeholders (victims, offenders, community)
5. Work toward repairing harm and healing relationships
- id: transformative
title: Transformative Justice Principles
componentId: principles
fieldId: principlesText
content: |
1. Seek to address immediate safety, healing, and agency
2. Work to transform the conditions that allowed violence to occur
3. Build community accountability systems
4. Focus on community-based responses without relying on punitive systems
5. Acknowledge interconnection of all forms of violence
- id: consensus
title: Consensus-Based Principles
componentId: principles
fieldId: principlesText
content: |
1. All members have equal input in decision-making
2. Seek solutions that address everyone's fundamental concerns
3. Prioritize listening and understanding diverse perspectives
4. Work toward outcomes that all parties can accept
5. Focus on collaborative problem-solving over adversarial positions

View File

@ -1,65 +0,0 @@
- id: participant-facilitation
title: "Participant Facilitation"
componentId: facilitation
fieldId: facilitationText
content: |
The dispute process is facilitated by the participants themselves:
1. Participants take turns leading different parts of the conversation
2. A written guide provides structure to ensure all voices are heard
3. All participants receive basic training in productive dialogue techniques
4. Time limits and speaking guidelines ensure fair participation
5. Any participant can call for a break or reset if the process becomes unproductive
- id: peer-facilitation
title: "Peer Facilitation"
componentId: facilitation
fieldId: facilitationText
content: |
The dispute process is facilitated by peers from within the community:
1. A pool of trained peer facilitators is maintained within the community
2. Facilitators are selected who have no direct involvement in the dispute
3. Peer facilitators receive regular training in conflict resolution techniques
4. Typically, two peer facilitators work together on each case
5. Peer facilitators help maintain structure but do not make decisions
- id: trained-facilitation
title: "Trained Facilitation (e.g. mediator)"
componentId: facilitation
fieldId: facilitationText
content: |
The dispute process is facilitated by professionally trained mediators:
1. Professional mediators with formal certification lead the process
2. Mediators are selected from outside the community to ensure neutrality
3. Mediators have specific training in the type of conflict being addressed
4. The community maintains a roster of approved mediators
5. Mediators are paid for their services according to a pre-established rate
- id: facilitation-committee
title: "Facilitation Committee"
componentId: facilitation
fieldId: facilitationText
content: |
The dispute process is facilitated by a standing committee:
1. A dedicated committee of 5-7 members oversees all dispute processes
2. Committee members serve rotating terms and receive ongoing training
3. For each dispute, a subgroup of 2-3 committee members is assigned
4. Committee members with conflicts of interest must recuse themselves
5. The committee follows established procedures and maintains records of all cases
- id: nonviolent-communication
title: "Nonviolent Communication"
componentId: ground_rules
fieldId: groundRulesText
content: |
Our dispute process follows the principles of Nonviolent Communication (NVC):
1. Participants focus on observations rather than evaluations or judgments
2. Participants express feelings using "I" statements rather than blaming others
3. Participants identify needs that are or are not being met by the situation
4. Participants make clear, actionable requests rather than demands
5. Facilitators help participants translate judgmental language into NVC format
6. Active listening is practiced, with participants reflecting back what they've heard

View File

@ -1,41 +0,0 @@
- id: participant-consensus
title: "Participant Consensus"
componentId: resolution_process
fieldId: resolutionProcessText
content: |
Resolution occurs through consensus of all involved participants:
1. Participants collaboratively develop possible solutions
2. All parties must agree to the final resolution
3. Consensus does not mean everyone's first choice, but a solution everyone can accept
4. Multiple rounds of proposal and revision may be necessary
5. Facilitators help test solutions against participants' stated needs
6. Once consensus is reached, the agreement is documented in writing
- id: facilitator-adjudication
title: "Facilitator Adjudication"
componentId: resolution_process
fieldId: resolutionProcessText
content: |
Resolution occurs through a decision made by the facilitator(s):
1. After full deliberation, the facilitator(s) makes a binding decision
2. The decision is based on community guidelines and the specifics of the case
3. Facilitators provide a written explanation of their decision and reasoning
4. All parties agree in advance to abide by the facilitator's decision
5. Decisions establish precedent for future similar cases
6. Decisions may be appealed only on specific grounds
- id: jury-adjudication
title: "Jury Adjudication"
componentId: resolution_process
fieldId: resolutionProcessText
content: |
Resolution occurs through a decision made by a jury of community peers:
1. A jury of 5-7 community members is randomly selected
2. Jury members receive an orientation to their role and responsibilities
3. After hearing all perspectives, the jury deliberates privately
4. The jury reaches a decision by two-thirds majority
5. The jury provides a written explanation of their decision
6. All community members agree in advance to respect jury decisions

View File

@ -74,139 +74,7 @@ nav ul li a:hover {
border-bottom: 1px solid var(--dark-color);
}
/* Modules Page Styles */
.search-filter {
margin-bottom: 2rem;
padding: 1.5rem;
background-color: #f5f5f5;
border: 1px solid var(--border-color);
display: flex;
flex-wrap: wrap;
gap: 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
border-radius: 4px;
}
.search-filter input,
.search-filter select {
padding: 0.8rem;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 1rem;
transition: all 0.2s ease;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
}
.search-filter input:focus,
.search-filter select:focus {
outline: none;
border-color: #000;
box-shadow: 0 0 0 2px rgba(0,0,0,0.1);
}
.search-filter input {
flex-grow: 1;
min-width: 200px;
}
.search-filter select {
min-width: 150px;
background-color: white;
cursor: pointer;
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M10.3 3.3L6 7.6 1.7 3.3c-.4-.4-1-.4-1.4 0s-.4 1 0 1.4l5 5c.2.2.4.3.7.3s.5-.1.7-.3l5-5c.4-.4.4-1 0-1.4s-1-.4-1.4 0z'/%3E%3C/svg%3E");
background-position: calc(100% - 10px) center;
background-repeat: no-repeat;
padding-right: 30px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
.modules-container {
margin-top: 2rem;
}
.module-stage {
margin-bottom: 3rem;
}
.module-stage h2 {
border-bottom: 2px solid var(--primary-color);
padding-bottom: 0.5rem;
margin-bottom: 1.5rem;
font-size: 1.75rem;
}
.module-component {
margin-bottom: 2rem;
padding-left: 1.5rem;
}
.module-component h3 {
color: var(--secondary-color);
margin-bottom: 1rem;
font-size: 1.4rem;
}
.module-card {
border: 1px solid var(--border-color);
margin-bottom: 1.5rem;
border-radius: 4px;
overflow: hidden;
background-color: var(--light-color);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
transition: box-shadow 0.3s ease;
}
.module-card.expanded {
box-shadow: 0 3px 6px rgba(0,0,0,0.16);
}
.module-header {
padding: 1rem 1.5rem;
background-color: #f5f5f5;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
transition: background-color 0.2s ease;
position: relative;
}
.module-header:hover {
background-color: #e9e9e9;
}
.module-header:after {
content: "▼";
position: absolute;
right: 1rem;
top: 1rem;
font-size: 0.8rem;
color: var(--secondary-color);
transition: transform 0.3s ease;
}
.expanded .module-header:after {
transform: rotate(180deg);
}
.module-header h4 {
margin: 0;
font-size: 1.1rem;
color: var(--primary-color);
flex-grow: 1;
padding-right: 2rem;
}
.module-tags {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-top: 0.8rem;
}
/* Tag styles for components */
.tag {
display: inline-block;
padding: 0.3rem 0.6rem;
@ -234,68 +102,6 @@ nav ul li a:hover {
color: #6600cc;
border: 1px solid rgba(102, 0, 204, 0.2);
}
/* Module Library specific styles */
.modules-container .component-card {
margin-bottom: 1rem;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modules-container .component-body {
padding: 1.5rem 2rem;
animation: slideDown 0.3s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.module-meta {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.module-content {
margin: 1.5rem 0;
}
.module-content textarea {
width: 100%;
min-height: 180px;
padding: 1rem;
border: 1px solid var(--border-color);
font-family: inherit;
resize: vertical;
background-color: var(--light-color);
font-size: 1.05rem;
line-height: 1.6;
color: var(--secondary-color);
}
.module-templates {
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid var(--border-color);
font-size: 0.9rem;
color: var(--accent-color);
}
.module-templates strong {
color: var(--secondary-color);
font-weight: 600;
margin-right: 0.5rem;
}
@ -1150,14 +956,21 @@ input:checked + .toggle-slider:before {
textarea {
width: 100%;
min-height: 250px;
padding: 1rem;
border: 1px solid var(--border-color);
font-family: inherit;
resize: vertical;
resize: none;
background-color: var(--light-color);
font-size: 1.05rem;
line-height: 1.5;
overflow: hidden;
transition: height 0.3s ease;
min-height: 60px;
box-sizing: border-box;
margin-bottom: 0.5rem;
display: block;
word-wrap: break-word;
white-space: pre-wrap;
}
.builder-actions {

View File

@ -436,6 +436,8 @@ document.addEventListener('DOMContentLoaded', function() {
// Apply the content to the textarea
textarea.value = content;
// Height adjustment is now handled by the global script
// Store in protocol data
protocol.stages[stageId][componentId][fieldId] = content;
fieldsPopulated++;
@ -467,6 +469,9 @@ document.addEventListener('DOMContentLoaded', function() {
if (previewModeActive) {
markComponentsWithContent();
}
// Dispatch custom event for textarea auto-resize
document.dispatchEvent(new CustomEvent('templateApplied'));
}
// No helper functions needed anymore as we've simplified the approach

View File

@ -1,695 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
console.log('Modules page initializing...');
// Reference to container and filters
const modulesContainer = document.getElementById('modules-container');
const moduleSearch = document.getElementById('module-search');
const stageFilter = document.getElementById('stage-filter');
const componentFilter = document.getElementById('component-filter');
const templateFilter = document.getElementById('template-filter');
// Data structures
let allModules = [];
let stageNames = {};
let componentNames = {};
let templateUsage = {};
let processedTemplates = [];
// Functions
function initializeData() {
console.log('Initializing data...');
// Check if data is available
if (typeof moduleData === 'undefined') {
console.error('Error: moduleData is undefined');
modulesContainer.innerHTML = '<div class="error">Error: Module data not available</div>';
return false;
}
if (typeof rawProtocolTemplates === 'undefined' || typeof templateMapper === 'undefined') {
console.error('Error: Template data or mapper not available');
modulesContainer.innerHTML = '<div class="error">Error: Template data not available</div>';
return false;
}
// Log what we're working with
console.log('Module categories:', Object.keys(moduleData));
console.log('Templates count:', rawProtocolTemplates.length);
try {
// Process templates
rawProtocolTemplates.forEach(template => {
const processedTemplate = templateMapper.convertTemplateToModules(template, moduleData);
processedTemplates.push(processedTemplate);
});
// Collect all modules into a flat array
for (const category in moduleData) {
moduleData[category].forEach(module => {
// Add category to module
module.category = category;
// Initialize template usage
module.usedInTemplates = [];
// Ensure all modules have a componentId - no uncategorized modules allowed
if (!module.componentId) {
// Try to infer from category first
if (category !== 'uncategorized') {
console.log(`Module ${module.id} missing componentId, using category: ${category}`);
module.componentId = category;
}
// If that doesn't work, infer from title or content
else {
// First, try to find clues in the title
if (module.title) {
const titleLower = module.title.toLowerCase();
// Check for key component names in the title
if (titleLower.includes('principle')) module.componentId = 'principles';
else if (titleLower.includes('process')) module.componentId = 'process';
else if (titleLower.includes('assessment')) module.componentId = 'assessment';
else if (titleLower.includes('intake')) module.componentId = 'intake';
else if (titleLower.includes('appeal')) module.componentId = 'appeal';
else if (titleLower.includes('deliberat')) module.componentId = 'deliberation';
else if (titleLower.includes('resolut')) module.componentId = 'resolution';
else if (titleLower.includes('facilit')) module.componentId = 'facilitation';
else if (titleLower.includes('particip')) module.componentId = 'participants';
else if (titleLower.includes('file') || titleLower.includes('submit')) module.componentId = 'filing';
else if (titleLower.includes('notif') || titleLower.includes('inform')) module.componentId = 'notification';
else if (titleLower.includes('delegat')) module.componentId = 'delegation';
else module.componentId = 'process'; // Default to process
}
// If no title clues, check content
else if (module.content) {
const contentLower = module.content.toLowerCase();
// Same checks in content
if (contentLower.includes('principle')) module.componentId = 'principles';
else if (contentLower.includes('process')) module.componentId = 'process';
else if (contentLower.includes('assessment')) module.componentId = 'assessment';
else if (contentLower.includes('intake')) module.componentId = 'intake';
else if (contentLower.includes('appeal')) module.componentId = 'appeal';
else if (contentLower.includes('deliberat')) module.componentId = 'deliberation';
else if (contentLower.includes('resolut')) module.componentId = 'resolution';
else if (contentLower.includes('facilit')) module.componentId = 'facilitation';
else if (contentLower.includes('particip')) module.componentId = 'participants';
else if (contentLower.includes('file') || contentLower.includes('submit')) module.componentId = 'filing';
else if (contentLower.includes('notif') || contentLower.includes('inform')) module.componentId = 'notification';
else if (contentLower.includes('delegat')) module.componentId = 'delegation';
else module.componentId = 'process'; // Default to process
}
// Last resort default
else {
module.componentId = 'process';
}
console.log(`Module ${module.id} had no componentId, assigned to: ${module.componentId}`);
}
}
// Add to all modules array
allModules.push(module);
});
}
console.log('Total modules collected:', allModules.length);
// Track which templates use each module
processedTemplates.forEach(template => {
for (const stageId in template.moduleRefs) {
if (!stageNames[stageId]) {
// Create a readable stage name
stageNames[stageId] = stageId.charAt(0).toUpperCase() + stageId.slice(1).replace(/_/g, ' ');
}
for (const componentId in template.moduleRefs[stageId]) {
if (!componentNames[componentId]) {
// Create a readable component name
componentNames[componentId] = componentId.charAt(0).toUpperCase() + componentId.slice(1).replace(/_/g, ' ');
}
for (const fieldId in template.moduleRefs[stageId][componentId]) {
const moduleId = template.moduleRefs[stageId][componentId][fieldId];
// Find the module with this id
const matchingModule = allModules.find(m => m.id === moduleId);
if (matchingModule) {
// Add template to module's usage
if (!matchingModule.usedInTemplates.includes(template.title)) {
matchingModule.usedInTemplates.push(template.title);
}
// Set stage and component for the module if not already set
if (!matchingModule.stageId) {
matchingModule.stageId = stageId;
}
// Track template usage
if (!templateUsage[template.title]) {
templateUsage[template.title] = [];
}
if (!templateUsage[template.title].includes(moduleId)) {
templateUsage[template.title].push(moduleId);
}
}
}
}
}
});
// Define official stages from YAML file
const officialStages = {
'intake': 'Intake',
'process': 'Process',
'assessment': 'Assessment',
'deliberation': 'Deliberation',
'resolution': 'Resolution',
'appeal': 'Appeal',
'delegation': 'Delegation'
};
// Define official components with standardized display names
const officialComponents = {
// Process stage components
'principles': 'Principles',
'community_values': 'Values',
'participants': 'Participants',
'facilitation': 'Facilitation',
'ground_rules': 'Ground Rules',
'skills': 'Skills',
// Intake stage components
'process_start': 'Process Start',
'filing': 'Filing',
'notification': 'Notification',
'rules_access': 'Rules Access',
'information_access': 'Information Access',
'participant_inclusion': 'Participant Inclusion',
// Assessment stage components
'dispute_assessment': 'Assessment',
'values_adherence': 'Values Adherence',
'jurisdiction': 'Jurisdiction',
// Deliberation stage components
'deliberation_process': 'Deliberation Process',
'additional_voices': 'Additional Voices',
'deliberation_conclusion': 'Deliberation Conclusion',
// Resolution stage components
'resolution_process': 'Resolution Process',
'resolution_failure': 'Resolution Failure',
// Appeal stage components
'appeal_criteria': 'Appeal Criteria',
'appeal_process': 'Appeal Process',
// Delegation stage components
'delegation_options': 'Delegation Options'
};
// Map all component IDs (including custom ones) to official stages
const componentToStageMap = {
// Process stage components
'principles': 'process',
'community_values': 'process',
'participants': 'process',
'facilitation': 'process',
'ground_rules': 'process',
'skills': 'process',
'values': 'process',
'agreements': 'process',
'participation_commitments': 'process',
// Intake stage components
'process_start': 'intake',
'filing': 'intake',
'notification': 'intake',
'rules_access': 'intake',
'information_access': 'intake',
'participant_inclusion': 'intake',
'reporting': 'intake',
// Assessment stage components
'dispute_assessment': 'assessment',
'values_adherence': 'assessment',
'jurisdiction': 'assessment',
'non_participation': 'assessment',
// Deliberation stage components
'deliberation_process': 'deliberation',
'additional_voices': 'deliberation',
'deliberation_conclusion': 'deliberation',
'decision_making': 'deliberation',
'discussion': 'deliberation',
// Resolution stage components
'resolution_process': 'resolution',
'resolution_failure': 'resolution',
// Appeal stage components
'appeal_criteria': 'appeal',
'appeal_process': 'appeal',
'appeal_deliberation': 'appeal',
'appeal_resolution': 'appeal',
// Delegation stage components
'delegation_options': 'delegation'
};
// Map non-standard components to official ones
const componentMapping = {
'direct_conversation': 'deliberation_process',
'conflict_awareness': 'dispute_assessment',
'accessing_help': 'process_start',
'preparation': 'principles',
'selection': 'participant_inclusion',
'criteria': 'appeal_criteria',
'agreement': 'resolution_process',
'process': 'principles',
'process_change': 'resolution_failure',
'assessment': 'dispute_assessment',
'situation': 'dispute_assessment',
'reflection': 'deliberation_conclusion',
'monitoring': 'dispute_assessment',
'participation_requirement': 'participant_inclusion'
};
allModules.forEach(module => {
// If module has no stageId, try to infer from category
if (!module.stageId && module.category) {
// Check if category matches a known stage
if (stageNames[module.category]) {
module.stageId = module.category;
} else {
// Use category as stageId if not already recognized
module.stageId = module.category;
// Create a readable stage name from category
stageNames[module.category] = module.category.charAt(0).toUpperCase() +
module.category.slice(1).replace(/_/g, ' ');
}
}
// If still no stageId, try to infer from componentId using the mapping
if (!module.stageId && module.componentId) {
const mappedStage = componentToStageMap[module.componentId];
if (mappedStage) {
module.stageId = mappedStage;
console.log(`Module ${module.id} missing stageId, inferred from component: ${mappedStage}`);
} else {
// Default stage if no mapping exists
module.stageId = 'process';
console.log(`Module ${module.id} missing stageId, defaulting to: process`);
}
}
// If STILL no stageId (somehow), assign to process
if (!module.stageId) {
module.stageId = 'process';
console.log(`Module ${module.id} had no stageId, assigned to: process`);
}
// Force module to use only official stages
if (officialStages[module.stageId]) {
// Use official stage name
module.stageName = officialStages[module.stageId];
} else {
// Map to closest official stage
const mappedStage = componentToStageMap[module.componentId];
if (mappedStage && officialStages[mappedStage]) {
module.stageId = mappedStage;
module.stageName = officialStages[mappedStage];
console.log(`Module ${module.id} had invalid stage "${module.stageId}", remapped to: ${mappedStage}`);
} else {
// Default to Process stage if can't map anywhere else
module.stageId = 'process';
module.stageName = officialStages['process'];
console.log(`Module ${module.id} had invalid stage "${module.stageId}", defaulting to: Process`);
}
}
// Handle component mapping for non-standard components
if (componentMapping[module.componentId]) {
const originalComponentId = module.componentId;
module.componentId = componentMapping[originalComponentId];
console.log(`Module ${module.id} had non-standard component "${originalComponentId}", mapped to: ${module.componentId}`);
}
// Set a readable component name using official list
if (officialComponents[module.componentId]) {
module.componentName = officialComponents[module.componentId];
} else {
// Generate a readable name for custom components
module.componentName = module.componentId.charAt(0).toUpperCase() +
module.componentId.slice(1).replace(/_/g, ' ');
// Log any component not in the official list
console.log(`Module ${module.id} uses custom component: ${module.componentId}`);
}
});
// Log the distribution of modules by stage
const stageDistribution = {};
allModules.forEach(module => {
stageDistribution[module.stageName] = (stageDistribution[module.stageName] || 0) + 1;
});
console.log('Module distribution by stage:', stageDistribution);
console.log('Data initialization complete');
console.log('Stages:', Object.keys(stageNames));
console.log('Components:', Object.keys(componentNames));
console.log('Templates:', Object.keys(templateUsage));
return true;
} catch (error) {
console.error('Error initializing data:', error);
modulesContainer.innerHTML = `<div class="error">Error initializing data: ${error.message}</div>`;
return false;
}
}
function populateFilters() {
console.log('Populating filters...');
// Clear existing options
stageFilter.innerHTML = '<option value="">All Stages</option>';
componentFilter.innerHTML = '<option value="">All Components</option>';
templateFilter.innerHTML = '<option value="">All Templates</option>';
// Get unique stages and components
const stages = [...new Set(allModules.map(m => m.stageName))].sort();
const components = [...new Set(allModules.map(m => m.componentName))].sort();
const templates = Object.keys(templateUsage).sort();
console.log('Filter options - Stages:', stages);
console.log('Filter options - Components:', components);
console.log('Filter options - Templates:', templates);
// Add options to filters
stages.forEach(stage => {
const option = document.createElement('option');
option.value = stage;
option.textContent = stage;
stageFilter.appendChild(option);
});
components.forEach(component => {
const option = document.createElement('option');
option.value = component;
option.textContent = component;
componentFilter.appendChild(option);
});
templates.forEach(template => {
const option = document.createElement('option');
option.value = template;
option.textContent = template;
templateFilter.appendChild(option);
});
}
function renderModules() {
console.log('Rendering modules...');
// Clear container
modulesContainer.innerHTML = '';
// Get filter values
const searchText = moduleSearch.value.toLowerCase();
const stageValue = stageFilter.value;
const componentValue = componentFilter.value;
const templateValue = templateFilter.value;
console.log('Filter values:', {
search: searchText,
stage: stageValue,
component: componentValue,
template: templateValue
});
// Create a stage-based organization of modules
const modulesByStage = {};
// Filter modules based on search and filter criteria
allModules.forEach(module => {
// Check if module matches search text
const matchesSearch = searchText === '' ||
module.title.toLowerCase().includes(searchText) ||
(module.content && module.content.toLowerCase().includes(searchText));
// Check if module matches stage filter
const matchesStage = stageValue === '' || module.stageName === stageValue;
// Check if module matches component filter
const matchesComponent = componentValue === '' || module.componentName === componentValue;
// Check if module matches template filter
const matchesTemplate = templateValue === '' ||
module.usedInTemplates.includes(templateValue);
// Include module if it matches all criteria
if (matchesSearch && matchesStage && matchesComponent && matchesTemplate) {
// Initialize stage object if not exists
if (!modulesByStage[module.stageName]) {
modulesByStage[module.stageName] = [];
}
// Add module to its stage
modulesByStage[module.stageName].push(module);
}
});
// Sort stages according to the official order from the YAML file
const stageOrder = [
'Intake', 'Process', 'Assessment',
'Deliberation', 'Resolution', 'Appeal', 'Delegation'
];
// Get all stages from the data
const availableStages = Object.keys(modulesByStage);
// Sort stages according to the defined order, with any others at the end
const sortedStages = [];
// First add stages in the predefined order (if they exist in the data)
stageOrder.forEach(stage => {
if (availableStages.includes(stage)) {
sortedStages.push(stage);
}
});
// Then add any other stages not in the predefined order (sorted alphabetically)
availableStages
.filter(stage => !stageOrder.includes(stage))
.sort()
.forEach(stage => sortedStages.push(stage));
// If no modules match, show message
if (sortedStages.length === 0) {
modulesContainer.innerHTML = '<div class="no-results">No modules match your search criteria</div>';
return;
}
// Create HTML for each stage and its modules
sortedStages.forEach(stageName => {
const stageModules = modulesByStage[stageName];
// Create stage section using similar structure to builder
const stageSection = document.createElement('div');
stageSection.className = 'stage-section';
// Create stage header
const stageHeader = document.createElement('div');
stageHeader.className = 'stage-header';
const stageHeaderContent = document.createElement('div');
stageHeaderContent.className = 'stage-header-content';
stageHeaderContent.innerHTML = `
<h2>${stageName}</h2>
<div class="stage-brief">
Contains ${stageModules.length} module${stageModules.length !== 1 ? 's' : ''}
</div>
`;
// Create toggle button
const toggleBtn = document.createElement('button');
toggleBtn.className = 'toggle-btn';
toggleBtn.innerHTML = '+';
toggleBtn.setAttribute('aria-label', 'Toggle stage content');
stageHeader.appendChild(stageHeaderContent);
stageHeader.appendChild(toggleBtn);
stageSection.appendChild(stageHeader);
// Create stage body
const stageBody = document.createElement('div');
stageBody.className = 'stage-body';
stageBody.style.display = 'none';
// Group modules by component
const modulesByComponent = {};
stageModules.forEach(module => {
// Use just the component name without duplicating stage info
const cleanComponentName = module.componentName.replace(module.stageName, '').trim();
const displayComponentName = cleanComponentName || module.componentName;
if (!modulesByComponent[displayComponentName]) {
modulesByComponent[displayComponentName] = [];
}
modulesByComponent[displayComponentName].push(module);
});
// Sort components
const sortedComponents = Object.keys(modulesByComponent).sort();
// Create components container
const componentsContainer = document.createElement('div');
componentsContainer.className = 'components';
// Create component sections
sortedComponents.forEach(componentName => {
const componentModules = modulesByComponent[componentName];
// Create a component group heading with count
const componentHeading = document.createElement('h3');
componentHeading.className = 'component-group-heading';
// Create main text
const headingText = document.createTextNode(componentName);
componentHeading.appendChild(headingText);
// Add count in parentheses
const moduleCount = componentModules.length;
const countSpan = document.createElement('span');
countSpan.className = 'component-module-count';
countSpan.textContent = ` (${moduleCount} module${moduleCount !== 1 ? 's' : ''})`;
componentHeading.appendChild(countSpan);
componentsContainer.appendChild(componentHeading);
// Add modules to component section
componentModules.forEach(module => {
const moduleCard = createModuleCard(module);
componentsContainer.appendChild(moduleCard);
});
});
stageBody.appendChild(componentsContainer);
stageSection.appendChild(stageBody);
// Add toggle functionality
stageHeaderContent.addEventListener('click', function() {
if (stageBody.style.display === 'none') {
stageBody.style.display = 'block';
toggleBtn.innerHTML = '';
} else {
stageBody.style.display = 'none';
toggleBtn.innerHTML = '+';
}
});
toggleBtn.addEventListener('click', function(e) {
e.stopPropagation();
if (stageBody.style.display === 'none') {
stageBody.style.display = 'block';
toggleBtn.innerHTML = '';
} else {
stageBody.style.display = 'none';
toggleBtn.innerHTML = '+';
}
});
modulesContainer.appendChild(stageSection);
});
console.log('Modules rendering complete');
}
function createModuleCard(module) {
const card = document.createElement('div');
card.className = 'component-card';
card.dataset.id = module.id;
// Format templates list
const templatesList = module.usedInTemplates.length > 0
? module.usedInTemplates.join(', ')
: 'Not used in any template';
// Create card content with structure similar to builder component
card.innerHTML = `
<div class="component-header">
<h3>${module.title}</h3>
</div>
<div class="component-body">
<div class="module-meta">
<span class="tag stage-tag">${module.stageName}</span>
<span class="tag component-tag">${module.componentName}</span>
</div>
<div class="module-content">
<textarea readonly>${module.content}</textarea>
</div>
<div class="module-templates">
<strong>Used in templates:</strong> ${templatesList}
</div>
</div>
`;
// Make the header clickable to toggle content visibility
const header = card.querySelector('.component-header');
const body = card.querySelector('.component-body');
// Add toggle button to the header (already positioned by CSS flexbox)
const toggleBtn = document.createElement('button');
toggleBtn.className = 'toggle-btn';
toggleBtn.innerHTML = '+';
toggleBtn.setAttribute('aria-label', 'Toggle module content');
header.appendChild(toggleBtn);
// Start with content hidden
body.style.display = 'none';
// Toggle functionality
function toggleContent() {
if (body.style.display === 'none') {
body.style.display = 'block';
toggleBtn.innerHTML = '';
card.classList.add('expanded');
} else {
body.style.display = 'none';
toggleBtn.innerHTML = '+';
card.classList.remove('expanded');
}
}
header.addEventListener('click', toggleContent);
return card;
}
// Initialize page
function init() {
console.log('Initializing modules page...');
// Load data
if (!initializeData()) {
console.error('Failed to initialize data');
return;
}
// Populate filters
populateFilters();
// Render modules
renderModules();
// Add event listeners to filters
moduleSearch.addEventListener('input', renderModules);
stageFilter.addEventListener('change', renderModules);
componentFilter.addEventListener('change', renderModules);
templateFilter.addEventListener('change', renderModules);
console.log('Modules page initialized with', allModules.length, 'modules');
}
// Start initialization
init();
});

View File

@ -58,7 +58,7 @@
</select>
</div>
<textarea id="{{ .id }}" name="{{ .id }}" placeholder="{{ .placeholder }}" {{ if .required }}required{{ end }}></textarea>
<textarea id="{{ .id }}" name="{{ .id }}" {{ if .required }}required{{ end }} rows="2" style="min-height: 60px; overflow: hidden;"></textarea>
</div>
{{ end }}
</div>
@ -165,6 +165,8 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
// Textarea auto-resizing is now handled by the script in head.html
// Export dropdown removed (moved to sidebar)
// Preview Mode Toggle

View File

@ -1,36 +0,0 @@
{{ 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

@ -18,6 +18,82 @@
<script defer src="{{ "js/debug.js" | relURL }}"></script>
<script type="module" src="{{ "js/templates/index.js" | relURL }}"></script>
<script type="module" src="{{ "js/builder.js" | relURL }}"></script>
<!-- Auto-resize textareas script -->
<script>
// Ensure this runs before other scripts
window.addEventListener('DOMContentLoaded', function() {
// Function to resize textarea to fit content
function autoResizeTextarea(textarea) {
// Reset height temporarily to get the correct scrollHeight
textarea.style.height = 'auto';
// Set to scrollHeight + padding
textarea.style.height = Math.max(textarea.scrollHeight + 10, 60) + 'px';
}
// Setup resize on all textareas
function setupTextareaResizing() {
const textareas = document.querySelectorAll('textarea');
textareas.forEach(function(textarea) {
// Initial sizing
autoResizeTextarea(textarea);
// Resize on input
textarea.addEventListener('input', function() {
autoResizeTextarea(this);
});
// Handle value changes via all possible methods
let lastValue = textarea.value;
// Create mutation observer to watch for DOM changes affecting the textarea
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (textarea.value !== lastValue) {
lastValue = textarea.value;
autoResizeTextarea(textarea);
}
});
});
// Configure and start the observer
observer.observe(textarea, {
attributes: true,
attributeFilter: ['value'],
characterData: true,
subtree: true
});
// Also check periodically as a fallback for template applications
const valueChecker = setInterval(function() {
if (textarea.value !== lastValue) {
lastValue = textarea.value;
autoResizeTextarea(textarea);
}
}, 50); // More frequent checks
// Store the interval ID to avoid memory leaks
textarea.dataset.checkerInterval = valueChecker;
});
}
// Run setup immediately
setupTextareaResizing();
// Also run after a slight delay to catch any content loaded asynchronously
setTimeout(setupTextareaResizing, 100);
setTimeout(setupTextareaResizing, 500);
setTimeout(setupTextareaResizing, 1000);
// Add listener for template application
document.addEventListener('templateApplied', function() {
console.log('Template applied, resizing all textareas');
document.querySelectorAll('textarea').forEach(autoResizeTextarea);
});
});
</script>
{{ end }}
<!-- Additional custom JS from site parameters -->

View File

@ -7,7 +7,6 @@
<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>