fix(wiki): keep raw-materials & recent-activity readable for read-only viewers

The KB workspace split into a reading view (pages + graph) and a manage view
(raw materials, config, transformations, advanced, recent-activity snapshot)
gated behind manage:wiki. That moved the raw-materials and recent-activity
surfaces — which read-only viewers (view:wiki without manage:wiki) could
previously browse — entirely behind the management gate, silently dropping
their access.

Re-surface both in the reading-view segmented toggle for viewers who lack a
management view. Managers keep the focused pages/graph toggle and still reach
these surfaces through the management view, so nothing is duplicated for them.
The content panels already render by activeTab, so this only widens the
reading toggle and the activeTab/readingTab types.
This commit is contained in:
matevip 2026-06-10 17:46:19 +08:00
parent 83615593cd
commit 92d401bc1f
2 changed files with 55 additions and 5 deletions

View File

@ -4,7 +4,7 @@
:kb="kb"
:mode="store.workspaceMode"
:can-manage="canManageWiki"
:reading-tab="activeTab === 'graph' ? 'graph' : 'pages'"
:reading-tab="readingTab"
@back="store.backToLibrary()"
@switch-mode="store.setWorkspaceMode($event)"
@switch-reading="activeTab = $event"
@ -114,9 +114,24 @@ const workspace = useWorkspaceStore()
// the processing-config and transformations tabs are management surfaces.
const canManageWiki = computed(() => workspace.can('manage:wiki'))
const activeTab = ref('pages')
// Reading-view surfaces (driven by the header toggle) vs management-only surfaces
// (driven by the manage-view tab strip). 'raw' and 'hotCache' appear in both: in
// the reading toggle for read-only viewers, and in the management tab strip for
// managers so the union spans every key activeTab can take.
type ReadingTab = 'pages' | 'graph' | 'raw' | 'hotCache'
type WikiTab = ReadingTab | 'config' | 'transformations' | 'advanced'
const activeTab = ref<WikiTab>('pages')
const brokenPanelOpen = ref(false)
// Narrow activeTab to the reading subset for the header toggle's highlight. In
// browse mode activeTab is always a reading key; the management-only keys map to
// 'pages' as a harmless default (the toggle isn't rendered in manage mode).
const readingTab = computed<ReadingTab>(() => {
const t = activeTab.value
return t === 'graph' || t === 'raw' || t === 'hotCache' ? t : 'pages'
})
// When a page becomes the currentPage (e.g. via the global wikilink click
// handler that lands on /wiki?kbId=X&slug=Y), switch the tab to 'pages' so
// the viewer is the thing the user sees. Only meaningful in the reading view
@ -129,7 +144,7 @@ watch(() => store.currentPage, (page) => {
// header's segmented control instead, so it has no tabs here. Entry into manage
// mode is gated by manage:wiki, but guard here too so a read-only viewer never
// sees management tabs even if the mode is somehow forced.
const tabs = computed(() => {
const tabs = computed<{ key: WikiTab; label: string }[]>(() => {
if (!canManageWiki.value) return []
return [
{ key: 'raw', label: t('wiki.rawMaterials') },

View File

@ -41,6 +41,37 @@
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="6" r="2.5"/><circle cx="19" cy="6" r="2.5"/><circle cx="12" cy="18" r="2.5"/><path d="M7 7.5 10.5 16M17 7.5 13.5 16M6.7 6h10.6"/></svg>
{{ t('wiki.graph.tab') }}
</button>
<!--
Read-only viewers have no management view (the gear requires
manage:wiki), so the raw-materials and recent-activity snapshot both
read-friendly surfaces are offered here in the reading toggle instead.
Managers reach them through the management view, so these stay hidden
for them to keep the reading toggle focused on pages/graph.
-->
<template v-if="!canManage">
<button
type="button"
class="reading-toggle-btn"
:class="{ active: readingTab === 'raw' }"
role="tab"
:aria-selected="readingTab === 'raw'"
@click="$emit('switch-reading', 'raw')"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2 2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
{{ t('wiki.rawMaterials') }}
</button>
<button
type="button"
class="reading-toggle-btn"
:class="{ active: readingTab === 'hotCache' }"
role="tab"
:aria-selected="readingTab === 'hotCache'"
@click="$emit('switch-reading', 'hotCache')"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 15 14"/></svg>
{{ t('wiki.hotCache.tab') }}
</button>
</template>
</div>
<button
v-if="mode === 'browse' && canManage"
@ -88,16 +119,20 @@ import { useI18n } from 'vue-i18n'
import type { WikiKB } from '@/stores/useWikiStore'
import { kbAccent, kbAccentFg, kbInitial } from '../utils/kbVisual'
// Reading-view surfaces: pages + graph for everyone, plus raw materials and the
// recent-activity snapshot for read-only viewers who have no management view.
type ReadingTab = 'pages' | 'graph' | 'raw' | 'hotCache'
const props = defineProps<{
kb: WikiKB
mode: 'browse' | 'manage'
canManage: boolean
readingTab: 'pages' | 'graph'
readingTab: ReadingTab
}>()
defineEmits<{
(e: 'back'): void
(e: 'switch-mode', mode: 'browse' | 'manage'): void
(e: 'switch-reading', tab: 'pages' | 'graph'): void
(e: 'switch-reading', tab: ReadingTab): void
}>()
const { t } = useI18n()