Removed the Module Library, no longer necessary
This commit is contained in:
@ -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.
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
});
|
@ -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
|
||||
|
@ -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 }}
|
@ -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 -->
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user