You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

wiki.go 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package repo
  5. import (
  6. "bytes"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "path/filepath"
  12. "strings"
  13. "time"
  14. git_model "code.gitea.io/gitea/models/git"
  15. repo_model "code.gitea.io/gitea/models/repo"
  16. "code.gitea.io/gitea/models/unit"
  17. "code.gitea.io/gitea/modules/base"
  18. "code.gitea.io/gitea/modules/charset"
  19. "code.gitea.io/gitea/modules/context"
  20. "code.gitea.io/gitea/modules/git"
  21. "code.gitea.io/gitea/modules/log"
  22. "code.gitea.io/gitea/modules/markup"
  23. "code.gitea.io/gitea/modules/markup/markdown"
  24. "code.gitea.io/gitea/modules/notification"
  25. "code.gitea.io/gitea/modules/setting"
  26. "code.gitea.io/gitea/modules/timeutil"
  27. "code.gitea.io/gitea/modules/util"
  28. "code.gitea.io/gitea/modules/web"
  29. "code.gitea.io/gitea/routers/common"
  30. "code.gitea.io/gitea/services/forms"
  31. wiki_service "code.gitea.io/gitea/services/wiki"
  32. )
  33. const (
  34. tplWikiStart base.TplName = "repo/wiki/start"
  35. tplWikiView base.TplName = "repo/wiki/view"
  36. tplWikiRevision base.TplName = "repo/wiki/revision"
  37. tplWikiNew base.TplName = "repo/wiki/new"
  38. tplWikiPages base.TplName = "repo/wiki/pages"
  39. )
  40. // MustEnableWiki check if wiki is enabled, if external then redirect
  41. func MustEnableWiki(ctx *context.Context) {
  42. if !ctx.Repo.CanRead(unit.TypeWiki) &&
  43. !ctx.Repo.CanRead(unit.TypeExternalWiki) {
  44. if log.IsTrace() {
  45. log.Trace("Permission Denied: User %-v cannot read %-v or %-v of repo %-v\n"+
  46. "User in repo has Permissions: %-+v",
  47. ctx.Doer,
  48. unit.TypeWiki,
  49. unit.TypeExternalWiki,
  50. ctx.Repo.Repository,
  51. ctx.Repo.Permission)
  52. }
  53. ctx.NotFound("MustEnableWiki", nil)
  54. return
  55. }
  56. unit, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalWiki)
  57. if err == nil {
  58. ctx.Redirect(unit.ExternalWikiConfig().ExternalWikiURL)
  59. return
  60. }
  61. }
  62. // PageMeta wiki page meta information
  63. type PageMeta struct {
  64. Name string
  65. SubURL string
  66. GitEntryName string
  67. UpdatedUnix timeutil.TimeStamp
  68. }
  69. // findEntryForFile finds the tree entry for a target filepath.
  70. func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
  71. entry, err := commit.GetTreeEntryByPath(target)
  72. if err != nil && !git.IsErrNotExist(err) {
  73. return nil, err
  74. }
  75. if entry != nil {
  76. return entry, nil
  77. }
  78. // Then the unescaped, the shortest alternative
  79. var unescapedTarget string
  80. if unescapedTarget, err = url.QueryUnescape(target); err != nil {
  81. return nil, err
  82. }
  83. return commit.GetTreeEntryByPath(unescapedTarget)
  84. }
  85. func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
  86. wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
  87. if err != nil {
  88. ctx.ServerError("OpenRepository", err)
  89. return nil, nil, err
  90. }
  91. commit, err := wikiRepo.GetBranchCommit(wiki_service.DefaultBranch)
  92. if err != nil {
  93. return wikiRepo, nil, err
  94. }
  95. return wikiRepo, commit, nil
  96. }
  97. // wikiContentsByEntry returns the contents of the wiki page referenced by the
  98. // given tree entry. Writes to ctx if an error occurs.
  99. func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
  100. reader, err := entry.Blob().DataAsync()
  101. if err != nil {
  102. ctx.ServerError("Blob.Data", err)
  103. return nil
  104. }
  105. defer reader.Close()
  106. content, err := io.ReadAll(reader)
  107. if err != nil {
  108. ctx.ServerError("ReadAll", err)
  109. return nil
  110. }
  111. return content
  112. }
  113. // wikiContentsByName returns the contents of a wiki page, along with a boolean
  114. // indicating whether the page exists. Writes to ctx if an error occurs.
  115. func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) ([]byte, *git.TreeEntry, string, bool) {
  116. gitFilename := wiki_service.WebPathToGitPath(wikiName)
  117. entry, err := findEntryForFile(commit, gitFilename)
  118. if err != nil && !git.IsErrNotExist(err) {
  119. ctx.ServerError("findEntryForFile", err)
  120. return nil, nil, "", false
  121. } else if entry == nil {
  122. return nil, nil, "", true
  123. }
  124. return wikiContentsByEntry(ctx, entry), entry, gitFilename, false
  125. }
  126. func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
  127. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  128. if err != nil {
  129. if wikiRepo != nil {
  130. wikiRepo.Close()
  131. }
  132. if !git.IsErrNotExist(err) {
  133. ctx.ServerError("GetBranchCommit", err)
  134. }
  135. return nil, nil
  136. }
  137. // Get page list.
  138. entries, err := commit.ListEntries()
  139. if err != nil {
  140. if wikiRepo != nil {
  141. wikiRepo.Close()
  142. }
  143. ctx.ServerError("ListEntries", err)
  144. return nil, nil
  145. }
  146. pages := make([]PageMeta, 0, len(entries))
  147. for _, entry := range entries {
  148. if !entry.IsRegular() {
  149. continue
  150. }
  151. wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
  152. if err != nil {
  153. if repo_model.IsErrWikiInvalidFileName(err) {
  154. continue
  155. }
  156. if wikiRepo != nil {
  157. wikiRepo.Close()
  158. }
  159. ctx.ServerError("WikiFilenameToName", err)
  160. return nil, nil
  161. } else if wikiName == "_Sidebar" || wikiName == "_Footer" {
  162. continue
  163. }
  164. _, displayName := wiki_service.WebPathToUserTitle(wikiName)
  165. pages = append(pages, PageMeta{
  166. Name: displayName,
  167. SubURL: wiki_service.WebPathToURLPath(wikiName),
  168. GitEntryName: entry.Name(),
  169. })
  170. }
  171. ctx.Data["Pages"] = pages
  172. // get requested page name
  173. pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
  174. if len(pageName) == 0 {
  175. pageName = "Home"
  176. }
  177. _, displayName := wiki_service.WebPathToUserTitle(pageName)
  178. ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
  179. ctx.Data["old_title"] = displayName
  180. ctx.Data["Title"] = displayName
  181. ctx.Data["title"] = displayName
  182. isSideBar := pageName == "_Sidebar"
  183. isFooter := pageName == "_Footer"
  184. // lookup filename in wiki - get filecontent, gitTree entry , real filename
  185. data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
  186. if noEntry {
  187. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
  188. }
  189. if entry == nil || ctx.Written() {
  190. if wikiRepo != nil {
  191. wikiRepo.Close()
  192. }
  193. return nil, nil
  194. }
  195. var sidebarContent []byte
  196. if !isSideBar {
  197. sidebarContent, _, _, _ = wikiContentsByName(ctx, commit, "_Sidebar")
  198. if ctx.Written() {
  199. if wikiRepo != nil {
  200. wikiRepo.Close()
  201. }
  202. return nil, nil
  203. }
  204. } else {
  205. sidebarContent = data
  206. }
  207. var footerContent []byte
  208. if !isFooter {
  209. footerContent, _, _, _ = wikiContentsByName(ctx, commit, "_Footer")
  210. if ctx.Written() {
  211. if wikiRepo != nil {
  212. wikiRepo.Close()
  213. }
  214. return nil, nil
  215. }
  216. } else {
  217. footerContent = data
  218. }
  219. rctx := &markup.RenderContext{
  220. Ctx: ctx,
  221. URLPrefix: ctx.Repo.RepoLink,
  222. Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
  223. IsWiki: true,
  224. }
  225. buf := &strings.Builder{}
  226. renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) {
  227. markupRd, markupWr := io.Pipe()
  228. defer markupWr.Close()
  229. done := make(chan struct{})
  230. go func() {
  231. // We allow NBSP here this is rendered
  232. escaped, _ = charset.EscapeControlReader(markupRd, buf, ctx.Locale, charset.RuneNBSP)
  233. output = buf.String()
  234. buf.Reset()
  235. close(done)
  236. }()
  237. err = markdown.Render(rctx, bytes.NewReader(data), markupWr)
  238. _ = markupWr.CloseWithError(err)
  239. <-done
  240. return escaped, output, err
  241. }
  242. ctx.Data["EscapeStatus"], ctx.Data["content"], err = renderFn(data)
  243. if err != nil {
  244. if wikiRepo != nil {
  245. wikiRepo.Close()
  246. }
  247. ctx.ServerError("Render", err)
  248. return nil, nil
  249. }
  250. if rctx.SidebarTocNode != nil {
  251. sb := &strings.Builder{}
  252. err = markdown.SpecializedMarkdown().Renderer().Render(sb, nil, rctx.SidebarTocNode)
  253. if err != nil {
  254. log.Error("Failed to render wiki sidebar TOC: %v", err)
  255. } else {
  256. ctx.Data["sidebarTocContent"] = sb.String()
  257. }
  258. }
  259. if !isSideBar {
  260. buf.Reset()
  261. ctx.Data["sidebarEscapeStatus"], ctx.Data["sidebarContent"], err = renderFn(sidebarContent)
  262. if err != nil {
  263. if wikiRepo != nil {
  264. wikiRepo.Close()
  265. }
  266. ctx.ServerError("Render", err)
  267. return nil, nil
  268. }
  269. ctx.Data["sidebarPresent"] = sidebarContent != nil
  270. } else {
  271. ctx.Data["sidebarPresent"] = false
  272. }
  273. if !isFooter {
  274. buf.Reset()
  275. ctx.Data["footerEscapeStatus"], ctx.Data["footerContent"], err = renderFn(footerContent)
  276. if err != nil {
  277. if wikiRepo != nil {
  278. wikiRepo.Close()
  279. }
  280. ctx.ServerError("Render", err)
  281. return nil, nil
  282. }
  283. ctx.Data["footerPresent"] = footerContent != nil
  284. } else {
  285. ctx.Data["footerPresent"] = false
  286. }
  287. // get commit count - wiki revisions
  288. commitsCount, _ := wikiRepo.FileCommitsCount(wiki_service.DefaultBranch, pageFilename)
  289. ctx.Data["CommitCount"] = commitsCount
  290. return wikiRepo, entry
  291. }
  292. func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
  293. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  294. if err != nil {
  295. if wikiRepo != nil {
  296. wikiRepo.Close()
  297. }
  298. if !git.IsErrNotExist(err) {
  299. ctx.ServerError("GetBranchCommit", err)
  300. }
  301. return nil, nil
  302. }
  303. // get requested pagename
  304. pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
  305. if len(pageName) == 0 {
  306. pageName = "Home"
  307. }
  308. _, displayName := wiki_service.WebPathToUserTitle(pageName)
  309. ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
  310. ctx.Data["old_title"] = displayName
  311. ctx.Data["Title"] = displayName
  312. ctx.Data["title"] = displayName
  313. ctx.Data["Username"] = ctx.Repo.Owner.Name
  314. ctx.Data["Reponame"] = ctx.Repo.Repository.Name
  315. // lookup filename in wiki - get filecontent, gitTree entry , real filename
  316. data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
  317. if noEntry {
  318. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
  319. }
  320. if entry == nil || ctx.Written() {
  321. if wikiRepo != nil {
  322. wikiRepo.Close()
  323. }
  324. return nil, nil
  325. }
  326. ctx.Data["content"] = string(data)
  327. ctx.Data["sidebarPresent"] = false
  328. ctx.Data["sidebarContent"] = ""
  329. ctx.Data["footerPresent"] = false
  330. ctx.Data["footerContent"] = ""
  331. // get commit count - wiki revisions
  332. commitsCount, _ := wikiRepo.FileCommitsCount(wiki_service.DefaultBranch, pageFilename)
  333. ctx.Data["CommitCount"] = commitsCount
  334. // get page
  335. page := ctx.FormInt("page")
  336. if page <= 1 {
  337. page = 1
  338. }
  339. // get Commit Count
  340. commitsHistory, err := wikiRepo.CommitsByFileAndRange(
  341. git.CommitsByFileAndRangeOptions{
  342. Revision: wiki_service.DefaultBranch,
  343. File: pageFilename,
  344. Page: page,
  345. })
  346. if err != nil {
  347. if wikiRepo != nil {
  348. wikiRepo.Close()
  349. }
  350. ctx.ServerError("CommitsByFileAndRange", err)
  351. return nil, nil
  352. }
  353. ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commitsHistory, ctx.Repo.Repository)
  354. pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5)
  355. pager.SetDefaultParams(ctx)
  356. ctx.Data["Page"] = pager
  357. return wikiRepo, entry
  358. }
  359. func renderEditPage(ctx *context.Context) {
  360. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  361. if err != nil {
  362. if wikiRepo != nil {
  363. wikiRepo.Close()
  364. }
  365. if !git.IsErrNotExist(err) {
  366. ctx.ServerError("GetBranchCommit", err)
  367. }
  368. return
  369. }
  370. defer func() {
  371. if wikiRepo != nil {
  372. wikiRepo.Close()
  373. }
  374. }()
  375. // get requested pagename
  376. pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
  377. if len(pageName) == 0 {
  378. pageName = "Home"
  379. }
  380. _, displayName := wiki_service.WebPathToUserTitle(pageName)
  381. ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
  382. ctx.Data["old_title"] = displayName
  383. ctx.Data["Title"] = displayName
  384. ctx.Data["title"] = displayName
  385. // lookup filename in wiki - get filecontent, gitTree entry , real filename
  386. data, entry, _, noEntry := wikiContentsByName(ctx, commit, pageName)
  387. if noEntry {
  388. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
  389. }
  390. if entry == nil || ctx.Written() {
  391. return
  392. }
  393. ctx.Data["content"] = string(data)
  394. ctx.Data["sidebarPresent"] = false
  395. ctx.Data["sidebarContent"] = ""
  396. ctx.Data["footerPresent"] = false
  397. ctx.Data["footerContent"] = ""
  398. }
  399. // WikiPost renders post of wiki page
  400. func WikiPost(ctx *context.Context) {
  401. switch ctx.FormString("action") {
  402. case "_new":
  403. if !ctx.Repo.CanWrite(unit.TypeWiki) {
  404. ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
  405. return
  406. }
  407. NewWikiPost(ctx)
  408. return
  409. case "_delete":
  410. if !ctx.Repo.CanWrite(unit.TypeWiki) {
  411. ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
  412. return
  413. }
  414. DeleteWikiPagePost(ctx)
  415. return
  416. }
  417. if !ctx.Repo.CanWrite(unit.TypeWiki) {
  418. ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
  419. return
  420. }
  421. EditWikiPost(ctx)
  422. }
  423. // Wiki renders single wiki page
  424. func Wiki(ctx *context.Context) {
  425. ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
  426. switch ctx.FormString("action") {
  427. case "_pages":
  428. WikiPages(ctx)
  429. return
  430. case "_revision":
  431. WikiRevision(ctx)
  432. return
  433. case "_edit":
  434. if !ctx.Repo.CanWrite(unit.TypeWiki) {
  435. ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
  436. return
  437. }
  438. EditWiki(ctx)
  439. return
  440. case "_new":
  441. if !ctx.Repo.CanWrite(unit.TypeWiki) {
  442. ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
  443. return
  444. }
  445. NewWiki(ctx)
  446. return
  447. }
  448. if !ctx.Repo.Repository.HasWiki() {
  449. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  450. ctx.HTML(http.StatusOK, tplWikiStart)
  451. return
  452. }
  453. wikiRepo, entry := renderViewPage(ctx)
  454. defer func() {
  455. if wikiRepo != nil {
  456. wikiRepo.Close()
  457. }
  458. }()
  459. if ctx.Written() {
  460. return
  461. }
  462. if entry == nil {
  463. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  464. ctx.HTML(http.StatusOK, tplWikiStart)
  465. return
  466. }
  467. wikiPath := entry.Name()
  468. if markup.Type(wikiPath) != markdown.MarkupName {
  469. ext := strings.ToUpper(filepath.Ext(wikiPath))
  470. ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext)
  471. }
  472. // Get last change information.
  473. lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
  474. if err != nil {
  475. ctx.ServerError("GetCommitByPath", err)
  476. return
  477. }
  478. ctx.Data["Author"] = lastCommit.Author
  479. ctx.HTML(http.StatusOK, tplWikiView)
  480. }
  481. // WikiRevision renders file revision list of wiki page
  482. func WikiRevision(ctx *context.Context) {
  483. ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
  484. if !ctx.Repo.Repository.HasWiki() {
  485. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  486. ctx.HTML(http.StatusOK, tplWikiStart)
  487. return
  488. }
  489. wikiRepo, entry := renderRevisionPage(ctx)
  490. defer func() {
  491. if wikiRepo != nil {
  492. wikiRepo.Close()
  493. }
  494. }()
  495. if ctx.Written() {
  496. return
  497. }
  498. if entry == nil {
  499. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  500. ctx.HTML(http.StatusOK, tplWikiStart)
  501. return
  502. }
  503. // Get last change information.
  504. wikiPath := entry.Name()
  505. lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
  506. if err != nil {
  507. ctx.ServerError("GetCommitByPath", err)
  508. return
  509. }
  510. ctx.Data["Author"] = lastCommit.Author
  511. ctx.HTML(http.StatusOK, tplWikiRevision)
  512. }
  513. // WikiPages render wiki pages list page
  514. func WikiPages(ctx *context.Context) {
  515. if !ctx.Repo.Repository.HasWiki() {
  516. ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
  517. return
  518. }
  519. ctx.Data["Title"] = ctx.Tr("repo.wiki.pages")
  520. ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
  521. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  522. if err != nil {
  523. if wikiRepo != nil {
  524. wikiRepo.Close()
  525. }
  526. return
  527. }
  528. defer func() {
  529. if wikiRepo != nil {
  530. wikiRepo.Close()
  531. }
  532. }()
  533. entries, err := commit.ListEntries()
  534. if err != nil {
  535. ctx.ServerError("ListEntries", err)
  536. return
  537. }
  538. pages := make([]PageMeta, 0, len(entries))
  539. for _, entry := range entries {
  540. if !entry.IsRegular() {
  541. continue
  542. }
  543. c, err := wikiRepo.GetCommitByPath(entry.Name())
  544. if err != nil {
  545. ctx.ServerError("GetCommit", err)
  546. return
  547. }
  548. wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
  549. if err != nil {
  550. if repo_model.IsErrWikiInvalidFileName(err) {
  551. continue
  552. }
  553. ctx.ServerError("WikiFilenameToName", err)
  554. return
  555. }
  556. _, displayName := wiki_service.WebPathToUserTitle(wikiName)
  557. pages = append(pages, PageMeta{
  558. Name: displayName,
  559. SubURL: wiki_service.WebPathToURLPath(wikiName),
  560. GitEntryName: entry.Name(),
  561. UpdatedUnix: timeutil.TimeStamp(c.Author.When.Unix()),
  562. })
  563. }
  564. ctx.Data["Pages"] = pages
  565. ctx.HTML(http.StatusOK, tplWikiPages)
  566. }
  567. // WikiRaw outputs raw blob requested by user (image for example)
  568. func WikiRaw(ctx *context.Context) {
  569. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  570. defer func() {
  571. if wikiRepo != nil {
  572. wikiRepo.Close()
  573. }
  574. }()
  575. if err != nil {
  576. if git.IsErrNotExist(err) {
  577. ctx.NotFound("findEntryForFile", nil)
  578. return
  579. }
  580. ctx.ServerError("findEntryForfile", err)
  581. return
  582. }
  583. providedWebPath := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
  584. providedGitPath := wiki_service.WebPathToGitPath(providedWebPath)
  585. var entry *git.TreeEntry
  586. if commit != nil {
  587. // Try to find a file with that name
  588. entry, err = findEntryForFile(commit, providedGitPath)
  589. if err != nil && !git.IsErrNotExist(err) {
  590. ctx.ServerError("findFile", err)
  591. return
  592. }
  593. if entry == nil {
  594. // Try to find a wiki page with that name
  595. providedGitPath = strings.TrimSuffix(providedGitPath, ".md")
  596. entry, err = findEntryForFile(commit, providedGitPath)
  597. if err != nil && !git.IsErrNotExist(err) {
  598. ctx.ServerError("findFile", err)
  599. return
  600. }
  601. }
  602. }
  603. if entry != nil {
  604. if err = common.ServeBlob(ctx.Base, ctx.Repo.TreePath, entry.Blob(), time.Time{}); err != nil {
  605. ctx.ServerError("ServeBlob", err)
  606. }
  607. return
  608. }
  609. ctx.NotFound("findEntryForFile", nil)
  610. }
  611. // NewWiki render wiki create page
  612. func NewWiki(ctx *context.Context) {
  613. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  614. if !ctx.Repo.Repository.HasWiki() {
  615. ctx.Data["title"] = "Home"
  616. }
  617. if ctx.FormString("title") != "" {
  618. ctx.Data["title"] = ctx.FormString("title")
  619. }
  620. ctx.HTML(http.StatusOK, tplWikiNew)
  621. }
  622. // NewWikiPost response for wiki create request
  623. func NewWikiPost(ctx *context.Context) {
  624. form := web.GetForm(ctx).(*forms.NewWikiForm)
  625. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  626. if ctx.HasError() {
  627. ctx.HTML(http.StatusOK, tplWikiNew)
  628. return
  629. }
  630. if util.IsEmptyString(form.Title) {
  631. ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplWikiNew, form)
  632. return
  633. }
  634. wikiName := wiki_service.UserTitleToWebPath("", form.Title)
  635. if len(form.Message) == 0 {
  636. form.Message = ctx.Tr("repo.editor.add", form.Title)
  637. }
  638. if err := wiki_service.AddWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName, form.Content, form.Message); err != nil {
  639. if repo_model.IsErrWikiReservedName(err) {
  640. ctx.Data["Err_Title"] = true
  641. ctx.RenderWithErr(ctx.Tr("repo.wiki.reserved_page", wikiName), tplWikiNew, &form)
  642. } else if repo_model.IsErrWikiAlreadyExist(err) {
  643. ctx.Data["Err_Title"] = true
  644. ctx.RenderWithErr(ctx.Tr("repo.wiki.page_already_exists"), tplWikiNew, &form)
  645. } else {
  646. ctx.ServerError("AddWikiPage", err)
  647. }
  648. return
  649. }
  650. notification.NotifyNewWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName), form.Message)
  651. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(wikiName))
  652. }
  653. // EditWiki render wiki modify page
  654. func EditWiki(ctx *context.Context) {
  655. ctx.Data["PageIsWikiEdit"] = true
  656. if !ctx.Repo.Repository.HasWiki() {
  657. ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
  658. return
  659. }
  660. renderEditPage(ctx)
  661. if ctx.Written() {
  662. return
  663. }
  664. ctx.HTML(http.StatusOK, tplWikiNew)
  665. }
  666. // EditWikiPost response for wiki modify request
  667. func EditWikiPost(ctx *context.Context) {
  668. form := web.GetForm(ctx).(*forms.NewWikiForm)
  669. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  670. if ctx.HasError() {
  671. ctx.HTML(http.StatusOK, tplWikiNew)
  672. return
  673. }
  674. oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
  675. newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
  676. if len(form.Message) == 0 {
  677. form.Message = ctx.Tr("repo.editor.update", form.Title)
  678. }
  679. if err := wiki_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, oldWikiName, newWikiName, form.Content, form.Message); err != nil {
  680. ctx.ServerError("EditWikiPage", err)
  681. return
  682. }
  683. notification.NotifyEditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(newWikiName), form.Message)
  684. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(newWikiName))
  685. }
  686. // DeleteWikiPagePost delete wiki page
  687. func DeleteWikiPagePost(ctx *context.Context) {
  688. wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
  689. if len(wikiName) == 0 {
  690. wikiName = "Home"
  691. }
  692. if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil {
  693. ctx.ServerError("DeleteWikiPage", err)
  694. return
  695. }
  696. notification.NotifyDeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName))
  697. ctx.JSON(http.StatusOK, map[string]any{
  698. "redirect": ctx.Repo.RepoLink + "/wiki/",
  699. })
  700. }