mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[gitlab] IJPL-82619: "/uploads/*" links are rendered correctly and targets correct gitlab-based urls
* works for comments in the editor, MR timeline notes, MR title, and MR diff comments, and also for title/details of commits in Gitlab toolwindow * doesn't work for the titles in the list of MRs in Gitlab toolwindow GitOrigin-RevId: 7a19d6520edb6f13077fa32f9d5ce7ae8c545834
This commit is contained in:
committed by
intellij-monorepo-bot
parent
526ad7da93
commit
fdd225e48e
@@ -1,4 +1,5 @@
|
||||
fragment project on Project {
|
||||
id
|
||||
name
|
||||
nameWithNamespace
|
||||
path
|
||||
|
||||
+6
@@ -1,15 +1,20 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.plugins.gitlab.api.dto
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.intellij.collaboration.api.dto.GraphQLFragment
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.plugins.gitlab.api.GitLabGid
|
||||
import org.jetbrains.plugins.gitlab.api.GitLabGidData
|
||||
import org.jetbrains.plugins.gitlab.api.SinceGitLab
|
||||
|
||||
// namespace can be null for some reason, so we need to parse paths manually
|
||||
@SinceGitLab("12.0")
|
||||
@GraphQLFragment("/graphql/fragment/project.graphql")
|
||||
data class GitLabProjectDTO(
|
||||
@JsonProperty("id") private val _id: String,
|
||||
val name: @Nls String,
|
||||
val nameWithNamespace: @Nls String,
|
||||
val path: @NlsSafe String,
|
||||
@@ -24,6 +29,7 @@ data class GitLabProjectDTO(
|
||||
@SinceGitLab("16.8") val allowsMultipleMergeRequestReviewers: Boolean?,
|
||||
val repository: Repository?
|
||||
) {
|
||||
@JsonIgnore val id: GitLabGid = GitLabGidData(_id)
|
||||
val ownerPath: @NlsSafe String = fullPath.split("/").dropLast(1).joinToString("/")
|
||||
|
||||
data class Repository(
|
||||
|
||||
+10
@@ -20,9 +20,11 @@ import org.jetbrains.plugins.gitlab.api.request.*
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.api.dto.GitLabMergeRequestDTO
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequest
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.api.request.mergeRequestSetReviewers
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.GitLabContextDataLoader
|
||||
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
|
||||
import org.jetbrains.plugins.gitlab.util.GitLabRegistry
|
||||
|
||||
|
||||
private val LOG = logger<GitLabProject>()
|
||||
|
||||
@CodeReviewDomainEntity
|
||||
@@ -38,6 +40,7 @@ interface GitLabProject {
|
||||
fun getMembersBatches(): Flow<List<GitLabUserDTO>>
|
||||
|
||||
val defaultBranch: String?
|
||||
val gitLabProjectId: GitLabId
|
||||
suspend fun isMultipleReviewersAllowed(): Boolean
|
||||
|
||||
/**
|
||||
@@ -50,6 +53,8 @@ interface GitLabProject {
|
||||
suspend fun adjustReviewers(mrIid: String, reviewers: List<GitLabUserDTO>): GitLabMergeRequestDTO
|
||||
|
||||
fun reloadData()
|
||||
|
||||
val contextDataLoader: GitLabContextDataLoader
|
||||
}
|
||||
|
||||
@CodeReviewDomainEntity
|
||||
@@ -77,6 +82,11 @@ class GitLabLazyProject(
|
||||
private val multipleReviewersAllowedRequest = cs.async(Dispatchers.IO, start = CoroutineStart.LAZY) {
|
||||
loadMultipleReviewersAllowed(initialData)
|
||||
}
|
||||
override val gitLabProjectId: GitLabId = initialData.id
|
||||
|
||||
val uploadFileUrlBase: String = projectMapping.repository.serverPath.toString() + "/-/project/" + gitLabProjectId.guessRestId() + "/uploads/"
|
||||
|
||||
override val contextDataLoader: GitLabContextDataLoader = GitLabContextDataLoader(uploadFileUrlBase)
|
||||
|
||||
override val mergeRequests by lazy {
|
||||
CachingGitLabProjectMergeRequestsStore(project, cs, api, glMetadata, projectMapping, currentUser, tokenRefreshFlow)
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.plugins.gitlab.mergerequest.ui
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* Class which handles external files loading
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
class GitLabContextDataLoader(
|
||||
val uploadFileUrlBase: String
|
||||
)
|
||||
+6
-3
@@ -5,13 +5,16 @@ import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabCommit
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequest
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.GitLabContextDataLoader
|
||||
import org.jetbrains.plugins.gitlab.ui.GitLabUIUtil
|
||||
|
||||
|
||||
@ApiStatus.Internal
|
||||
class GitLabCommitViewModel(
|
||||
private val project: Project,
|
||||
private val mr: GitLabMergeRequest,
|
||||
model: GitLabCommit
|
||||
model: GitLabCommit,
|
||||
private val contextDataLoader: GitLabContextDataLoader
|
||||
) {
|
||||
internal val sha = model.sha
|
||||
internal val shortId = model.shortId
|
||||
@@ -20,9 +23,9 @@ class GitLabCommitViewModel(
|
||||
internal val authoredDate = model.authoredDate
|
||||
|
||||
internal val titleHtml = model.fullTitle?.let {
|
||||
GitLabUIUtil.convertToHtml(project, mr.gitRepository, mr.glProject.projectPath, it)
|
||||
GitLabUIUtil.convertToHtml(project, mr.gitRepository, mr.glProject.projectPath, it, contextDataLoader.uploadFileUrlBase)
|
||||
}
|
||||
internal val descriptionHtml = model.description?.removePrefix(model.fullTitle.orEmpty())?.let {
|
||||
GitLabUIUtil.convertToHtml(project, mr.gitRepository, mr.glProject.projectPath, it)
|
||||
GitLabUIUtil.convertToHtml(project, mr.gitRepository, mr.glProject.projectPath, it, contextDataLoader.uploadFileUrlBase)
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -18,6 +18,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequest
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.GitLabContextDataLoader
|
||||
|
||||
@ApiStatus.Internal
|
||||
interface GitLabMergeRequestChangesViewModel : CodeReviewChangesViewModel<GitLabCommitViewModel> {
|
||||
@@ -34,7 +35,8 @@ private val LOG = logger<GitLabMergeRequestChangesViewModel>()
|
||||
internal class GitLabMergeRequestChangesViewModelImpl(
|
||||
private val project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
mergeRequest: GitLabMergeRequest
|
||||
mergeRequest: GitLabMergeRequest,
|
||||
private val contextDataLoader: GitLabContextDataLoader,
|
||||
) : GitLabMergeRequestChangesViewModel,
|
||||
CodeReviewChangesViewModel<GitLabCommitViewModel> {
|
||||
private val cs = parentCs.childScope(javaClass.name)
|
||||
@@ -54,7 +56,7 @@ internal class GitLabMergeRequestChangesViewModelImpl(
|
||||
|
||||
override val reviewCommits: SharedFlow<List<GitLabCommitViewModel>> =
|
||||
mergeRequest.changes
|
||||
.map { it.getCommits().map { commit -> GitLabCommitViewModel(project, mergeRequest, commit) } }
|
||||
.map { it.getCommits().map { commit -> GitLabCommitViewModel(project, mergeRequest, commit, contextDataLoader) } }
|
||||
.modelFlow(cs, LOG)
|
||||
|
||||
override val selectedCommitIndex: SharedFlow<Int> = reviewCommits.combine(delegate.selectedCommit) { commits, sha ->
|
||||
|
||||
+5
-3
@@ -56,13 +56,15 @@ internal class GitLabMergeRequestDetailsViewModelImpl(
|
||||
override val author: GitLabUserDTO = mergeRequest.author
|
||||
|
||||
override val title: SharedFlow<String> = mergeRequest.details.map { it.title }.map { title ->
|
||||
GitLabUIUtil.convertToHtml(project, mergeRequest.gitRepository, mergeRequest.glProject.projectPath, title)
|
||||
GitLabUIUtil.convertToHtml(project, mergeRequest.gitRepository, mergeRequest.glProject.projectPath, title,
|
||||
projectData.contextDataLoader.uploadFileUrlBase)
|
||||
}.modelFlow(cs, LOG)
|
||||
override val description: SharedFlow<String> = mergeRequest.details.map { it.description }.map { description ->
|
||||
processIssueIdsHtml(project, description)
|
||||
}.modelFlow(cs, LOG)
|
||||
override val descriptionHtml: SharedFlow<String> = mergeRequest.details.map { it.description }.map {
|
||||
if (it.isNotBlank()) GitLabUIUtil.convertToHtml(project, mergeRequest.gitRepository, mergeRequest.glProject.projectPath, it) else it
|
||||
if (it.isNotBlank()) GitLabUIUtil.convertToHtml(project, mergeRequest.gitRepository, mergeRequest.glProject.projectPath, it,
|
||||
projectData.contextDataLoader.uploadFileUrlBase) else it
|
||||
}.modelFlow(cs, LOG)
|
||||
override val reviewRequestState: SharedFlow<ReviewRequestState> = mergeRequest.details.map { it.reviewState }
|
||||
.modelFlow(cs, LOG)
|
||||
@@ -84,7 +86,7 @@ internal class GitLabMergeRequestDetailsViewModelImpl(
|
||||
override val branchesVm = GitLabMergeRequestBranchesViewModel(cs, mergeRequest, projectData.projectMapping)
|
||||
override val statusVm = GitLabMergeRequestStatusViewModelImpl(project, cs, projectData.projectMapping.gitRepository,
|
||||
projectData.projectMapping.repository.serverPath, mergeRequest)
|
||||
override val changesVm = GitLabMergeRequestChangesViewModelImpl(project, cs, mergeRequest)
|
||||
override val changesVm = GitLabMergeRequestChangesViewModelImpl(project, cs, mergeRequest, projectData.contextDataLoader)
|
||||
|
||||
override fun reloadData() {
|
||||
cs.launchNow {
|
||||
|
||||
+2
-1
@@ -88,7 +88,8 @@ internal class GitLabMergeRequestDiscussionsViewModelsImpl(
|
||||
.map { ComputedResult.fromResult(it) }
|
||||
.transformConsecutiveSuccesses {
|
||||
mapFiltered { it.discussionId == null }
|
||||
.mapStatefulToStateful { GitLabMergeRequestStandaloneDraftNoteViewModelBase(project, this, it, mergeRequest) }
|
||||
.mapStatefulToStateful { GitLabMergeRequestStandaloneDraftNoteViewModelBase(project, this, it, mergeRequest,
|
||||
projectData.contextDataLoader) }
|
||||
}
|
||||
.stateInNow(cs, ComputedResult.loading())
|
||||
|
||||
|
||||
+28
-18
@@ -26,14 +26,15 @@ sealed interface GitLabMergeRequestTimelineItemViewModel {
|
||||
sealed class Immutable(
|
||||
private val project: Project,
|
||||
private val mr: GitLabMergeRequest,
|
||||
private val model: GitLabMergeRequestTimelineItem.Immutable
|
||||
private val model: GitLabMergeRequestTimelineItem.Immutable,
|
||||
private val uploadFileUrlBase: String,
|
||||
) : GitLabMergeRequestTimelineItemViewModel {
|
||||
val date = model.date
|
||||
val actor = model.actor
|
||||
|
||||
val contentHtml: String? by lazy {
|
||||
(model as? GitLabMergeRequestTimelineItem.SystemNote)?.content?.let {
|
||||
GitLabUIUtil.convertToHtml(project, mr.gitRepository, mr.glProject.projectPath, it)
|
||||
GitLabUIUtil.convertToHtml(project, mr.gitRepository, mr.glProject.projectPath, it, uploadFileUrlBase)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,33 +42,41 @@ sealed interface GitLabMergeRequestTimelineItemViewModel {
|
||||
fun fromModel(
|
||||
project: Project,
|
||||
mr: GitLabMergeRequest,
|
||||
model: GitLabMergeRequestTimelineItem.Immutable
|
||||
): Immutable = when (model) {
|
||||
is GitLabMergeRequestTimelineItem.StateEvent -> StateEvent(project, mr, model)
|
||||
is GitLabMergeRequestTimelineItem.LabelEvent -> LabelEvent(project, mr, model)
|
||||
is GitLabMergeRequestTimelineItem.MilestoneEvent -> MilestoneEvent(project, mr, model)
|
||||
is GitLabMergeRequestTimelineItem.SystemNote -> SystemNote(project, mr, model)
|
||||
model: GitLabMergeRequestTimelineItem.Immutable,
|
||||
uploadFileUrlBase: String,
|
||||
): Immutable {
|
||||
|
||||
return when (model) {
|
||||
is GitLabMergeRequestTimelineItem.StateEvent -> StateEvent(project, mr, model, uploadFileUrlBase)
|
||||
is GitLabMergeRequestTimelineItem.LabelEvent -> LabelEvent(project, mr, model, uploadFileUrlBase)
|
||||
is GitLabMergeRequestTimelineItem.MilestoneEvent -> MilestoneEvent(project, mr, model, uploadFileUrlBase)
|
||||
is GitLabMergeRequestTimelineItem.SystemNote -> SystemNote(project, mr, model, uploadFileUrlBase)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StateEvent(project: Project, mr: GitLabMergeRequest, model: GitLabMergeRequestTimelineItem.StateEvent)
|
||||
: Immutable(project, mr, model) {
|
||||
class StateEvent(project: Project, mr: GitLabMergeRequest, model: GitLabMergeRequestTimelineItem.StateEvent,
|
||||
uploadFileUrlBase: String)
|
||||
: Immutable(project, mr, model, uploadFileUrlBase) {
|
||||
val event = model.event
|
||||
}
|
||||
|
||||
class LabelEvent(project: Project, mr: GitLabMergeRequest, model: GitLabMergeRequestTimelineItem.LabelEvent)
|
||||
: Immutable(project, mr, model) {
|
||||
class LabelEvent(project: Project, mr: GitLabMergeRequest, model: GitLabMergeRequestTimelineItem.LabelEvent,
|
||||
uploadFileUrlBase: String)
|
||||
: Immutable(project, mr, model, uploadFileUrlBase) {
|
||||
val event = model.event
|
||||
}
|
||||
|
||||
class MilestoneEvent(project: Project, mr: GitLabMergeRequest, model: GitLabMergeRequestTimelineItem.MilestoneEvent)
|
||||
: Immutable(project, mr, model) {
|
||||
class MilestoneEvent(project: Project, mr: GitLabMergeRequest, model: GitLabMergeRequestTimelineItem.MilestoneEvent,
|
||||
uploadFileUrlBase: String)
|
||||
: Immutable(project, mr, model, uploadFileUrlBase) {
|
||||
val event = model.event
|
||||
}
|
||||
|
||||
class SystemNote(project: Project, mr: GitLabMergeRequest, model: GitLabMergeRequestTimelineItem.SystemNote)
|
||||
: Immutable(project, mr, model) {
|
||||
class SystemNote(project: Project, mr: GitLabMergeRequest, model: GitLabMergeRequestTimelineItem.SystemNote,
|
||||
uploadFileUrlBase: String)
|
||||
: Immutable(project, mr, model, uploadFileUrlBase) {
|
||||
val content = model.content
|
||||
}
|
||||
|
||||
@@ -97,7 +106,8 @@ sealed interface GitLabMergeRequestTimelineItemViewModel {
|
||||
project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
mr: GitLabMergeRequest,
|
||||
note: GitLabMergeRequestNote
|
||||
note: GitLabMergeRequestNote,
|
||||
projectData: GitLabProject
|
||||
) : GitLabMergeRequestTimelineItemViewModel, GitLabNoteViewModel {
|
||||
private val cs = parentCs.childScope(this::class)
|
||||
|
||||
@@ -113,7 +123,7 @@ sealed interface GitLabMergeRequestTimelineItemViewModel {
|
||||
|
||||
override val body: StateFlow<String> = note.body
|
||||
override val bodyHtml: StateFlow<String> = body.mapStateInNow(cs) {
|
||||
GitLabUIUtil.convertToHtml(project, mr.gitRepository, mr.glProject.projectPath, it)
|
||||
GitLabUIUtil.convertToHtml(project, mr.gitRepository, mr.glProject.projectPath, it, projectData.contextDataLoader.uploadFileUrlBase)
|
||||
}
|
||||
|
||||
override val discussionState: StateFlow<GitLabDiscussionStateContainer> =
|
||||
|
||||
+8
-6
@@ -47,10 +47,12 @@ internal class LoadAllGitLabMergeRequestTimelineViewModel(
|
||||
override val number: String = "!${mergeRequest.iid}"
|
||||
override val author: GitLabUserDTO = mergeRequest.author
|
||||
override val title: SharedFlow<String> = mergeRequest.details.map { it.title }.map { title ->
|
||||
GitLabUIUtil.convertToHtml(project, mergeRequest.gitRepository, mergeRequest.glProject.projectPath, title)
|
||||
GitLabUIUtil.convertToHtml(project, mergeRequest.gitRepository, mergeRequest.glProject.projectPath, title,
|
||||
projectData.contextDataLoader.uploadFileUrlBase)
|
||||
}.modelFlow(cs, LOG)
|
||||
override val descriptionHtml: SharedFlow<String> = mergeRequest.details.map { it.description }.map { description ->
|
||||
GitLabUIUtil.convertToHtml(project, mergeRequest.gitRepository, mergeRequest.glProject.projectPath, description)
|
||||
GitLabUIUtil.convertToHtml(project, mergeRequest.gitRepository, mergeRequest.glProject.projectPath, description,
|
||||
projectData.contextDataLoader.uploadFileUrlBase)
|
||||
}.modelFlow(cs, LOG)
|
||||
override val url: String = mergeRequest.url
|
||||
|
||||
@@ -60,7 +62,7 @@ internal class LoadAllGitLabMergeRequestTimelineViewModel(
|
||||
override val timelineItems: SharedFlow<Result<List<GitLabMergeRequestTimelineItemViewModel>>> =
|
||||
mergeRequest.createTimelineItemsFlow(showEvents)
|
||||
.transformConsecutiveSuccesses {
|
||||
mapDataToModel({ it.id }, { createItemVm(it) }, {})
|
||||
mapDataToModel({ it.id }, { createItemVm(it, projectData.contextDataLoader.uploadFileUrlBase) }, {})
|
||||
}
|
||||
.modelFlow(cs, LOG)
|
||||
|
||||
@@ -142,17 +144,17 @@ internal class LoadAllGitLabMergeRequestTimelineViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.createItemVm(item: GitLabMergeRequestTimelineItem)
|
||||
private fun CoroutineScope.createItemVm(item: GitLabMergeRequestTimelineItem, basePath: String)
|
||||
: GitLabMergeRequestTimelineItemViewModel =
|
||||
when (item) {
|
||||
is GitLabMergeRequestTimelineItem.Immutable ->
|
||||
GitLabMergeRequestTimelineItemViewModel.Immutable.fromModel(project, mergeRequest, item)
|
||||
GitLabMergeRequestTimelineItemViewModel.Immutable.fromModel(project, mergeRequest, item, basePath)
|
||||
is GitLabMergeRequestTimelineItem.UserDiscussion ->
|
||||
GitLabMergeRequestTimelineItemViewModel.Discussion(project, cs, projectData, currentUser, mergeRequest, item.discussion).also {
|
||||
handleDiffRequests(it.diffVm, _diffRequests::emit)
|
||||
}
|
||||
is GitLabMergeRequestTimelineItem.DraftNote ->
|
||||
GitLabMergeRequestTimelineItemViewModel.DraftNote(project, cs, mergeRequest, item.note).also {
|
||||
GitLabMergeRequestTimelineItemViewModel.DraftNote(project, cs, mergeRequest, item.note, projectData).also {
|
||||
handleDiffRequests(it.diffVm, _diffRequests::emit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ import java.net.URI
|
||||
|
||||
private val MARKDOWN_IMAGE_SETTINGS: IElementType = MarkdownElementType("MARKDOWN_IMAGE_SETTINGS")
|
||||
|
||||
private const val UPLOADS_PATH = "/uploads/"
|
||||
|
||||
object GitLabUIUtil {
|
||||
const val OPEN_FILE_LINK_PREFIX = "glfilelink:"
|
||||
const val OPEN_MR_LINK_PREFIX = "glmergerequest:"
|
||||
@@ -64,12 +66,14 @@ object GitLabUIUtil {
|
||||
project: Project,
|
||||
gitRepository: GitRepository,
|
||||
projectPath: GitLabProjectPath,
|
||||
markdownSource: @NonNls String
|
||||
markdownSource: @NonNls String,
|
||||
uploadFileUrlBase: String
|
||||
): @NlsSafe String {
|
||||
if (markdownSource.isBlank()) return markdownSource
|
||||
// TODO: fix bug with CRLF line endings from markdown library
|
||||
val text = preprocessMergeRequestIds(processIssueIdsMarkdown(project, markdownSource)).replace("\r", "")
|
||||
val flavourDescriptor = GitLabFlavourDescriptor(gitRepository, projectPath, CodeBlockHtmlSyntaxHighlighter(project))
|
||||
val flavourDescriptor = GitLabFlavourDescriptor(gitRepository, projectPath, CodeBlockHtmlSyntaxHighlighter(project),
|
||||
uploadFileUrlBase)
|
||||
|
||||
return MarkdownToHtmlConverter(flavourDescriptor).convertMarkdownToHtml(text, null)
|
||||
}
|
||||
@@ -80,7 +84,8 @@ object GitLabUIUtil {
|
||||
private class GitLabFlavourDescriptor(
|
||||
private val gitRepository: GitRepository,
|
||||
private val projectPath: GitLabProjectPath,
|
||||
private val htmlSyntaxHighlighter: HtmlSyntaxHighlighter
|
||||
private val htmlSyntaxHighlighter: HtmlSyntaxHighlighter,
|
||||
private val uploadFileUrlBase: String,
|
||||
) : GFMFlavourDescriptor() {
|
||||
|
||||
/**
|
||||
@@ -104,7 +109,7 @@ object GitLabUIUtil {
|
||||
MarkdownElementTypes.IMAGE to GitLabImageWithSettingsGeneratingProvider(linkMap, baseURI).makeXssSafe(useSafeLinks),
|
||||
GFMElementTypes.STRIKETHROUGH to SimpleInlineTagProvider("strike", 2, -2),
|
||||
MarkdownElementTypes.CODE_FENCE to CodeFenceSyntaxHighlighterGeneratingProvider(htmlSyntaxHighlighter),
|
||||
MarkdownElementTypes.INLINE_LINK to GitLabLinkGeneratingProvider(gitRepository, projectPath, map[MarkdownElementTypes.INLINE_LINK]),
|
||||
MarkdownElementTypes.INLINE_LINK to GitLabLinkGeneratingProvider(gitRepository, projectPath, uploadFileUrlBase, map[MarkdownElementTypes.INLINE_LINK]),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -198,6 +203,7 @@ object GitLabUIUtil {
|
||||
private class GitLabLinkGeneratingProvider(
|
||||
private val gitRepository: GitRepository,
|
||||
private val projectPath: GitLabProjectPath,
|
||||
private val uploadFileUrlBase: String,
|
||||
private val fallback: GeneratingProvider?
|
||||
) : GeneratingProvider {
|
||||
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
|
||||
@@ -231,6 +237,13 @@ object GitLabUIUtil {
|
||||
return
|
||||
}
|
||||
|
||||
// "uploads" files links should be updated to absolute URLs of the uploads
|
||||
if (linkDestination.startsWith(UPLOADS_PATH)) {
|
||||
visitor.consumeHtml(createLink(
|
||||
uploadFileUrlBase + linkDestination.substring(UPLOADS_PATH.length), linkText).toString())
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, the destination is a file in the current git repo, so we can make the link go to it directly
|
||||
try {
|
||||
val fileDestination = gitRepository.root.toNioPath().resolve(linkDestination.replace('\\', '/')).toString()
|
||||
|
||||
+3
-1
@@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.plugins.gitlab.api.GitLabId
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.*
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.GitLabContextDataLoader
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.emoji.GitLabReactionsViewModel
|
||||
import org.jetbrains.plugins.gitlab.ui.GitLabUIUtil
|
||||
import org.jetbrains.plugins.gitlab.ui.comment.GitLabMergeRequestDiscussionViewModel.NoteItem
|
||||
@@ -116,6 +117,7 @@ class GitLabMergeRequestStandaloneDraftNoteViewModelBase internal constructor(
|
||||
parentCs: CoroutineScope,
|
||||
note: GitLabMergeRequestDraftNote,
|
||||
mr: GitLabMergeRequest,
|
||||
contextDataLoader: GitLabContextDataLoader,
|
||||
) : GitLabNoteViewModel {
|
||||
|
||||
private val cs = parentCs.childScope(this::class)
|
||||
@@ -132,7 +134,7 @@ class GitLabMergeRequestStandaloneDraftNoteViewModelBase internal constructor(
|
||||
|
||||
override val body: StateFlow<String> = note.body
|
||||
override val bodyHtml: StateFlow<String> = body.mapStateInNow(cs) {
|
||||
GitLabUIUtil.convertToHtml(project, mr.gitRepository, mr.glProject.projectPath, it)
|
||||
GitLabUIUtil.convertToHtml(project, mr.gitRepository, mr.glProject.projectPath, it, contextDataLoader.uploadFileUrlBase)
|
||||
}
|
||||
|
||||
override val discussionState: StateFlow<GitLabDiscussionStateContainer> =
|
||||
|
||||
+3
-1
@@ -17,6 +17,7 @@ import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.plugins.gitlab.api.GitLabId
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.*
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.GitLabContextDataLoader
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.emoji.GitLabReactionsViewModel
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.ui.emoji.GitLabReactionsViewModelImpl
|
||||
import org.jetbrains.plugins.gitlab.ui.GitLabUIUtil
|
||||
@@ -64,7 +65,8 @@ class GitLabNoteViewModelImpl(
|
||||
|
||||
override val body: StateFlow<String> = note.body
|
||||
override val bodyHtml: StateFlow<String> = body.mapStateInNow(cs) {
|
||||
GitLabUIUtil.convertToHtml(project, projectData.projectMapping.gitRepository, projectData.projectMapping.repository.projectPath,it)
|
||||
GitLabUIUtil.convertToHtml(project, projectData.projectMapping.gitRepository, projectData.projectMapping.repository.projectPath, it,
|
||||
projectData.contextDataLoader.uploadFileUrlBase)
|
||||
}
|
||||
|
||||
override val discussionState: StateFlow<GitLabDiscussionStateContainer> = isMainNote.map {
|
||||
|
||||
+12
-2
@@ -17,6 +17,8 @@ class GitLabUIUtilTest : LightPlatformTestCase() {
|
||||
private lateinit var gitRepository: GitRepository
|
||||
private lateinit var gitRootVf: VirtualFile
|
||||
|
||||
private val baseUrl = "http://base/url/uploads/"
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
|
||||
@@ -36,7 +38,7 @@ class GitLabUIUtilTest : LightPlatformTestCase() {
|
||||
GitLabUIUtil.convertToHtml(
|
||||
project, gitRepository, GitLabProjectPath("test-account", "mr-test"), """
|
||||
[link](/some/invalid/file/path?query=123)
|
||||
""".trimIndent())
|
||||
""".trimIndent(), baseUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +74,14 @@ class GitLabUIUtilTest : LightPlatformTestCase() {
|
||||
assertThat(parsed.substituteSeparators()).contains("${GitLabUIUtil.OPEN_FILE_LINK_PREFIX}${gitRoot}/directory/a/b/bla.md")
|
||||
}
|
||||
|
||||
fun `test uploads files link rendering`() {
|
||||
val parsed = convertToHtml("""
|
||||
[link](/uploads/a/b/c.jpg) some text
|
||||
""".trimIndent())
|
||||
|
||||
assertThat(parsed).isEqualTo("""<body><p><a href="${baseUrl}a/b/c.jpg">link</a> some text</p></body>""")
|
||||
}
|
||||
|
||||
fun `test simple MR link gets MR link prefix`() {
|
||||
val parsed = convertToHtml("""
|
||||
!53
|
||||
@@ -240,7 +250,7 @@ class GitLabUIUtilTest : LightPlatformTestCase() {
|
||||
|
||||
private fun convertToHtml(markdownSource: String): @NlsSafe String {
|
||||
val parsed = GitLabUIUtil.convertToHtml(
|
||||
project, gitRepository, GitLabProjectPath("test-account", "mr-test"), markdownSource)
|
||||
project, gitRepository, GitLabProjectPath("test-account", "mr-test"), markdownSource, baseUrl)
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user