Skip to content

Commit 7d7044c

Browse files
committed
Merge branch 'main' into feat/tiptap-editor
2 parents 6992c3b + 7e1ab68 commit 7d7044c

File tree

9 files changed

+66
-116
lines changed

9 files changed

+66
-116
lines changed

src/app/src/components/content/ContentEditorConflict.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { ref, computed, type PropType } from 'vue'
33
import type { ContentConflict, DraftItem } from '../../types'
44
import { useMonacoDiff } from '../../composables/useMonacoDiff'
55
import { useStudio } from '../../composables/useStudio'
6-
import { ContentFileExtension } from '../../types'
7-
import { joinURL } from 'ufo'
6+
import { ContentFileExtension, StudioFeature } from '../../types'
87
98
const props = defineProps({
109
draftItem: {
@@ -19,7 +18,7 @@ const diffEditorRef = ref<HTMLDivElement>()
1918
2019
const conflict = computed<ContentConflict>(() => props.draftItem.conflict!)
2120
const repositoryInfo = computed(() => gitProvider.api.getRepositoryInfo())
22-
const fileRemoteUrl = computed(() => joinURL(gitProvider.api.getContentRootDirUrl(), props.draftItem.fsPath))
21+
const fileRemoteUrl = computed(() => gitProvider.api.getFileUrl(StudioFeature.Content, props.draftItem.fsPath))
2322
2423
const language = computed(() => {
2524
switch (props.draftItem.fsPath.split('.').pop()) {

src/app/src/components/shared/item/ItemActionsDropdown.vue

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ const props = defineProps({
1515
type: Object as PropType<TreeItem>,
1616
required: true,
1717
},
18+
extraActions: {
19+
type: Array as PropType<DropdownMenuItem[]>,
20+
default: () => [],
21+
},
1822
})
1923
2024
const isOpen = ref(false)
@@ -44,11 +48,11 @@ const getPendingActionLabel = (action: StudioAction<StudioItemActionId> | null)
4448
return t('studio.actions.confirmAction', { action: t(`studio.actions.verbs.${verb}`, verb) })
4549
}
4650
47-
const actions = computed<DropdownMenuItem[]>(() => {
51+
const actions = computed<DropdownMenuItem[][]>(() => {
4852
const hasPendingAction = pendingAction.value !== null
4953
const hasLoadingAction = loadingAction.value !== null
5054
51-
return computeItemActions(context.itemActions.value, props.item, context.currentFeature.value).map((action) => {
55+
const itemActions = computeItemActions(context.itemActions.value, props.item, context.currentFeature.value).map((action) => {
5256
const isOneStepAction = oneStepActions.includes(action.id)
5357
const isPending = pendingAction.value?.id === action.id
5458
const isLoading = loadingAction.value?.id === action.id
@@ -79,11 +83,16 @@ const actions = computed<DropdownMenuItem[]>(() => {
7983
8084
// For two-step actions, execute it without confirmation
8185
if (!isOneStepAction) {
86+
// Navigate into folder before adding form creation
8287
if (props.item.type === 'directory' && [StudioItemActionId.CreateDocument, StudioItemActionId.CreateDocumentFolder, StudioItemActionId.CreateMediaFolder].includes(action.id)) {
83-
// Navigate into folder before adding form creation
8488
context.activeTree.value.selectItemByFsPath(props.item.fsPath)
8589
}
8690
91+
// Navigate to parent folder if needed before renaming
92+
if (action.id === StudioItemActionId.RenameItem && context.activeTree.value.currentItem.value.fsPath === props.item.fsPath) {
93+
context.activeTree.value.selectParentByFsPath(props.item.fsPath)
94+
}
95+
8796
action.handler!(props.item)
8897
return
8998
}
@@ -107,7 +116,15 @@ const actions = computed<DropdownMenuItem[]>(() => {
107116
}
108117
},
109118
}
110-
})
119+
}) as DropdownMenuItem[]
120+
121+
const groups: DropdownMenuItem[][] = [itemActions]
122+
123+
if (props.extraActions.length > 0) {
124+
groups.push(props.extraActions)
125+
}
126+
127+
return groups
111128
})
112129
113130
const pendingActionLabel = computed(() => getPendingActionLabel(pendingAction.value))

src/app/src/components/shared/item/ItemActionsToolbar.vue

Lines changed: 24 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<script setup lang="ts">
22
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
3-
import { computeItemActions, oneStepActions } from '../../../utils/context'
43
import { useStudio } from '../../../composables/useStudio'
54
import type { StudioAction } from '../../../types'
6-
import { StudioItemActionId } from '../../../types'
5+
import { StudioItemActionId, TreeStatus } from '../../../types'
76
import { MEDIA_EXTENSIONS } from '../../../utils/file'
7+
import type { DropdownMenuItem } from '@nuxt/ui/runtime/components/DropdownMenu.vue.d.ts'
88
import { useI18n } from 'vue-i18n'
99
10-
const { context } = useStudio()
10+
const { context, gitProvider } = useStudio()
1111
const { t } = useI18n()
1212
const fileInputRef = ref<HTMLInputElement>()
1313
const toolbarRef = ref<HTMLElement>()
@@ -23,44 +23,25 @@ watch(context.actionInProgress, (action) => {
2323
2424
const item = computed(() => context.activeTree.value.currentItem.value)
2525
26-
const getActionTooltip = (action: StudioAction<StudioItemActionId>, isPending: boolean) => {
27-
if (isPending) {
28-
const verb = action.id.split('-')[0]
29-
return t('studio.actions.confirmAction', { action: t(`studio.actions.verbs.${verb}`, verb) })
30-
}
31-
return action.tooltip ? t(action.tooltip) : t(action.label)
32-
}
33-
34-
const actions = computed(() => {
35-
const hasPendingAction = pendingAction.value !== null
36-
const hasLoadingAction = loadingAction.value !== null
37-
38-
return computeItemActions(context.itemActions.value, item.value, context.currentFeature.value).map((action) => {
39-
const isOneStepAction = oneStepActions.includes(action.id)
40-
const isPending = pendingAction.value?.id === action.id
41-
const isLoading = loadingAction.value?.id === action.id
42-
const isDeleteAction = action.id === StudioItemActionId.DeleteItem
43-
44-
let icon = action.icon
45-
if (isLoading) {
46-
icon = 'i-ph-circle-notch'
47-
}
48-
else if (isPending) {
49-
icon = isDeleteAction ? 'i-ph-x' : 'i-ph-check'
26+
const extraActions = computed<DropdownMenuItem[]>(() => {
27+
const actions: DropdownMenuItem[] = []
28+
29+
if (item.value.type === 'file' && item.value.status !== TreeStatus.Created) {
30+
const providerInfo = gitProvider.api.getRepositoryInfo()
31+
const provider = providerInfo.provider
32+
const feature = context.currentFeature.value
33+
34+
if (feature) {
35+
actions.push({
36+
label: t(`studio.actions.labels.openGitProvider`, { providerName: gitProvider.name }),
37+
icon: provider === 'gitlab' ? 'i-simple-icons:gitlab' : 'i-simple-icons:github',
38+
to: gitProvider.api.getFileUrl(feature, item.value.fsPath),
39+
target: '_blank',
40+
})
5041
}
42+
}
5143
52-
return {
53-
...action,
54-
color: isPending ? (isDeleteAction ? 'error' : 'secondary') : 'neutral',
55-
variant: isPending ? 'soft' : 'ghost',
56-
icon,
57-
tooltip: getActionTooltip(action, isPending),
58-
disabled: (hasPendingAction && !isPending) || hasLoadingAction,
59-
isOneStepAction,
60-
isPending,
61-
isLoading,
62-
}
63-
})
44+
return actions
6445
})
6546
6647
const handleFileSelection = (event: Event) => {
@@ -75,49 +56,6 @@ const handleFileSelection = (event: Event) => {
7556
}
7657
}
7758
78-
const actionHandler = (action: StudioAction<StudioItemActionId> & { isPending?: boolean, isOneStepAction?: boolean, isLoading?: boolean }, event: Event) => {
79-
// Stop propagation to prevent click outside handler from triggering
80-
event.stopPropagation()
81-
82-
// Don't allow action if already loading
83-
if (action.isLoading) {
84-
return
85-
}
86-
87-
if (action.id === StudioItemActionId.UploadMedia) {
88-
fileInputRef.value?.click()
89-
return
90-
}
91-
92-
const targetItem = item.value
93-
94-
// For two-steps actions, execute without confirmation
95-
if (!action.isOneStepAction) {
96-
if (action.id === StudioItemActionId.RenameItem) {
97-
// Navigate to parent since rename form is displayed in the parent tree
98-
context.activeTree.value.selectParentByFsPath(targetItem.fsPath)
99-
}
100-
101-
action.handler!(targetItem)
102-
return
103-
}
104-
105-
// Second click on pending action - execute it
106-
if (action.isPending) {
107-
loadingAction.value = action
108-
action.handler!(targetItem)
109-
pendingAction.value = null
110-
}
111-
// Click on different action while one is pending - cancel pending state
112-
else if (pendingAction.value !== null) {
113-
pendingAction.value = null
114-
}
115-
// First click - enter pending state
116-
else {
117-
pendingAction.value = action
118-
}
119-
}
120-
12159
const handleClickOutside = () => {
12260
if (pendingAction.value !== null && loadingAction.value === null) {
12361
pendingAction.value = null
@@ -138,23 +76,10 @@ onUnmounted(() => {
13876
ref="toolbarRef"
13977
class="flex items-center -mr-1"
14078
>
141-
<UTooltip
142-
v-for="action in actions"
143-
:key="action.id"
144-
:text="action.tooltip"
145-
:open="action.isPending ? true : undefined"
146-
>
147-
<UButton
148-
:key="action.id"
149-
:icon="action.icon"
150-
:disabled="action.disabled"
151-
size="sm"
152-
:color="action.color as never"
153-
:variant="action.variant as never"
154-
:loading="action.isLoading"
155-
@click="actionHandler(action, $event)"
156-
/>
157-
</UTooltip>
79+
<ItemActionsDropdown
80+
:item="item"
81+
:extra-actions="extraActions"
82+
/>
15883

15984
<input
16085
ref="fileInputRef"

src/app/src/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@
127127
"uploadMedia": "Upload media",
128128
"duplicateItem": "Duplicate",
129129
"revertItem": "Revert changes",
130-
"publishBranch": "Publish branch"
130+
"publishBranch": "Publish branch",
131+
"openGitProvider": "Open on {providerName}"
131132
},
132133
"tooltips": {
133134
"createDocument": "Create a new file",

src/app/src/locales/fr.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@
127127
"uploadMedia": "Téléverser un média",
128128
"duplicateItem": "Dupliquer",
129129
"revertItem": "Annuler les changements",
130-
"publishBranch": "Publier la branche"
130+
"publishBranch": "Publier la branche",
131+
"openGitProvider": "Ouvrir sur {providerName}"
131132
},
132133
"tooltips": {
133134
"createDocument": "Créer un nouveau fichier",

src/app/src/types/git.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { DraftStatus } from './draft'
2+
import type { StudioFeature } from '../types'
23

34
export type GitProviderType = 'github' | 'gitlab'
45

@@ -48,7 +49,7 @@ export interface GitProviderAPI {
4849
getRepositoryUrl(): string
4950
getBranchUrl(): string
5051
getCommitUrl(sha: string): string
51-
getContentRootDirUrl(): string
52+
getFileUrl(feature: StudioFeature, fsPath: string): string
5253
getRepositoryInfo(): { owner: string, repo: string, branch: string, provider: GitProviderType | null }
5354
}
5455

src/app/src/utils/providers/github.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ofetch } from 'ofetch'
22
import { joinURL } from 'ufo'
33
import type { GitOptions, GitProviderAPI, GitFile, RawFile, CommitResult, CommitFilesOptions } from '../../types'
4+
import { StudioFeature } from '../../types'
45
import { DraftStatus } from '../../types/draft'
56

67
export function createGitHubProvider(options: GitOptions): GitProviderAPI {
@@ -165,8 +166,10 @@ export function createGitHubProvider(options: GitOptions): GitProviderAPI {
165166
return `https://github.com/${owner}/${repo}/commit/${sha}`
166167
}
167168

168-
function getContentRootDirUrl() {
169-
return `https://github.com/${owner}/${repo}/tree/${branch}/${rootDir}/content`
169+
function getFileUrl(feature: StudioFeature, fsPath: string) {
170+
const featureDir = feature === StudioFeature.Content ? 'content' : 'public'
171+
const fullPath = joinURL(rootDir, featureDir, fsPath)
172+
return `https://github.com/${owner}/${repo}/blob/${branch}/${fullPath}`
170173
}
171174

172175
function getRepositoryInfo() {
@@ -184,7 +187,7 @@ export function createGitHubProvider(options: GitOptions): GitProviderAPI {
184187
getRepositoryUrl,
185188
getBranchUrl,
186189
getCommitUrl,
187-
getContentRootDirUrl,
190+
getFileUrl,
188191
getRepositoryInfo,
189192
}
190193
}

src/app/src/utils/providers/gitlab.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ofetch } from 'ofetch'
22
import { joinURL } from 'ufo'
33
import type { GitOptions, GitProviderAPI, GitFile, RawFile, CommitResult, CommitFilesOptions } from '../../types'
44
import { DraftStatus } from '../../types/draft'
5+
import { StudioFeature } from '../../types'
56

67
export function createGitLabProvider(options: GitOptions): GitProviderAPI {
78
const { owner, repo, token, branch, rootDir, authorName, authorEmail, instanceUrl = 'https://gitlab.com' } = options
@@ -143,8 +144,10 @@ export function createGitLabProvider(options: GitOptions): GitProviderAPI {
143144
return `${instanceUrl}/${owner}/${repo}/-/commit/${sha}`
144145
}
145146

146-
function getContentRootDirUrl() {
147-
return `${instanceUrl}/${owner}/${repo}/-/tree/${branch}/${rootDir}/content`
147+
function getFileUrl(feature: StudioFeature, fsPath: string) {
148+
const featureDir = feature === StudioFeature.Content ? 'content' : 'public'
149+
const fullPath = joinURL(rootDir, featureDir, fsPath)
150+
return `${instanceUrl}/${owner}/${repo}/-/blob/${branch}/${fullPath}`
148151
}
149152

150153
function getRepositoryInfo() {
@@ -162,7 +165,7 @@ export function createGitLabProvider(options: GitOptions): GitProviderAPI {
162165
getRepositoryUrl,
163166
getBranchUrl,
164167
getCommitUrl,
165-
getContentRootDirUrl,
168+
getFileUrl,
166169
getRepositoryInfo,
167170
}
168171
}

src/app/src/utils/providers/null.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export function createNullProvider(_options: GitOptions): GitProviderAPI {
1111
getRepositoryUrl: () => '',
1212
getBranchUrl: () => '',
1313
getCommitUrl: () => '',
14-
getContentRootDirUrl: () => '',
14+
getFileUrl: (_feature, _fsPath) => '',
1515
getRepositoryInfo: () => ({ owner: '', repo: '', branch: '', provider: null }),
1616
}
1717
}

0 commit comments

Comments
 (0)