feat: improve documentation UI and add published docs indicators (#5620)
Co-authored-by: mirarifhasan <arif.ishan05@gmail.com>
This commit is contained in:
parent
1e8edd2c9c
commit
ab52efc075
19 changed files with 613 additions and 195 deletions
|
|
@ -34,6 +34,10 @@ import { InfraConfigModule } from 'src/infra-config/infra-config.module';
|
|||
})
|
||||
export class AuthModule {
|
||||
static async register() {
|
||||
if (process.env.GENERATE_GQL_SCHEMA === 'true') {
|
||||
return { module: AuthModule };
|
||||
}
|
||||
|
||||
const isInfraConfigPopulated = await isInfraConfigTablePopulated();
|
||||
if (!isInfraConfigPopulated) {
|
||||
return { module: AuthModule };
|
||||
|
|
|
|||
|
|
@ -119,8 +119,9 @@ export class PublishedDocsResolver {
|
|||
name: 'collectionID',
|
||||
type: () => ID,
|
||||
description: 'Id of the collection to add to',
|
||||
nullable: true,
|
||||
})
|
||||
collectionID: string,
|
||||
collectionID: string | undefined,
|
||||
@Args() args: OffsetPaginationArgs,
|
||||
) {
|
||||
const docs = await this.publishedDocsService.getAllTeamPublishedDocs(
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import {
|
|||
import { TeamAccessRole } from 'src/team/team.model';
|
||||
import { TreeLevel } from './published-docs.dto';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { right } from 'fp-ts/lib/EitherT';
|
||||
|
||||
const mockPrisma = mockDeep<PrismaService>();
|
||||
const mockUserCollectionService = mockDeep<UserCollectionService>();
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import { OffsetPaginationArgs } from 'src/types/input-types.args';
|
|||
import { stringToJson } from 'src/utils';
|
||||
import { UserCollectionService } from 'src/user-collection/user-collection.service';
|
||||
import { TeamCollectionService } from 'src/team-collection/team-collection.service';
|
||||
import { CollectionFolder } from 'src/types/CollectionFolder';
|
||||
import { GetPublishedDocsQueryDto, TreeLevel } from './published-docs.dto';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
|
|
@ -275,7 +274,7 @@ export class PublishedDocsService {
|
|||
*/
|
||||
async getAllTeamPublishedDocs(
|
||||
teamID: string,
|
||||
collectionID: string,
|
||||
collectionID: string | undefined,
|
||||
args: OffsetPaginationArgs,
|
||||
) {
|
||||
const docs = await this.prisma.publishedDocs.findMany({
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import {
|
|||
UserCollection,
|
||||
UserCollectionDuplicatedData,
|
||||
UserCollectionExportJSONData,
|
||||
UserCollectionImportResult,
|
||||
UserCollectionRemovedData,
|
||||
UserCollectionReorderData,
|
||||
} from './user-collections.model';
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
"more": "More",
|
||||
"new": "New",
|
||||
"no": "No",
|
||||
"open": "Open",
|
||||
"open_workspace": "Open workspace",
|
||||
"paste": "Paste",
|
||||
"prettify": "Prettify",
|
||||
|
|
@ -69,6 +70,7 @@
|
|||
"turn_off": "Turn off",
|
||||
"turn_on": "Turn on",
|
||||
"undo": "Undo",
|
||||
"unpublish": "Unpublish",
|
||||
"yes": "Yes",
|
||||
"verify": "Verify",
|
||||
"enable": "Enable",
|
||||
|
|
@ -504,13 +506,15 @@
|
|||
"auto_sync_description": "Automatically update published docs when collection changes",
|
||||
"button": "Publish",
|
||||
"copy_url": "Copy URL",
|
||||
"delete_published_doc": "Are you sure you want to delete the published documentation?",
|
||||
"delete": "Delete Documentation",
|
||||
"unpublish_doc": "Are you sure you want to unpublish the documentation?",
|
||||
"delete_success": "Published documentation deleted successfully",
|
||||
"doc_title": "Title",
|
||||
"doc_version": "Version",
|
||||
"edit_published_doc": "Edit Published Doc",
|
||||
"last_updated": "Last Updated",
|
||||
"metadata": "Metadata (JSON)",
|
||||
"open_published_doc": "Open Published Documentation in new tab",
|
||||
"publish_error": "Failed to publish documentation",
|
||||
"publish_success": "Documentation published successfully!",
|
||||
"published": "Published",
|
||||
|
|
@ -520,6 +524,7 @@
|
|||
"update_error": "Failed to update documentation",
|
||||
"update_published_docs": "Update Published Docs",
|
||||
"update_success": "Documentation updated successfully!",
|
||||
"unpublish": "Unpublish",
|
||||
"update_title": "Update Published Documentation",
|
||||
"url_copied": "URL copied to clipboard!",
|
||||
"view_published": "View Published Docs",
|
||||
|
|
|
|||
|
|
@ -76,8 +76,18 @@
|
|||
}"
|
||||
/>
|
||||
</span>
|
||||
<!-- Published Doc Status Indicator -->
|
||||
<span
|
||||
v-if="publishedDocStatus"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('documentation.publish.published')"
|
||||
class="ml-2 flex items-center"
|
||||
>
|
||||
<component :is="IconGlobe" class="svg-icons text-green-500" />
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isCollectionLoading && !isOpen"
|
||||
class="flex items-center px-2"
|
||||
|
|
@ -179,6 +189,19 @@
|
|||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="isDocumentationVisible"
|
||||
ref="documentationAction"
|
||||
:icon="IconBook"
|
||||
:label="t('documentation.title')"
|
||||
:shortcut="['I']"
|
||||
@click="
|
||||
() => {
|
||||
handleDocumentationAction()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="
|
||||
!hasNoTeamAccess &&
|
||||
|
|
@ -250,19 +273,7 @@
|
|||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="isDocumentationVisible"
|
||||
ref="documentationAction"
|
||||
:icon="IconBook"
|
||||
:label="t('documentation.title')"
|
||||
:shortcut="['I']"
|
||||
@click="
|
||||
() => {
|
||||
handleDocumentationAction()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
||||
<HoppSmartItem
|
||||
ref="propertiesAction"
|
||||
:icon="IconSettings2"
|
||||
|
|
@ -346,6 +357,8 @@ import { useMockServerStatus } from "~/composables/mockServer"
|
|||
import { useMockServerVisibility } from "~/composables/mockServerVisibility"
|
||||
import { platform } from "~/platform"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
import { DocumentationService } from "~/services/documentation.service"
|
||||
import IconGlobe from "~icons/lucide/globe"
|
||||
|
||||
type CollectionType = "my-collections" | "team-collections"
|
||||
type FolderType = "collection" | "folder"
|
||||
|
|
@ -500,6 +513,19 @@ const mockServerStatus = computed(() => {
|
|||
return getMockServerStatus(collectionId || "")
|
||||
})
|
||||
|
||||
// Published Doc Status
|
||||
const documentationService = useService(DocumentationService)
|
||||
|
||||
const publishedDocStatus = computed(() => {
|
||||
const collectionId =
|
||||
props.collectionsType === "my-collections"
|
||||
? ((props.data as HoppCollection).id ??
|
||||
(props.data as HoppCollection)._ref_id)
|
||||
: (props.data as TeamCollection).id
|
||||
|
||||
return documentationService.getPublishedDocStatus(collectionId || "")
|
||||
})
|
||||
|
||||
// Determine if this is a root collection (not a child folder)
|
||||
const isRootCollection = computed(() => {
|
||||
return props.folderType === "collection"
|
||||
|
|
|
|||
|
|
@ -875,18 +875,13 @@ const updateCollectionOrder = (
|
|||
}
|
||||
|
||||
const debouncedSorting = useDebounceFn(() => {
|
||||
sortCollection()
|
||||
}, 250)
|
||||
|
||||
const sortCollection = () => {
|
||||
currentSortOrder.value = currentSortOrder.value === "asc" ? "desc" : "asc"
|
||||
|
||||
emit("sort-collections", {
|
||||
collectionID: null,
|
||||
sortOrder: currentSortOrder.value,
|
||||
collectionRefID: "personal",
|
||||
})
|
||||
}
|
||||
}, 250)
|
||||
|
||||
type MyCollectionNode = Collection | Folder | Requests
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
<template>
|
||||
<div class="rounded-sm relative h-full" @click.stop>
|
||||
<div
|
||||
v-if="!(readOnly && isEmpty)"
|
||||
class="rounded-sm relative h-full"
|
||||
@click.stop
|
||||
>
|
||||
<!-- Edit mode textarea -->
|
||||
<template v-if="editMode && !readOnly">
|
||||
<textarea
|
||||
ref="textareaRef"
|
||||
v-model="internalContent"
|
||||
class="text-wrap w-full p-4 rounded-sm text-sm font-mono text-secondaryLight outline-none resize-none focus:border focus:border-accent focus:bg-primaryLight transition"
|
||||
class="text-wrap w-full p-4 rounded-sm text-sm font-mono text-secondary outline-none resize-none focus:border focus:border-accent focus:bg-primaryLight transition placeholder:text-secondaryLight"
|
||||
:style="{ height: textareaHeight + 'px' }"
|
||||
spellcheck="false"
|
||||
:placeholder="placeholder"
|
||||
|
|
@ -74,6 +78,11 @@ const textareaHeight = ref<number>(200)
|
|||
// Internal content that syncs with modelValue
|
||||
const internalContent = ref<string>(props.modelValue)
|
||||
|
||||
// Check if the content is empty
|
||||
const isEmpty = computed(
|
||||
() => !internalContent.value || internalContent.value.trim() === ""
|
||||
)
|
||||
|
||||
// Watch for external changes to modelValue
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
|
|
@ -88,7 +97,7 @@ watch(
|
|||
// Render markdown content with DOMPurify sanitization
|
||||
const renderedMarkdown = computed(() => {
|
||||
try {
|
||||
if (!internalContent.value || internalContent.value.trim() === "") {
|
||||
if (isEmpty.value) {
|
||||
return DOMPurify.sanitize(
|
||||
`<p class='text-secondaryLight italic'>${props.placeholder || t("documentation.add_description_placeholder")}</p>`
|
||||
)
|
||||
|
|
@ -201,7 +210,7 @@ onMounted(() => {
|
|||
/* List styles */
|
||||
.markdown-content :deep(ul),
|
||||
.markdown-content :deep(ol) {
|
||||
@apply pl-6 my-3 text-sm text-secondaryLight space-y-1;
|
||||
@apply pl-6 my-3 text-sm text-secondary space-y-1;
|
||||
}
|
||||
|
||||
.markdown-content :deep(li > ul),
|
||||
|
|
@ -252,7 +261,7 @@ onMounted(() => {
|
|||
}
|
||||
|
||||
.markdown-content :deep(td) {
|
||||
@apply border border-divider px-3 py-1 text-secondaryLight;
|
||||
@apply border border-divider px-3 py-1 text-secondary;
|
||||
@apply bg-primaryDark;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,21 +70,56 @@
|
|||
input-styles="floating-input"
|
||||
class="flex-1 opacity-80 cursor-not-allowed"
|
||||
/>
|
||||
<HoppButtonSecondary :icon="copyIcon" outline @click="copyUrl" />
|
||||
<HoppButtonSecondary
|
||||
v-if="publishedUrl"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('documentation.publish.copy_url')"
|
||||
:icon="copyIcon"
|
||||
outline
|
||||
@click="copyUrl"
|
||||
/>
|
||||
<HoppButtonPrimary
|
||||
v-if="(mode === 'view' || mode === 'update') && publishedUrl"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('documentation.publish.open_published_doc')"
|
||||
:label="t('action.open')"
|
||||
:icon="IconExternalLink"
|
||||
@click="viewPublished"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<template #footer>
|
||||
<div class="flex justify-between items-center flex-1">
|
||||
<div class="flex items-center w-full space-x-2">
|
||||
<HoppButtonPrimary
|
||||
v-if="mode === 'view' || mode === 'update'"
|
||||
:label="t('documentation.publish.view_published')"
|
||||
:icon="IconExternalLink"
|
||||
@click="viewPublished"
|
||||
v-if="mode === 'create' && !publishedUrl"
|
||||
:label="t('documentation.publish.button')"
|
||||
:disabled="!canPublish || loading"
|
||||
:loading="loading"
|
||||
@click="handlePublish"
|
||||
/>
|
||||
<HoppButtonPrimary
|
||||
v-else-if="mode === 'update'"
|
||||
:label="t('documentation.publish.update_button')"
|
||||
:disabled="!canPublish || loading || !hasChanges"
|
||||
:loading="loading"
|
||||
@click="handleUpdate"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.cancel')"
|
||||
outline
|
||||
filled
|
||||
@click="hideModal"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-if="mode === 'update'"
|
||||
:icon="IconTrash2"
|
||||
label="Delete Documentation"
|
||||
:label="t('documentation.publish.unpublish')"
|
||||
class="!text-red-500"
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
|
|
@ -95,37 +130,12 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-between items-center w-full">
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.cancel')"
|
||||
outline
|
||||
filled
|
||||
@click="hideModal"
|
||||
/>
|
||||
<HoppButtonPrimary
|
||||
v-if="mode === 'create' && !publishedUrl"
|
||||
:label="t('documentation.publish.button')"
|
||||
:disabled="!canPublish || loading"
|
||||
:loading="loading"
|
||||
@click="handlePublish"
|
||||
/>
|
||||
<HoppButtonPrimary
|
||||
v-else-if="mode === 'update'"
|
||||
:label="t('documentation.publish.update_button')"
|
||||
:disabled="!canPublish || loading || !hasChanges"
|
||||
:loading="loading"
|
||||
@click="handleUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartModal>
|
||||
|
||||
<HoppSmartConfirmModal
|
||||
:show="showDeleteConfirmModal"
|
||||
:title="t('documentation.publish.delete_published_doc')"
|
||||
:confirm="t('action.delete')"
|
||||
:title="t('documentation.publish.unpublish_doc')"
|
||||
:confirm="t('action.unpublish')"
|
||||
:loading-state="loading"
|
||||
@hide-modal="showDeleteConfirmModal = false"
|
||||
@resolve="confirmDelete"
|
||||
|
|
|
|||
|
|
@ -51,12 +51,6 @@
|
|||
<template #footer>
|
||||
<div class="flex justify-between items-center w-full">
|
||||
<span class="flex space-x-2">
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.close')"
|
||||
outline
|
||||
filled
|
||||
@click="hideModal"
|
||||
/>
|
||||
<HoppButtonPrimary
|
||||
v-if="hasTeamWriteAccess"
|
||||
:label="t('action.save')"
|
||||
|
|
@ -66,28 +60,29 @@
|
|||
filled
|
||||
@click="saveDocumentation"
|
||||
/>
|
||||
<!-- Publish Button - Simple button when not published -->
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.close')"
|
||||
outline
|
||||
filled
|
||||
@click="hideModal"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<div class="flex space-x-2 items-center">
|
||||
<!-- Publish Button - Simple button when not published -->
|
||||
<HoppButtonSecondary
|
||||
v-if="
|
||||
currentCollection && !isCollectionPublished && hasTeamWriteAccess
|
||||
"
|
||||
:icon="isCheckingPublishedStatus ? IconLoader2 : IconShare2"
|
||||
:icon="IconShare2"
|
||||
:label="t('documentation.publish.button')"
|
||||
:loading="isCheckingPublishedStatus"
|
||||
:disabled="isCheckingPublishedStatus"
|
||||
outline
|
||||
filled
|
||||
@click="openPublishModal"
|
||||
/>
|
||||
<tippy
|
||||
v-else-if="
|
||||
currentCollection &&
|
||||
isCollectionPublished &&
|
||||
!isCheckingPublishedStatus &&
|
||||
hasTeamWriteAccess
|
||||
currentCollection && isCollectionPublished && hasTeamWriteAccess
|
||||
"
|
||||
ref="publishedDropdown"
|
||||
interactive
|
||||
|
|
@ -104,8 +99,6 @@
|
|||
:icon="IconCheveronDown"
|
||||
reverse
|
||||
:label="t('documentation.publish.published')"
|
||||
:loading="isCheckingPublishedStatus"
|
||||
:disabled="isCheckingPublishedStatus"
|
||||
class="!pr-2"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -126,7 +119,7 @@
|
|||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.copy')"
|
||||
:title="t('documentation.publish.copy_url')"
|
||||
:icon="copyIcon"
|
||||
@click="copyPublishedUrl"
|
||||
/>
|
||||
|
|
@ -235,10 +228,6 @@ import {
|
|||
deletePublishedDoc,
|
||||
updatePublishedDoc,
|
||||
} from "~/helpers/backend/mutations/PublishedDocs"
|
||||
import {
|
||||
getUserPublishedDocs,
|
||||
getTeamPublishedDocs,
|
||||
} from "~/helpers/backend/queries/PublishedDocs"
|
||||
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
|
||||
|
|
@ -284,7 +273,6 @@ const documentationService = useService(DocumentationService)
|
|||
|
||||
const isLoadingTeamCollection = ref<boolean>(false)
|
||||
const isSavingDocumentation = ref<boolean>(false)
|
||||
const isCheckingPublishedStatus = ref<boolean>(false)
|
||||
const isProcessingPublish = ref<boolean>(false)
|
||||
|
||||
const copyIcon = refAutoReset(markRaw(IconCopy), 3000)
|
||||
|
|
@ -299,17 +287,22 @@ const publishedDropdown = ref<TippyComponent | null>(null)
|
|||
const publishedDropdownActions = ref<HTMLDivElement | null>(null)
|
||||
|
||||
// Published docs state
|
||||
const isCollectionPublished = ref<boolean>(false)
|
||||
const publishedDocId = ref<string | undefined>(undefined)
|
||||
const existingPublishedData = ref<
|
||||
| {
|
||||
title: string
|
||||
version: string
|
||||
autoSync: boolean
|
||||
url: string
|
||||
}
|
||||
| undefined
|
||||
>(undefined)
|
||||
const publishedDocStatus = computed(() => {
|
||||
if (!props.collectionID) return undefined
|
||||
return documentationService.getPublishedDocStatus(props.collectionID)
|
||||
})
|
||||
|
||||
const isCollectionPublished = computed(() => !!publishedDocStatus.value)
|
||||
const publishedDocId = computed(() => publishedDocStatus.value?.id)
|
||||
const existingPublishedData = computed(() => {
|
||||
if (!publishedDocStatus.value) return undefined
|
||||
return {
|
||||
title: publishedDocStatus.value.title,
|
||||
version: publishedDocStatus.value.version,
|
||||
autoSync: publishedDocStatus.value.autoSync,
|
||||
url: publishedDocStatus.value.url,
|
||||
}
|
||||
})
|
||||
|
||||
const publishModalMode = computed<"create" | "update" | "view">(() => {
|
||||
return isCollectionPublished.value ? "update" : "create"
|
||||
|
|
@ -412,81 +405,12 @@ const handleToggleAllDocumentation = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
// Check for existing published docs status
|
||||
const checkPublishedDocsStatus = async () => {
|
||||
if (!props.collectionID) return
|
||||
|
||||
isCheckingPublishedStatus.value = true
|
||||
|
||||
isCollectionPublished.value = false
|
||||
publishedDocId.value = undefined
|
||||
existingPublishedData.value = undefined
|
||||
|
||||
// Check if collection is already published
|
||||
if (props.isTeamCollection && props.teamID) {
|
||||
await pipe(
|
||||
getTeamPublishedDocs(props.teamID, props.collectionID),
|
||||
TE.match(
|
||||
(error) => {
|
||||
console.error("No published docs found or error:", error)
|
||||
isCheckingPublishedStatus.value = false
|
||||
},
|
||||
(docs) => {
|
||||
// Find published doc for this collection
|
||||
const publishedDoc = docs.find(
|
||||
(doc) => doc.collection.id === props.collectionID
|
||||
)
|
||||
if (publishedDoc) {
|
||||
isCollectionPublished.value = true
|
||||
publishedDocId.value = publishedDoc.id
|
||||
existingPublishedData.value = {
|
||||
title: publishedDoc.title,
|
||||
version: publishedDoc.version,
|
||||
autoSync: publishedDoc.autoSync,
|
||||
url: publishedDoc.url,
|
||||
}
|
||||
}
|
||||
isCheckingPublishedStatus.value = false
|
||||
}
|
||||
)
|
||||
)()
|
||||
} else {
|
||||
await pipe(
|
||||
getUserPublishedDocs(),
|
||||
TE.match(
|
||||
(error) => {
|
||||
console.error("No published docs found or error:", error)
|
||||
isCheckingPublishedStatus.value = false
|
||||
},
|
||||
(docs) => {
|
||||
// Find published doc for this collection
|
||||
const publishedDoc = docs.find(
|
||||
(doc) => doc.collection.id === props.collectionID
|
||||
)
|
||||
if (publishedDoc) {
|
||||
isCollectionPublished.value = true
|
||||
publishedDocId.value = publishedDoc.id
|
||||
existingPublishedData.value = {
|
||||
title: publishedDoc.title,
|
||||
version: publishedDoc.version,
|
||||
autoSync: publishedDoc.autoSync,
|
||||
url: publishedDoc.url,
|
||||
}
|
||||
}
|
||||
isCheckingPublishedStatus.value = false
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
}
|
||||
|
||||
// Reset fetched collection data when modal opens/closes
|
||||
watch(
|
||||
() => props.show,
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
// Check for existing published docs when modal opens
|
||||
await checkPublishedDocsStatus()
|
||||
// No need to manually check published docs status as it is now reactive
|
||||
} else {
|
||||
// Reset when modal closes
|
||||
fullCollectionData.value = null
|
||||
|
|
@ -874,16 +798,21 @@ const handlePublish = async (doc: CreatePublishedDocsArgs) => {
|
|||
const url = data.createPublishedDoc.url
|
||||
toast.success(t("documentation.publish.publish_success"))
|
||||
|
||||
// Update state
|
||||
isCollectionPublished.value = true
|
||||
publishedDocId.value = data.createPublishedDoc.id
|
||||
|
||||
existingPublishedData.value = {
|
||||
const newDocInfo = {
|
||||
id: data.createPublishedDoc.id,
|
||||
title: doc.title,
|
||||
version: doc.version,
|
||||
autoSync: doc.autoSync,
|
||||
url: url,
|
||||
}
|
||||
|
||||
// Update service
|
||||
if (props.collectionID) {
|
||||
documentationService.setPublishedDocStatus(
|
||||
props.collectionID,
|
||||
newDocInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
)()
|
||||
|
|
@ -904,12 +833,21 @@ const handleUpdate = async (id: string, doc: UpdatePublishedDocsArgs) => {
|
|||
toast.success(t("documentation.publish.update_success"))
|
||||
// Update existing data
|
||||
if (existingPublishedData.value) {
|
||||
existingPublishedData.value = {
|
||||
const updatedDocInfo = {
|
||||
id: id,
|
||||
title: data.updatePublishedDoc.title,
|
||||
version: data.updatePublishedDoc.version,
|
||||
autoSync: data.updatePublishedDoc.autoSync,
|
||||
url: url,
|
||||
}
|
||||
|
||||
// Update service
|
||||
if (props.collectionID) {
|
||||
documentationService.setPublishedDocStatus(
|
||||
props.collectionID,
|
||||
updatedDocInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -931,10 +869,12 @@ const handleDelete = async () => {
|
|||
() => {
|
||||
toast.success(t("documentation.publish.delete_success"))
|
||||
|
||||
isCollectionPublished.value = false
|
||||
publishedDocId.value = undefined
|
||||
existingPublishedData.value = undefined
|
||||
showPublishModal.value = false
|
||||
|
||||
// Update service
|
||||
if (props.collectionID) {
|
||||
documentationService.setPublishedDocStatus(props.collectionID, null)
|
||||
}
|
||||
}
|
||||
)
|
||||
)()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-md font-bold text-secondaryDark">
|
||||
<span
|
||||
class="text-md font-bold text-secondaryDark px-6 py-1 rounded-full border border-dividerDark shadow"
|
||||
>
|
||||
{{ publishedDoc?.title || "Untitled Project" }}
|
||||
</span>
|
||||
<!-- TODO: Add version (will be added in next iteration) -->
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
query TeamPublishedDocsList(
|
||||
$teamID: ID!
|
||||
$collectionID: ID!
|
||||
$collectionID: ID
|
||||
$skip: Int!
|
||||
$take: Int!
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export const getUserPublishedDocs = (skip: number = 0, take: number = 100) =>
|
|||
|
||||
export const getTeamPublishedDocs = (
|
||||
teamID: string,
|
||||
collectionID: string,
|
||||
collectionID?: string,
|
||||
skip: number = 0,
|
||||
take: number = 100
|
||||
) =>
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import { PublishedDocs } from "~/helpers/backend/graphql"
|
|||
import { getKernelMode } from "@hoppscotch/kernel"
|
||||
import { platform } from "~/platform"
|
||||
import { useReadonlyStream } from "~/composables/stream"
|
||||
import { usePageHead } from "~/composables/head"
|
||||
|
||||
const route = useRoute()
|
||||
const t = useI18n()
|
||||
|
|
@ -203,6 +204,74 @@ onMounted(async () => {
|
|||
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
usePageHead({
|
||||
title: computed(
|
||||
() => publishedDoc.value?.title || "Hoppscotch Documentation"
|
||||
),
|
||||
meta: [
|
||||
{
|
||||
name: "description",
|
||||
content: computed(
|
||||
() =>
|
||||
collectionData.value?.description ||
|
||||
"Hoppscotch API Documentation - Open source API development ecosystem"
|
||||
),
|
||||
},
|
||||
{
|
||||
property: "og:title",
|
||||
content: computed(
|
||||
() => publishedDoc.value?.title || "Hoppscotch Documentation"
|
||||
),
|
||||
},
|
||||
{
|
||||
property: "og:description",
|
||||
content: computed(
|
||||
() =>
|
||||
collectionData.value?.description ||
|
||||
"Hoppscotch API Documentation - Open source API development ecosystem"
|
||||
),
|
||||
},
|
||||
{
|
||||
property: "og:site_name",
|
||||
content: "Hoppscotch",
|
||||
},
|
||||
{
|
||||
property: "og:image",
|
||||
content: "https://hoppscotch.io/banner.png",
|
||||
},
|
||||
{
|
||||
name: "twitter:card",
|
||||
content: "summary_large_image",
|
||||
},
|
||||
{
|
||||
name: "twitter:site",
|
||||
content: "@hoppscotch_io",
|
||||
},
|
||||
{
|
||||
name: "twitter:creator",
|
||||
content: "@hoppscotch_io",
|
||||
},
|
||||
{
|
||||
name: "twitter:title",
|
||||
content: computed(
|
||||
() => publishedDoc.value?.title || "Hoppscotch Documentation"
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "twitter:description",
|
||||
content: computed(
|
||||
() =>
|
||||
collectionData.value?.description ||
|
||||
"Hoppscotch API Documentation - Open source API development ecosystem"
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "twitter:image",
|
||||
content: "https://hoppscotch.io/banner.png",
|
||||
},
|
||||
],
|
||||
})
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { describe, it, expect, beforeEach } from "vitest"
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { TestContainer } from "dioc/testing"
|
||||
import {
|
||||
HoppCollection,
|
||||
|
|
@ -13,6 +14,15 @@ import {
|
|||
SetCollectionDocumentationOptions,
|
||||
SetRequestDocumentationOptions,
|
||||
} from "../documentation.service"
|
||||
import {
|
||||
getUserPublishedDocs,
|
||||
getTeamPublishedDocs,
|
||||
} from "~/helpers/backend/queries/PublishedDocs"
|
||||
|
||||
vi.mock("~/helpers/backend/queries/PublishedDocs", () => ({
|
||||
getUserPublishedDocs: vi.fn(),
|
||||
getTeamPublishedDocs: vi.fn(),
|
||||
}))
|
||||
|
||||
describe("DocumentationService", () => {
|
||||
let container: TestContainer
|
||||
|
|
@ -451,4 +461,187 @@ describe("DocumentationService", () => {
|
|||
expect(service.hasChanges.value).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Published Documentation", () => {
|
||||
it("should fetch user published docs and update map", async () => {
|
||||
const mockDocs = [
|
||||
{
|
||||
id: "doc-1",
|
||||
collection: { id: "col-1" },
|
||||
title: "Doc 1",
|
||||
version: "v1",
|
||||
autoSync: true,
|
||||
url: "url-1",
|
||||
},
|
||||
]
|
||||
|
||||
vi.mocked(getUserPublishedDocs).mockReturnValue(() =>
|
||||
Promise.resolve(E.right(mockDocs as any))
|
||||
)
|
||||
|
||||
await service.fetchUserPublishedDocs()
|
||||
|
||||
const status = service.getPublishedDocStatus("col-1")
|
||||
expect(status).toEqual({
|
||||
id: "doc-1",
|
||||
title: "Doc 1",
|
||||
version: "v1",
|
||||
autoSync: true,
|
||||
url: "url-1",
|
||||
})
|
||||
})
|
||||
|
||||
it("should fetch team published docs and update map", async () => {
|
||||
const mockDocs = [
|
||||
{
|
||||
id: "doc-2",
|
||||
collection: { id: "col-2" },
|
||||
title: "Doc 2",
|
||||
version: "v2",
|
||||
autoSync: false,
|
||||
url: "url-2",
|
||||
},
|
||||
]
|
||||
|
||||
vi.mocked(getTeamPublishedDocs).mockReturnValue(() =>
|
||||
Promise.resolve(E.right(mockDocs as any))
|
||||
)
|
||||
|
||||
await service.fetchTeamPublishedDocs("team-1")
|
||||
|
||||
const status = service.getPublishedDocStatus("col-2")
|
||||
expect(status).toEqual({
|
||||
id: "doc-2",
|
||||
title: "Doc 2",
|
||||
version: "v2",
|
||||
autoSync: false,
|
||||
url: "url-2",
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle error when fetching user published docs", async () => {
|
||||
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
vi.mocked(getUserPublishedDocs).mockReturnValue(() =>
|
||||
Promise.resolve(E.left("user/not_authenticated"))
|
||||
)
|
||||
|
||||
await service.fetchUserPublishedDocs()
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
"Failed to fetch user published docs:",
|
||||
"user/not_authenticated"
|
||||
)
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("should handle error when fetching team published docs", async () => {
|
||||
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
vi.mocked(getTeamPublishedDocs).mockReturnValue(() =>
|
||||
Promise.resolve(E.left("team/not_required" as any))
|
||||
)
|
||||
|
||||
await service.fetchTeamPublishedDocs("team-1")
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
"Failed to fetch team published docs:",
|
||||
"team/not_required"
|
||||
)
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("should manually set published doc status", () => {
|
||||
const info = {
|
||||
id: "doc-3",
|
||||
title: "Doc 3",
|
||||
version: "v3",
|
||||
autoSync: true,
|
||||
url: "url-3",
|
||||
}
|
||||
|
||||
service.setPublishedDocStatus("col-3", info)
|
||||
|
||||
expect(service.getPublishedDocStatus("col-3")).toEqual(info)
|
||||
})
|
||||
|
||||
it("should remove published doc status", () => {
|
||||
const info = {
|
||||
id: "doc-3",
|
||||
title: "Doc 3",
|
||||
version: "v3",
|
||||
autoSync: true,
|
||||
url: "url-3",
|
||||
}
|
||||
|
||||
service.setPublishedDocStatus("col-3", info)
|
||||
expect(service.getPublishedDocStatus("col-3")).toBeDefined()
|
||||
|
||||
service.setPublishedDocStatus("col-3", null)
|
||||
expect(service.getPublishedDocStatus("col-3")).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should handle race conditions by ignoring stale responses", async () => {
|
||||
const slowDocs = [
|
||||
{
|
||||
id: "doc-slow",
|
||||
collection: { id: "col-1" },
|
||||
title: "Slow Doc",
|
||||
version: "v1",
|
||||
autoSync: true,
|
||||
url: "url-slow",
|
||||
},
|
||||
]
|
||||
|
||||
const fastDocs = [
|
||||
{
|
||||
id: "doc-fast",
|
||||
collection: { id: "col-1" },
|
||||
title: "Fast Doc",
|
||||
version: "v2",
|
||||
autoSync: true,
|
||||
url: "url-fast",
|
||||
},
|
||||
]
|
||||
|
||||
let resolveSlow: (value: any) => void
|
||||
const slowPromise = new Promise((resolve) => {
|
||||
resolveSlow = resolve
|
||||
})
|
||||
|
||||
// Mock the first call to be slow
|
||||
vi.mocked(getUserPublishedDocs)
|
||||
.mockReturnValueOnce(() => slowPromise as any)
|
||||
.mockReturnValueOnce(() => Promise.resolve(E.right(fastDocs as any)))
|
||||
|
||||
// Start the slow request
|
||||
const firstCall = service.fetchUserPublishedDocs()
|
||||
|
||||
// Start the fast request immediately after
|
||||
const secondCall = service.fetchUserPublishedDocs()
|
||||
|
||||
// Wait for the fast request to complete
|
||||
await secondCall
|
||||
|
||||
// Verify the fast response is applied
|
||||
expect(service.getPublishedDocStatus("col-1")).toEqual({
|
||||
id: "doc-fast",
|
||||
title: "Fast Doc",
|
||||
version: "v2",
|
||||
autoSync: true,
|
||||
url: "url-fast",
|
||||
})
|
||||
|
||||
// Now resolve the slow request
|
||||
resolveSlow!(E.right(slowDocs as any))
|
||||
await firstCall
|
||||
|
||||
// Verify the state hasn't changed (slow response ignored)
|
||||
expect(service.getPublishedDocStatus("col-1")).toEqual({
|
||||
id: "doc-fast",
|
||||
title: "Fast Doc",
|
||||
version: "v2",
|
||||
autoSync: true,
|
||||
url: "url-fast",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -37,6 +37,18 @@ vi.mock("../team-collection.service", () => ({
|
|||
},
|
||||
}))
|
||||
|
||||
// Mock DocumentationService
|
||||
vi.mock("../documentation.service", () => ({
|
||||
DocumentationService: class MockDocumentationService {
|
||||
static readonly ID = "DOCUMENTATION_SERVICE"
|
||||
|
||||
fetchTeamPublishedDocs = vi.fn()
|
||||
fetchUserPublishedDocs = vi.fn()
|
||||
|
||||
onServiceInit = vi.fn()
|
||||
},
|
||||
}))
|
||||
|
||||
describe("WorkspaceService", () => {
|
||||
const platformMock = {
|
||||
auth: {
|
||||
|
|
@ -252,13 +264,14 @@ describe("WorkspaceService", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("Team Collection Service Synchronization", () => {
|
||||
it("should call changeTeamID when workspace changes to a team workspace", async () => {
|
||||
describe("Workspace Synchronization", () => {
|
||||
it("should call changeTeamID and fetchTeamPublishedDocs when workspace changes to a team workspace", async () => {
|
||||
const container = new TestContainer()
|
||||
const service = container.bind(WorkspaceService)
|
||||
|
||||
// Access the team collection service mock
|
||||
// Access the mocks
|
||||
const teamCollectionServiceMock = (service as any).teamCollectionService
|
||||
const documentationServiceMock = (service as any).documentationService
|
||||
|
||||
// Change to team workspace
|
||||
service.changeWorkspace({
|
||||
|
|
@ -273,9 +286,12 @@ describe("WorkspaceService", () => {
|
|||
expect(teamCollectionServiceMock.changeTeamID).toHaveBeenCalledWith(
|
||||
"team-123"
|
||||
)
|
||||
expect(
|
||||
documentationServiceMock.fetchTeamPublishedDocs
|
||||
).toHaveBeenCalledWith("team-123")
|
||||
})
|
||||
|
||||
it("should call clearCollections when workspace changes to personal workspace", async () => {
|
||||
it("should call clearCollections and fetchUserPublishedDocs when workspace changes to personal workspace", async () => {
|
||||
const container = new TestContainer()
|
||||
const service = container.bind(WorkspaceService)
|
||||
|
||||
|
|
@ -290,7 +306,10 @@ describe("WorkspaceService", () => {
|
|||
await nextTick()
|
||||
|
||||
const teamCollectionServiceMock = (service as any).teamCollectionService
|
||||
const documentationServiceMock = (service as any).documentationService
|
||||
|
||||
teamCollectionServiceMock.clearCollections.mockClear()
|
||||
documentationServiceMock.fetchUserPublishedDocs.mockClear()
|
||||
|
||||
// Change to personal workspace
|
||||
service.changeWorkspace({
|
||||
|
|
@ -300,13 +319,15 @@ describe("WorkspaceService", () => {
|
|||
await nextTick()
|
||||
|
||||
expect(teamCollectionServiceMock.clearCollections).toHaveBeenCalled()
|
||||
expect(documentationServiceMock.fetchUserPublishedDocs).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should call clearCollections when workspace changes to team workspace without teamID", async () => {
|
||||
it("should call clearCollections and fetchUserPublishedDocs when workspace changes to team workspace without teamID", async () => {
|
||||
const container = new TestContainer()
|
||||
const service = container.bind(WorkspaceService)
|
||||
|
||||
const teamCollectionServiceMock = (service as any).teamCollectionService
|
||||
const documentationServiceMock = (service as any).documentationService
|
||||
|
||||
// Change to team workspace without teamID
|
||||
service.changeWorkspace({
|
||||
|
|
@ -319,6 +340,7 @@ describe("WorkspaceService", () => {
|
|||
await nextTick()
|
||||
|
||||
expect(teamCollectionServiceMock.clearCollections).toHaveBeenCalled()
|
||||
expect(documentationServiceMock.fetchUserPublishedDocs).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should not sync when workspaces are effectively the same", async () => {
|
||||
|
|
@ -336,7 +358,10 @@ describe("WorkspaceService", () => {
|
|||
await nextTick()
|
||||
|
||||
const teamCollectionServiceMock = (service as any).teamCollectionService
|
||||
const documentationServiceMock = (service as any).documentationService
|
||||
|
||||
teamCollectionServiceMock.changeTeamID.mockClear()
|
||||
documentationServiceMock.fetchTeamPublishedDocs.mockClear()
|
||||
|
||||
// Change to same team workspace (different name, same ID)
|
||||
service.changeWorkspace({
|
||||
|
|
@ -348,11 +373,14 @@ describe("WorkspaceService", () => {
|
|||
|
||||
await nextTick()
|
||||
|
||||
// Should not call changeTeamID again since it's the same team
|
||||
// Should not call sync methods again since it's the same team
|
||||
expect(teamCollectionServiceMock.changeTeamID).not.toHaveBeenCalled()
|
||||
expect(
|
||||
documentationServiceMock.fetchTeamPublishedDocs
|
||||
).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle errors during team collection service sync gracefully", async () => {
|
||||
it("should handle errors during synchronization gracefully", async () => {
|
||||
const container = new TestContainer()
|
||||
const service = container.bind(WorkspaceService)
|
||||
|
||||
|
|
@ -376,7 +404,7 @@ describe("WorkspaceService", () => {
|
|||
await nextTick()
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
"Failed to sync team collections:",
|
||||
"Failed to sync team collections and published docs:",
|
||||
expect.any(Error)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,24 @@
|
|||
import { Service } from "dioc"
|
||||
import { reactive, computed } from "vue"
|
||||
import { reactive, computed, ref } from "vue"
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import {
|
||||
getUserPublishedDocs,
|
||||
getTeamPublishedDocs,
|
||||
} from "~/helpers/backend/queries/PublishedDocs"
|
||||
import * as E from "fp-ts/Either"
|
||||
|
||||
// Types for documentation
|
||||
export type DocumentationType = "collection" | "request"
|
||||
|
||||
// Published documentation info
|
||||
export interface PublishedDocInfo {
|
||||
id: string
|
||||
title: string
|
||||
version: string
|
||||
autoSync: boolean
|
||||
url: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Base documentation item with common properties
|
||||
*/
|
||||
|
|
@ -91,6 +105,17 @@ export class DocumentationService extends Service {
|
|||
*/
|
||||
public hasChanges = computed(() => this.editedDocumentation.size > 0)
|
||||
|
||||
/**
|
||||
* Map to store published docs
|
||||
*/
|
||||
private publishedDocsMap = ref<Map<string, PublishedDocInfo>>(new Map())
|
||||
|
||||
/**
|
||||
* Counter to track the latest fetch request ID
|
||||
* This prevents race conditions where a stale request overwrites a newer one
|
||||
*/
|
||||
private fetchRequestId = 0
|
||||
|
||||
/**
|
||||
* Sets collection documentation
|
||||
*/
|
||||
|
|
@ -226,4 +251,109 @@ export class DocumentationService extends Service {
|
|||
public getChangesCount(): number {
|
||||
return this.editedDocumentation.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches user published docs and updates the map
|
||||
*/
|
||||
public async fetchUserPublishedDocs() {
|
||||
// Increment request ID to invalidate any previous pending requests
|
||||
const requestId = ++this.fetchRequestId
|
||||
|
||||
try {
|
||||
const result = await getUserPublishedDocs()()
|
||||
|
||||
// If a newer request has started, ignore this result
|
||||
if (requestId !== this.fetchRequestId) return
|
||||
|
||||
if (E.isRight(result)) {
|
||||
const docs = result.right
|
||||
const newMap = new Map<string, PublishedDocInfo>()
|
||||
docs.forEach((doc) => {
|
||||
if (doc.collection?.id) {
|
||||
newMap.set(doc.collection.id, {
|
||||
id: doc.id,
|
||||
title: doc.title,
|
||||
version: doc.version,
|
||||
autoSync: doc.autoSync,
|
||||
url: doc.url,
|
||||
})
|
||||
}
|
||||
})
|
||||
this.publishedDocsMap.value = newMap
|
||||
} else {
|
||||
console.error("Failed to fetch user published docs:", result.left)
|
||||
}
|
||||
} catch (error) {
|
||||
// If a newer request has started, ignore this error
|
||||
if (requestId !== this.fetchRequestId) return
|
||||
console.error("Failed to fetch user published docs:", error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches published docs for team collections
|
||||
*/
|
||||
public async fetchTeamPublishedDocs(teamID: string) {
|
||||
// Increment request ID to invalidate any previous pending requests
|
||||
const requestId = ++this.fetchRequestId
|
||||
|
||||
try {
|
||||
// Fetch all published docs for the team (collectionID is optional now)
|
||||
const result = await getTeamPublishedDocs(teamID)()
|
||||
|
||||
// If a newer request has started, ignore this result
|
||||
if (requestId !== this.fetchRequestId) return
|
||||
|
||||
if (E.isRight(result)) {
|
||||
const docs = result.right
|
||||
const newMap = new Map<string, PublishedDocInfo>()
|
||||
docs.forEach((doc) => {
|
||||
if (doc.collection?.id) {
|
||||
newMap.set(doc.collection.id, {
|
||||
id: doc.id,
|
||||
title: doc.title,
|
||||
version: doc.version,
|
||||
autoSync: doc.autoSync,
|
||||
url: doc.url,
|
||||
})
|
||||
}
|
||||
})
|
||||
this.publishedDocsMap.value = newMap
|
||||
} else {
|
||||
console.error("Failed to fetch team published docs:", result.left)
|
||||
}
|
||||
} catch (error) {
|
||||
// If a newer request has started, ignore this error
|
||||
if (requestId !== this.fetchRequestId) return
|
||||
console.error("Failed to fetch team published docs:", error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the published status of a collection
|
||||
* @param collectionId The ID of the collection
|
||||
*/
|
||||
public getPublishedDocStatus(
|
||||
collectionId: string
|
||||
): PublishedDocInfo | undefined {
|
||||
return this.publishedDocsMap.value.get(collectionId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually updates the published status of a collection
|
||||
* @param collectionId The ID of the collection
|
||||
* @param info The new info or null to remove
|
||||
*/
|
||||
public setPublishedDocStatus(
|
||||
collectionId: string,
|
||||
info: PublishedDocInfo | null
|
||||
) {
|
||||
const newMap = new Map(this.publishedDocsMap.value)
|
||||
if (info) {
|
||||
newMap.set(collectionId, info)
|
||||
} else {
|
||||
newMap.delete(collectionId)
|
||||
}
|
||||
this.publishedDocsMap.value = newMap
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { platform } from "~/platform"
|
|||
import { min } from "lodash-es"
|
||||
import { TeamAccessRole } from "~/helpers/backend/graphql"
|
||||
import { TeamCollectionsService } from "./team-collection.service"
|
||||
import { DocumentationService } from "./documentation.service"
|
||||
|
||||
/**
|
||||
* Defines a workspace and its information
|
||||
|
|
@ -47,6 +48,7 @@ export class WorkspaceService extends Service<WorkspaceServiceEvent> {
|
|||
private managedTeamListAdapter = new TeamListAdapter(true, false)
|
||||
|
||||
private teamCollectionService = this.bind(TeamCollectionsService)
|
||||
private documentationService = this.bind(DocumentationService)
|
||||
|
||||
private currentUser = useStreamStatic(
|
||||
platform.auth.getCurrentUserStream(),
|
||||
|
|
@ -105,15 +107,15 @@ export class WorkspaceService extends Service<WorkspaceServiceEvent> {
|
|||
{ immediate: true }
|
||||
)
|
||||
|
||||
// Watch for workspace changes and update team collection service accordingly
|
||||
this.setupTeamCollectionServiceSync()
|
||||
// Watch for workspace changes and update team collection service and documentation service accordingly
|
||||
this.setupWorkspaceSync()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up synchronization with team collection service
|
||||
* This ensures team collections are updated when workspace changes
|
||||
* Sets up synchronization with team collection service and documentation service
|
||||
* This ensures team collections and published docs are updated when workspace changes
|
||||
*/
|
||||
private setupTeamCollectionServiceSync() {
|
||||
private setupWorkspaceSync() {
|
||||
watch(
|
||||
this._currentWorkspace,
|
||||
(newWorkspace, oldWorkspace) => {
|
||||
|
|
@ -123,11 +125,18 @@ export class WorkspaceService extends Service<WorkspaceServiceEvent> {
|
|||
try {
|
||||
if (newWorkspace.type === "team" && newWorkspace.teamID) {
|
||||
this.teamCollectionService.changeTeamID(newWorkspace.teamID)
|
||||
this.documentationService.fetchTeamPublishedDocs(
|
||||
newWorkspace.teamID
|
||||
)
|
||||
} else {
|
||||
this.teamCollectionService.clearCollections()
|
||||
this.documentationService.fetchUserPublishedDocs()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to sync team collections:", error)
|
||||
console.error(
|
||||
"Failed to sync team collections and published docs:",
|
||||
error
|
||||
)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
|
|
|
|||
Loading…
Reference in a new issue