FossilRepo
Split sidebar: FossilRepo Docs (read-only) + Knowledge Base (org wiki) FossilRepo Docs: product documentation (setup, API, architecture, etc.) with no + New button — managed by superusers only. Knowledge Base: org-wide wiki for user-created content (runbooks, guides, onboarding). Has + New for users with page permissions. Seeded with a home page explaining the purpose. Context processor splits pages by known product doc slugs vs user-created.
Commit
303df36bb6324ca187540e6c526b09f122d92a280b837f3d4c7f61a9f67cfc00
Parent
b603bf823721e96…
2 files changed
+11
-1
+35
-5
+11
-1
| --- core/context_processors.py | ||
| +++ core/context_processors.py | ||
| @@ -22,11 +22,21 @@ | ||
| 22 | 22 | grouped_projects.append({"group": group, "projects": group_projects}) |
| 23 | 23 | grouped_ids.update(p.id for p in group_projects) |
| 24 | 24 | |
| 25 | 25 | ungrouped_projects = [p for p in projects if p.id not in grouped_ids] |
| 26 | 26 | |
| 27 | + # Split pages: product docs (known slugs) vs org knowledge base (user-created) | |
| 28 | + PRODUCT_DOC_SLUGS = { | |
| 29 | + "agentic-development", "api-reference", "architecture", | |
| 30 | + "administration", "setup-guide", | |
| 31 | + } | |
| 32 | + product_docs = [p for p in pages if p.slug in PRODUCT_DOC_SLUGS] | |
| 33 | + kb_pages = [p for p in pages if p.slug not in PRODUCT_DOC_SLUGS] | |
| 34 | + | |
| 27 | 35 | return { |
| 28 | 36 | "sidebar_projects": projects, |
| 29 | 37 | "sidebar_grouped": grouped_projects, |
| 30 | 38 | "sidebar_ungrouped": ungrouped_projects, |
| 31 | - "sidebar_pages": pages, | |
| 39 | + "sidebar_pages": pages, # Keep for backwards compat | |
| 40 | + "sidebar_product_docs": product_docs, | |
| 41 | + "sidebar_kb_pages": kb_pages, | |
| 32 | 42 | } |
| 33 | 43 |
| --- core/context_processors.py | |
| +++ core/context_processors.py | |
| @@ -22,11 +22,21 @@ | |
| 22 | grouped_projects.append({"group": group, "projects": group_projects}) |
| 23 | grouped_ids.update(p.id for p in group_projects) |
| 24 | |
| 25 | ungrouped_projects = [p for p in projects if p.id not in grouped_ids] |
| 26 | |
| 27 | return { |
| 28 | "sidebar_projects": projects, |
| 29 | "sidebar_grouped": grouped_projects, |
| 30 | "sidebar_ungrouped": ungrouped_projects, |
| 31 | "sidebar_pages": pages, |
| 32 | } |
| 33 |
| --- core/context_processors.py | |
| +++ core/context_processors.py | |
| @@ -22,11 +22,21 @@ | |
| 22 | grouped_projects.append({"group": group, "projects": group_projects}) |
| 23 | grouped_ids.update(p.id for p in group_projects) |
| 24 | |
| 25 | ungrouped_projects = [p for p in projects if p.id not in grouped_ids] |
| 26 | |
| 27 | # Split pages: product docs (known slugs) vs org knowledge base (user-created) |
| 28 | PRODUCT_DOC_SLUGS = { |
| 29 | "agentic-development", "api-reference", "architecture", |
| 30 | "administration", "setup-guide", |
| 31 | } |
| 32 | product_docs = [p for p in pages if p.slug in PRODUCT_DOC_SLUGS] |
| 33 | kb_pages = [p for p in pages if p.slug not in PRODUCT_DOC_SLUGS] |
| 34 | |
| 35 | return { |
| 36 | "sidebar_projects": projects, |
| 37 | "sidebar_grouped": grouped_projects, |
| 38 | "sidebar_ungrouped": ungrouped_projects, |
| 39 | "sidebar_pages": pages, # Keep for backwards compat |
| 40 | "sidebar_product_docs": product_docs, |
| 41 | "sidebar_kb_pages": kb_pages, |
| 42 | } |
| 43 |
+35
-5
| --- templates/includes/sidebar.html | ||
| +++ templates/includes/sidebar.html | ||
| @@ -85,13 +85,13 @@ | ||
| 85 | 85 | {% endif %} |
| 86 | 86 | </div> |
| 87 | 87 | </div> |
| 88 | 88 | {% endif %} |
| 89 | 89 | |
| 90 | - <!-- FossilRepo Docs section --> | |
| 91 | - {% if perms.pages.view_page %} | |
| 92 | - <div> | |
| 90 | + <!-- FossilRepo Docs (product docs — read-only) --> | |
| 91 | + {% if sidebar_product_docs %} | |
| 92 | + <div x-data="{ docsOpen: false }"> | |
| 93 | 93 | <button @click="collapsed ? (collapsed = false, docsOpen = true) : (docsOpen = !docsOpen)" |
| 94 | 94 | class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/kb/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}" |
| 95 | 95 | :title="collapsed ? 'FossilRepo Docs' : ''"> |
| 96 | 96 | <span class="flex items-center gap-2"> |
| 97 | 97 | <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| @@ -102,17 +102,47 @@ | ||
| 102 | 102 | <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="docsOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 103 | 103 | <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> |
| 104 | 104 | </svg> |
| 105 | 105 | </button> |
| 106 | 106 | <div x-show="docsOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3"> |
| 107 | - {% for p in sidebar_pages %} | |
| 107 | + {% for p in sidebar_product_docs %} | |
| 108 | + <a href="{% url 'pages:detail' slug=p.slug %}" | |
| 109 | + class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate"> | |
| 110 | + {{ p.name }} | |
| 111 | + </a> | |
| 112 | + {% endfor %} | |
| 113 | + </div> | |
| 114 | + </div> | |
| 115 | + {% endif %} | |
| 116 | + | |
| 117 | + <!-- Knowledge Base (org wiki — user-editable) --> | |
| 118 | + {% if perms.pages.view_page %} | |
| 119 | + <div x-data="{ kbOpen: false }"> | |
| 120 | + <button @click="collapsed ? (collapsed = false, kbOpen = true) : (kbOpen = !kbOpen)" | |
| 121 | + class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium text-gray-400 hover:bg-gray-800 hover:text-white" | |
| 122 | + :title="collapsed ? 'Knowledge Base' : ''"> | |
| 123 | + <span class="flex items-center gap-2"> | |
| 124 | + <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> | |
| 125 | + <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776" /> | |
| 126 | + </svg> | |
| 127 | + <span x-show="!collapsed" class="truncate">Knowledge Base</span> | |
| 128 | + </span> | |
| 129 | + <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="kbOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> | |
| 130 | + <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> | |
| 131 | + </svg> | |
| 132 | + </button> | |
| 133 | + <div x-show="kbOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3"> | |
| 134 | + {% for p in sidebar_kb_pages %} | |
| 108 | 135 | <a href="{% url 'pages:detail' slug=p.slug %}" |
| 109 | 136 | class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate"> |
| 110 | 137 | {{ p.name }} |
| 111 | 138 | </a> |
| 112 | 139 | {% endfor %} |
| 113 | - {% if user.is_superuser %} | |
| 140 | + {% if sidebar_kb_pages|length == 0 %} | |
| 141 | + <p class="px-3 py-1.5 text-xs text-gray-600">No articles yet.</p> | |
| 142 | + {% endif %} | |
| 143 | + {% if perms.pages.add_page %} | |
| 114 | 144 | <a href="{% url 'pages:create' %}" |
| 115 | 145 | class="block rounded-md px-3 py-1.5 text-sm text-gray-600 hover:text-brand-light"> |
| 116 | 146 | + New |
| 117 | 147 | </a> |
| 118 | 148 | {% endif %} |
| 119 | 149 |
| --- templates/includes/sidebar.html | |
| +++ templates/includes/sidebar.html | |
| @@ -85,13 +85,13 @@ | |
| 85 | {% endif %} |
| 86 | </div> |
| 87 | </div> |
| 88 | {% endif %} |
| 89 | |
| 90 | <!-- FossilRepo Docs section --> |
| 91 | {% if perms.pages.view_page %} |
| 92 | <div> |
| 93 | <button @click="collapsed ? (collapsed = false, docsOpen = true) : (docsOpen = !docsOpen)" |
| 94 | class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/kb/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}" |
| 95 | :title="collapsed ? 'FossilRepo Docs' : ''"> |
| 96 | <span class="flex items-center gap-2"> |
| 97 | <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| @@ -102,17 +102,47 @@ | |
| 102 | <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="docsOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 103 | <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> |
| 104 | </svg> |
| 105 | </button> |
| 106 | <div x-show="docsOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3"> |
| 107 | {% for p in sidebar_pages %} |
| 108 | <a href="{% url 'pages:detail' slug=p.slug %}" |
| 109 | class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate"> |
| 110 | {{ p.name }} |
| 111 | </a> |
| 112 | {% endfor %} |
| 113 | {% if user.is_superuser %} |
| 114 | <a href="{% url 'pages:create' %}" |
| 115 | class="block rounded-md px-3 py-1.5 text-sm text-gray-600 hover:text-brand-light"> |
| 116 | + New |
| 117 | </a> |
| 118 | {% endif %} |
| 119 |
| --- templates/includes/sidebar.html | |
| +++ templates/includes/sidebar.html | |
| @@ -85,13 +85,13 @@ | |
| 85 | {% endif %} |
| 86 | </div> |
| 87 | </div> |
| 88 | {% endif %} |
| 89 | |
| 90 | <!-- FossilRepo Docs (product docs — read-only) --> |
| 91 | {% if sidebar_product_docs %} |
| 92 | <div x-data="{ docsOpen: false }"> |
| 93 | <button @click="collapsed ? (collapsed = false, docsOpen = true) : (docsOpen = !docsOpen)" |
| 94 | class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/kb/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}" |
| 95 | :title="collapsed ? 'FossilRepo Docs' : ''"> |
| 96 | <span class="flex items-center gap-2"> |
| 97 | <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| @@ -102,17 +102,47 @@ | |
| 102 | <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="docsOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 103 | <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> |
| 104 | </svg> |
| 105 | </button> |
| 106 | <div x-show="docsOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3"> |
| 107 | {% for p in sidebar_product_docs %} |
| 108 | <a href="{% url 'pages:detail' slug=p.slug %}" |
| 109 | class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate"> |
| 110 | {{ p.name }} |
| 111 | </a> |
| 112 | {% endfor %} |
| 113 | </div> |
| 114 | </div> |
| 115 | {% endif %} |
| 116 | |
| 117 | <!-- Knowledge Base (org wiki — user-editable) --> |
| 118 | {% if perms.pages.view_page %} |
| 119 | <div x-data="{ kbOpen: false }"> |
| 120 | <button @click="collapsed ? (collapsed = false, kbOpen = true) : (kbOpen = !kbOpen)" |
| 121 | class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium text-gray-400 hover:bg-gray-800 hover:text-white" |
| 122 | :title="collapsed ? 'Knowledge Base' : ''"> |
| 123 | <span class="flex items-center gap-2"> |
| 124 | <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 125 | <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776" /> |
| 126 | </svg> |
| 127 | <span x-show="!collapsed" class="truncate">Knowledge Base</span> |
| 128 | </span> |
| 129 | <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="kbOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 130 | <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> |
| 131 | </svg> |
| 132 | </button> |
| 133 | <div x-show="kbOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3"> |
| 134 | {% for p in sidebar_kb_pages %} |
| 135 | <a href="{% url 'pages:detail' slug=p.slug %}" |
| 136 | class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate"> |
| 137 | {{ p.name }} |
| 138 | </a> |
| 139 | {% endfor %} |
| 140 | {% if sidebar_kb_pages|length == 0 %} |
| 141 | <p class="px-3 py-1.5 text-xs text-gray-600">No articles yet.</p> |
| 142 | {% endif %} |
| 143 | {% if perms.pages.add_page %} |
| 144 | <a href="{% url 'pages:create' %}" |
| 145 | class="block rounded-md px-3 py-1.5 text-sm text-gray-600 hover:text-brand-light"> |
| 146 | + New |
| 147 | </a> |
| 148 | {% endif %} |
| 149 |