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

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