return nil, runErr | return nil, runErr | ||||
} | } | ||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) | |||||
var err error | var err error | ||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, repo.RepoPath()) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
changes.Updates, err = parseGitLsTreeOutput(objectFormat, stdout) | changes.Updates, err = parseGitLsTreeOutput(objectFormat, stdout) | ||||
return &changes, err | return &changes, err | ||||
} | } | ||||
return nil, err | return nil, err | ||||
} | } | ||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, repo.RepoPath()) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) | |||||
changes.Updates, err = parseGitLsTreeOutput(objectFormat, lsTreeStdout) | changes.Updates, err = parseGitLsTreeOutput(objectFormat, lsTreeStdout) | ||||
return &changes, err | return &changes, err | ||||
} | } |
} | } | ||||
// include language-x class as part of commonmark spec | // include language-x class as part of commonmark spec | ||||
_, err = w.WriteString(`<code class="chroma language-` + string(language) + `">`) | |||||
// the "display" class is used by "js/markup/math.js" to render the code element as a block | |||||
_, err = w.WriteString(`<code class="chroma language-` + string(language) + ` display">`) | |||||
if err != nil { | if err != nil { | ||||
return | return | ||||
} | } |
// so we need to try to guess the link kind again here | // so we need to try to guess the link kind again here | ||||
kind = org.RegularLink{URL: link}.Kind() | kind = org.RegularLink{URL: link}.Kind() | ||||
} | } | ||||
base := r.Ctx.Links.Base | base := r.Ctx.Links.Base | ||||
if r.Ctx.IsWiki { | |||||
base = r.Ctx.Links.WikiLink() | |||||
} else if r.Ctx.Links.HasBranchInfo() { | |||||
base = r.Ctx.Links.SrcLink() | |||||
} | |||||
if kind == "image" || kind == "video" { | if kind == "image" || kind == "video" { | ||||
base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsWiki) | base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsWiki) | ||||
} | } | ||||
link = util.URLJoin(base, link) | link = util.URLJoin(base, link) | ||||
} | } | ||||
return link | return link |
func TestRender_StandardLinks(t *testing.T) { | func TestRender_StandardLinks(t *testing.T) { | ||||
setting.AppURL = AppURL | setting.AppURL = AppURL | ||||
test := func(input, expected string) { | |||||
test := func(input, expected string, isWiki bool) { | |||||
buffer, err := RenderString(&markup.RenderContext{ | buffer, err := RenderString(&markup.RenderContext{ | ||||
Ctx: git.DefaultContext, | Ctx: git.DefaultContext, | ||||
Links: markup.Links{ | Links: markup.Links{ | ||||
Base: "/relative-path", | Base: "/relative-path", | ||||
BranchPath: "branch/main", | BranchPath: "branch/main", | ||||
}, | }, | ||||
IsWiki: isWiki, | |||||
}, input) | }, input) | ||||
assert.NoError(t, err) | assert.NoError(t, err) | ||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) | assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) | ||||
} | } | ||||
test("[[https://google.com/]]", | test("[[https://google.com/]]", | ||||
`<p><a href="https://google.com/">https://google.com/</a></p>`) | |||||
`<p><a href="https://google.com/">https://google.com/</a></p>`, false) | |||||
test("[[WikiPage][The WikiPage Desc]]", | test("[[WikiPage][The WikiPage Desc]]", | ||||
`<p><a href="/relative-path/WikiPage">The WikiPage Desc</a></p>`) | |||||
`<p><a href="/relative-path/wiki/WikiPage">The WikiPage Desc</a></p>`, true) | |||||
test("[[ImageLink.svg][The Image Desc]]", | test("[[ImageLink.svg][The Image Desc]]", | ||||
`<p><a href="/relative-path/media/branch/main/ImageLink.svg">The Image Desc</a></p>`) | |||||
`<p><a href="/relative-path/media/branch/main/ImageLink.svg">The Image Desc</a></p>`, false) | |||||
} | |||||
func TestRender_InternalLinks(t *testing.T) { | |||||
setting.AppURL = AppURL | |||||
test := func(input, expected string) { | |||||
buffer, err := RenderString(&markup.RenderContext{ | |||||
Ctx: git.DefaultContext, | |||||
Links: markup.Links{ | |||||
Base: "/relative-path", | |||||
BranchPath: "branch/main", | |||||
}, | |||||
}, input) | |||||
assert.NoError(t, err) | |||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) | |||||
} | |||||
test("[[file:test.org][Test]]", | |||||
`<p><a href="/relative-path/src/branch/main/test.org">Test</a></p>`) | |||||
test("[[./test.org][Test]]", | |||||
`<p><a href="/relative-path/src/branch/main/test.org">Test</a></p>`) | |||||
test("[[test.org][Test]]", | |||||
`<p><a href="/relative-path/src/branch/main/test.org">Test</a></p>`) | |||||
test("[[path/to/test.org][Test]]", | |||||
`<p><a href="/relative-path/src/branch/main/path/to/test.org">Test</a></p>`) | |||||
} | } | ||||
func TestRender_Media(t *testing.T) { | func TestRender_Media(t *testing.T) { |
return | return | ||||
} | } | ||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, ctx.Repo.Repository.RepoPath()) | |||||
if err != nil { | |||||
log.Error("RestoreBranch: CreateBranch: %w", err) | |||||
ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name)) | |||||
return | |||||
} | |||||
objectFormat := git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName) | |||||
// Don't return error below this | // Don't return error below this | ||||
if err := repo_service.PushUpdate( | if err := repo_service.PushUpdate( |
commit := ctx.Repo.Commit | commit := ctx.Repo.Commit | ||||
if commit == nil { | if commit == nil { | ||||
ghost := user_model.NewGhostUser() | ghost := user_model.NewGhostUser() | ||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, ctx.Repo.Repository.RepoPath()) | |||||
if err != nil { | |||||
ctx.Flash.Error("GetObjectFormatOfRepo: " + err.Error()) | |||||
ctx.Status(http.StatusInternalServerError) | |||||
return | |||||
} | |||||
objectFormat := git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName) | |||||
commit = &git.Commit{ | commit = &git.Commit{ | ||||
ID: objectFormat.EmptyObjectID(), | ID: objectFormat.EmptyObjectID(), | ||||
Author: ghost.NewGitSig(), | Author: ghost.NewGitSig(), |
log.Error("SyncMirrors [repo: %-v]: unable to GetRefCommitID [ref_name: %s]: %v", m.Repo, result.refName, err) | log.Error("SyncMirrors [repo: %-v]: unable to GetRefCommitID [ref_name: %s]: %v", m.Repo, result.refName, err) | ||||
continue | continue | ||||
} | } | ||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, m.Repo.RepoPath()) | |||||
if err != nil { | |||||
log.Error("SyncMirrors [repo: %-v]: unable to GetHashTypeOfRepo: %v", m.Repo, err) | |||||
} | |||||
objectFormat := git.ObjectFormatFromName(m.Repo.ObjectFormatName) | |||||
notify_service.SyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{ | notify_service.SyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{ | ||||
RefFullName: result.refName, | RefFullName: result.refName, | ||||
OldCommitID: objectFormat.EmptyObjectID().String(), | OldCommitID: objectFormat.EmptyObjectID().String(), |
} | } | ||||
if err == nil { | if err == nil { | ||||
for _, pr := range prs { | for _, pr := range prs { | ||||
objectFormat, _ := git.GetObjectFormatOfRepo(ctx, pr.BaseRepo.RepoPath()) | |||||
objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName) | |||||
if newCommitID != "" && newCommitID != objectFormat.EmptyObjectID().String() { | if newCommitID != "" && newCommitID != objectFormat.EmptyObjectID().String() { | ||||
changed, err := checkIfPRContentChanged(ctx, pr, oldCommitID, newCommitID) | changed, err := checkIfPRContentChanged(ctx, pr, oldCommitID, newCommitID) | ||||
if err != nil { | if err != nil { |
} | } | ||||
refName := git.RefNameFromTag(rel.TagName) | refName := git.RefNameFromTag(rel.TagName) | ||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, repo.RepoPath()) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) | |||||
notify_service.PushCommits( | notify_service.PushCommits( | ||||
ctx, doer, repo, | ctx, doer, repo, | ||||
&repository.PushUpdateOptions{ | &repository.PushUpdateOptions{ |
.repository .data-table tr { | .repository .data-table tr { | ||||
border-top: 0; | border-top: 0; | ||||
background: none !important; | |||||
} | } | ||||
.repository .data-table td, | .repository .data-table td, | ||||
border: 1px solid var(--color-secondary); | border: 1px solid var(--color-secondary); | ||||
} | } | ||||
/* the border css competes with .markup where all tables have outer border which would add a double | |||||
border here, remove only the outer borders from this table */ | |||||
.repository .data-table tr:first-child :is(td,th) { | |||||
border-top: none !important; | |||||
} | |||||
.repository .data-table tr:last-child :is(td,th) { | |||||
border-bottom: none !important; | |||||
} | |||||
.repository .data-table tr :is(td,th):first-child { | |||||
border-left: none !important; | |||||
} | |||||
.repository .data-table tr :is(td,th):last-child { | |||||
border-right: none !important; | |||||
} | |||||
.repository .data-table td { | .repository .data-table td { | ||||
white-space: pre-line; | white-space: pre-line; | ||||
} | } | ||||
min-width: 50px; | min-width: 50px; | ||||
font-family: monospace; | font-family: monospace; | ||||
line-height: 20px; | line-height: 20px; | ||||
color: var(--color-secondary-dark-2); | |||||
color: var(--color-text-light-1); | |||||
white-space: nowrap; | white-space: nowrap; | ||||
vertical-align: top; | vertical-align: top; | ||||
cursor: pointer; | cursor: pointer; |
import $ from 'jquery'; | import $ from 'jquery'; | ||||
import {svg} from '../svg.js'; | import {svg} from '../svg.js'; | ||||
import {showErrorToast} from '../modules/toast.js'; | import {showErrorToast} from '../modules/toast.js'; | ||||
import {GET, POST} from '../modules/fetch.js'; | |||||
const {appSubUrl, csrfToken} = window.config; | |||||
const {appSubUrl} = window.config; | |||||
let i18nTextEdited; | let i18nTextEdited; | ||||
let i18nTextOptions; | let i18nTextOptions; | ||||
let i18nTextDeleteFromHistory; | let i18nTextDeleteFromHistory; | ||||
$dialog.find('.dialog-header-options').dropdown({ | $dialog.find('.dialog-header-options').dropdown({ | ||||
showOnFocus: false, | showOnFocus: false, | ||||
allowReselection: true, | allowReselection: true, | ||||
onChange(_value, _text, $item) { | |||||
async onChange(_value, _text, $item) { | |||||
const optionItem = $item.data('option-item'); | const optionItem = $item.data('option-item'); | ||||
if (optionItem === 'delete') { | if (optionItem === 'delete') { | ||||
if (window.confirm(i18nTextDeleteFromHistoryConfirm)) { | if (window.confirm(i18nTextDeleteFromHistoryConfirm)) { | ||||
$.post(`${issueBaseUrl}/content-history/soft-delete?comment_id=${commentId}&history_id=${historyId}`, { | |||||
_csrf: csrfToken, | |||||
}).done((resp) => { | |||||
try { | |||||
const params = new URLSearchParams(); | |||||
params.append('comment_id', commentId); | |||||
params.append('history_id', historyId); | |||||
const response = await POST(`${issueBaseUrl}/content-history/soft-delete?${params.toString()}`); | |||||
const resp = await response.json(); | |||||
if (resp.ok) { | if (resp.ok) { | ||||
$dialog.modal('hide'); | $dialog.modal('hide'); | ||||
} else { | } else { | ||||
showErrorToast(resp.message); | showErrorToast(resp.message); | ||||
} | } | ||||
}); | |||||
} catch (error) { | |||||
console.error('Error:', error); | |||||
showErrorToast('An error occurred while deleting the history.'); | |||||
} | |||||
} | } | ||||
} else { // required by eslint | } else { // required by eslint | ||||
showErrorToast(`unknown option item: ${optionItem}`); | showErrorToast(`unknown option item: ${optionItem}`); | ||||
} | } | ||||
}); | }); | ||||
$dialog.modal({ | $dialog.modal({ | ||||
onShow() { | |||||
$.ajax({ | |||||
url: `${issueBaseUrl}/content-history/detail?comment_id=${commentId}&history_id=${historyId}`, | |||||
data: { | |||||
_csrf: csrfToken, | |||||
}, | |||||
}).done((resp) => { | |||||
async onShow() { | |||||
try { | |||||
const params = new URLSearchParams(); | |||||
params.append('comment_id', commentId); | |||||
params.append('history_id', historyId); | |||||
const url = `${issueBaseUrl}/content-history/detail?${params.toString()}`; | |||||
const response = await GET(url); | |||||
const resp = await response.json(); | |||||
$dialog.find('.comment-diff-data').removeClass('is-loading').html(resp.diffHtml); | $dialog.find('.comment-diff-data').removeClass('is-loading').html(resp.diffHtml); | ||||
// there is only one option "item[data-option-item=delete]", so the dropdown can be entirely shown/hidden. | // there is only one option "item[data-option-item=delete]", so the dropdown can be entirely shown/hidden. | ||||
if (resp.canSoftDelete) { | if (resp.canSoftDelete) { | ||||
$dialog.find('.dialog-header-options').removeClass('gt-hidden'); | $dialog.find('.dialog-header-options').removeClass('gt-hidden'); | ||||
} | } | ||||
}); | |||||
} catch (error) { | |||||
console.error('Error:', error); | |||||
} | |||||
}, | }, | ||||
onHidden() { | onHidden() { | ||||
$dialog.remove(); | $dialog.remove(); | ||||
}); | }); | ||||
} | } | ||||
export function initRepoIssueContentHistory() { | |||||
export async function initRepoIssueContentHistory() { | |||||
const issueIndex = $('#issueIndex').val(); | const issueIndex = $('#issueIndex').val(); | ||||
if (!issueIndex) return; | if (!issueIndex) return; | ||||
const repoLink = $('#repolink').val(); | const repoLink = $('#repolink').val(); | ||||
const issueBaseUrl = `${appSubUrl}/${repoLink}/issues/${issueIndex}`; | const issueBaseUrl = `${appSubUrl}/${repoLink}/issues/${issueIndex}`; | ||||
$.ajax({ | |||||
url: `${issueBaseUrl}/content-history/overview`, | |||||
data: { | |||||
_csrf: csrfToken, | |||||
}, | |||||
}).done((resp) => { | |||||
try { | |||||
const response = await GET(`${issueBaseUrl}/content-history/overview`); | |||||
const resp = await response.json(); | |||||
i18nTextEdited = resp.i18n.textEdited; | i18nTextEdited = resp.i18n.textEdited; | ||||
i18nTextDeleteFromHistory = resp.i18n.textDeleteFromHistory; | i18nTextDeleteFromHistory = resp.i18n.textDeleteFromHistory; | ||||
i18nTextDeleteFromHistoryConfirm = resp.i18n.textDeleteFromHistoryConfirm; | i18nTextDeleteFromHistoryConfirm = resp.i18n.textDeleteFromHistoryConfirm; | ||||
const $itemComment = $(`#issuecomment-${commentId}`); | const $itemComment = $(`#issuecomment-${commentId}`); | ||||
showContentHistoryMenu(issueBaseUrl, $itemComment, commentId); | showContentHistoryMenu(issueBaseUrl, $itemComment, commentId); | ||||
} | } | ||||
}); | |||||
} catch (error) { | |||||
console.error('Error:', error); | |||||
} | |||||
} | } |