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

wiki.go 11 kB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. "fmt"
  7. "io/ioutil"
  8. "path/filepath"
  9. "strings"
  10. "code.gitea.io/git"
  11. "code.gitea.io/gitea/models"
  12. "code.gitea.io/gitea/modules/auth"
  13. "code.gitea.io/gitea/modules/base"
  14. "code.gitea.io/gitea/modules/context"
  15. "code.gitea.io/gitea/modules/markup"
  16. "code.gitea.io/gitea/modules/markup/markdown"
  17. "code.gitea.io/gitea/modules/util"
  18. )
  19. const (
  20. tplWikiStart base.TplName = "repo/wiki/start"
  21. tplWikiView base.TplName = "repo/wiki/view"
  22. tplWikiNew base.TplName = "repo/wiki/new"
  23. tplWikiPages base.TplName = "repo/wiki/pages"
  24. )
  25. // MustEnableWiki check if wiki is enabled, if external then redirect
  26. func MustEnableWiki(ctx *context.Context) {
  27. if !ctx.Repo.Repository.UnitEnabled(models.UnitTypeWiki) &&
  28. !ctx.Repo.Repository.UnitEnabled(models.UnitTypeExternalWiki) {
  29. ctx.Handle(404, "MustEnableWiki", nil)
  30. return
  31. }
  32. unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalWiki)
  33. if err == nil {
  34. ctx.Redirect(unit.ExternalWikiConfig().ExternalWikiURL)
  35. return
  36. }
  37. }
  38. // PageMeta wiki page meat information
  39. type PageMeta struct {
  40. Name string
  41. SubURL string
  42. UpdatedUnix util.TimeStamp
  43. }
  44. // findEntryForFile finds the tree entry for a target filepath.
  45. func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
  46. entries, err := commit.ListEntries()
  47. if err != nil {
  48. return nil, err
  49. }
  50. for _, entry := range entries {
  51. if entry.Type == git.ObjectBlob && entry.Name() == target {
  52. return entry, nil
  53. }
  54. }
  55. return nil, nil
  56. }
  57. func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
  58. wikiRepo, err := git.OpenRepository(ctx.Repo.Repository.WikiPath())
  59. if err != nil {
  60. ctx.Handle(500, "OpenRepository", err)
  61. return nil, nil, err
  62. }
  63. commit, err := wikiRepo.GetBranchCommit("master")
  64. if err != nil {
  65. ctx.Handle(500, "GetBranchCommit", err)
  66. return wikiRepo, nil, err
  67. }
  68. return wikiRepo, commit, nil
  69. }
  70. // wikiContentsByEntry returns the contents of the wiki page referenced by the
  71. // given tree entry. Writes to ctx if an error occurs.
  72. func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
  73. reader, err := entry.Blob().Data()
  74. if err != nil {
  75. ctx.Handle(500, "Blob.Data", err)
  76. return nil
  77. }
  78. content, err := ioutil.ReadAll(reader)
  79. if err != nil {
  80. ctx.Handle(500, "ReadAll", err)
  81. return nil
  82. }
  83. return content
  84. }
  85. // wikiContentsByName returns the contents of a wiki page, along with a boolean
  86. // indicating whether the page exists. Writes to ctx if an error occurs.
  87. func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, bool) {
  88. entry, err := findEntryForFile(commit, models.WikiNameToFilename(wikiName))
  89. if err != nil {
  90. ctx.Handle(500, "findEntryForFile", err)
  91. return nil, false
  92. } else if entry == nil {
  93. return nil, false
  94. }
  95. return wikiContentsByEntry(ctx, entry), true
  96. }
  97. func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *git.TreeEntry) {
  98. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  99. if err != nil {
  100. return nil, nil
  101. }
  102. // Get page list.
  103. if isViewPage {
  104. entries, err := commit.ListEntries()
  105. if err != nil {
  106. ctx.Handle(500, "ListEntries", err)
  107. return nil, nil
  108. }
  109. pages := make([]PageMeta, 0, len(entries))
  110. for _, entry := range entries {
  111. if entry.Type != git.ObjectBlob {
  112. continue
  113. }
  114. wikiName, err := models.WikiFilenameToName(entry.Name())
  115. if err != nil {
  116. ctx.Handle(500, "WikiFilenameToName", err)
  117. return nil, nil
  118. } else if wikiName == "_Sidebar" || wikiName == "_Footer" {
  119. continue
  120. }
  121. pages = append(pages, PageMeta{
  122. Name: wikiName,
  123. SubURL: models.WikiNameToSubURL(wikiName),
  124. })
  125. }
  126. ctx.Data["Pages"] = pages
  127. }
  128. pageName := models.NormalizeWikiName(ctx.Params(":page"))
  129. if len(pageName) == 0 {
  130. pageName = "Home"
  131. }
  132. ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
  133. ctx.Data["old_title"] = pageName
  134. ctx.Data["Title"] = pageName
  135. ctx.Data["title"] = pageName
  136. ctx.Data["RequireHighlightJS"] = true
  137. pageFilename := models.WikiNameToFilename(pageName)
  138. var entry *git.TreeEntry
  139. if entry, err = findEntryForFile(commit, pageFilename); err != nil {
  140. ctx.Handle(500, "findEntryForFile", err)
  141. return nil, nil
  142. } else if entry == nil {
  143. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages")
  144. return nil, nil
  145. }
  146. data := wikiContentsByEntry(ctx, entry)
  147. if ctx.Written() {
  148. return nil, nil
  149. }
  150. if isViewPage {
  151. sidebarContent, sidebarPresent := wikiContentsByName(ctx, commit, "_Sidebar")
  152. if ctx.Written() {
  153. return nil, nil
  154. }
  155. footerContent, footerPresent := wikiContentsByName(ctx, commit, "_Footer")
  156. if ctx.Written() {
  157. return nil, nil
  158. }
  159. metas := ctx.Repo.Repository.ComposeMetas()
  160. ctx.Data["content"] = markdown.RenderWiki(data, ctx.Repo.RepoLink, metas)
  161. ctx.Data["sidebarPresent"] = sidebarPresent
  162. ctx.Data["sidebarContent"] = markdown.RenderWiki(sidebarContent, ctx.Repo.RepoLink, metas)
  163. ctx.Data["footerPresent"] = footerPresent
  164. ctx.Data["footerContent"] = markdown.RenderWiki(footerContent, ctx.Repo.RepoLink, metas)
  165. } else {
  166. ctx.Data["content"] = string(data)
  167. ctx.Data["sidebarPresent"] = false
  168. ctx.Data["sidebarContent"] = ""
  169. ctx.Data["footerPresent"] = false
  170. ctx.Data["footerContent"] = ""
  171. }
  172. return wikiRepo, entry
  173. }
  174. // Wiki renders single wiki page
  175. func Wiki(ctx *context.Context) {
  176. ctx.Data["PageIsWiki"] = true
  177. if !ctx.Repo.Repository.HasWiki() {
  178. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  179. ctx.HTML(200, tplWikiStart)
  180. return
  181. }
  182. wikiRepo, entry := renderWikiPage(ctx, true)
  183. if ctx.Written() {
  184. return
  185. }
  186. if entry == nil {
  187. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  188. ctx.HTML(200, tplWikiStart)
  189. return
  190. }
  191. wikiPath := entry.Name()
  192. if markup.Type(wikiPath) != markdown.MarkupName {
  193. ext := strings.ToUpper(filepath.Ext(wikiPath))
  194. ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext)
  195. }
  196. // Get last change information.
  197. lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
  198. if err != nil {
  199. ctx.Handle(500, "GetCommitByPath", err)
  200. return
  201. }
  202. ctx.Data["Author"] = lastCommit.Author
  203. ctx.HTML(200, tplWikiView)
  204. }
  205. // WikiPages render wiki pages list page
  206. func WikiPages(ctx *context.Context) {
  207. ctx.Data["Title"] = ctx.Tr("repo.wiki.pages")
  208. ctx.Data["PageIsWiki"] = true
  209. if !ctx.Repo.Repository.HasWiki() {
  210. ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
  211. return
  212. }
  213. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  214. if err != nil {
  215. return
  216. }
  217. entries, err := commit.ListEntries()
  218. if err != nil {
  219. ctx.Handle(500, "ListEntries", err)
  220. return
  221. }
  222. pages := make([]PageMeta, 0, len(entries))
  223. for _, entry := range entries {
  224. if entry.Type != git.ObjectBlob {
  225. continue
  226. }
  227. c, err := wikiRepo.GetCommitByPath(entry.Name())
  228. if err != nil {
  229. ctx.Handle(500, "GetCommit", err)
  230. return
  231. }
  232. wikiName, err := models.WikiFilenameToName(entry.Name())
  233. if err != nil {
  234. ctx.Handle(500, "WikiFilenameToName", err)
  235. return
  236. }
  237. pages = append(pages, PageMeta{
  238. Name: wikiName,
  239. SubURL: models.WikiNameToSubURL(wikiName),
  240. UpdatedUnix: util.TimeStamp(c.Author.When.Unix()),
  241. })
  242. }
  243. ctx.Data["Pages"] = pages
  244. ctx.HTML(200, tplWikiPages)
  245. }
  246. // WikiRaw outputs raw blob requested by user (image for example)
  247. func WikiRaw(ctx *context.Context) {
  248. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  249. if err != nil {
  250. if wikiRepo != nil {
  251. return
  252. }
  253. }
  254. providedPath := ctx.Params("*")
  255. if strings.HasSuffix(providedPath, ".md") {
  256. providedPath = providedPath[:len(providedPath)-3]
  257. }
  258. wikiPath := models.WikiNameToFilename(providedPath)
  259. var entry *git.TreeEntry
  260. if commit != nil {
  261. entry, err = findEntryForFile(commit, wikiPath)
  262. }
  263. if err != nil {
  264. ctx.Handle(500, "findFile", err)
  265. return
  266. } else if entry == nil {
  267. ctx.Handle(404, "findEntryForFile", nil)
  268. return
  269. }
  270. if err = ServeBlob(ctx, entry.Blob()); err != nil {
  271. ctx.Handle(500, "ServeBlob", err)
  272. }
  273. }
  274. // NewWiki render wiki create page
  275. func NewWiki(ctx *context.Context) {
  276. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  277. ctx.Data["PageIsWiki"] = true
  278. ctx.Data["RequireSimpleMDE"] = true
  279. if !ctx.Repo.Repository.HasWiki() {
  280. ctx.Data["title"] = "Home"
  281. }
  282. ctx.HTML(200, tplWikiNew)
  283. }
  284. // NewWikiPost response for wiki create request
  285. func NewWikiPost(ctx *context.Context, form auth.NewWikiForm) {
  286. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  287. ctx.Data["PageIsWiki"] = true
  288. ctx.Data["RequireSimpleMDE"] = true
  289. if ctx.HasError() {
  290. ctx.HTML(200, tplWikiNew)
  291. return
  292. }
  293. wikiName := models.NormalizeWikiName(form.Title)
  294. if err := ctx.Repo.Repository.AddWikiPage(ctx.User, wikiName, form.Content, form.Message); err != nil {
  295. if models.IsErrWikiReservedName(err) {
  296. ctx.Data["Err_Title"] = true
  297. ctx.RenderWithErr(ctx.Tr("repo.wiki.reserved_page", wikiName), tplWikiNew, &form)
  298. } else if models.IsErrWikiAlreadyExist(err) {
  299. ctx.Data["Err_Title"] = true
  300. ctx.RenderWithErr(ctx.Tr("repo.wiki.page_already_exists"), tplWikiNew, &form)
  301. } else {
  302. ctx.Handle(500, "AddWikiPage", err)
  303. }
  304. return
  305. }
  306. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToFilename(wikiName))
  307. }
  308. // EditWiki render wiki modify page
  309. func EditWiki(ctx *context.Context) {
  310. ctx.Data["PageIsWiki"] = true
  311. ctx.Data["PageIsWikiEdit"] = true
  312. ctx.Data["RequireSimpleMDE"] = true
  313. if !ctx.Repo.Repository.HasWiki() {
  314. ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
  315. return
  316. }
  317. renderWikiPage(ctx, false)
  318. if ctx.Written() {
  319. return
  320. }
  321. ctx.HTML(200, tplWikiNew)
  322. }
  323. // EditWikiPost response for wiki modify request
  324. func EditWikiPost(ctx *context.Context, form auth.NewWikiForm) {
  325. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  326. ctx.Data["PageIsWiki"] = true
  327. ctx.Data["RequireSimpleMDE"] = true
  328. if ctx.HasError() {
  329. ctx.HTML(200, tplWikiNew)
  330. return
  331. }
  332. oldWikiName := models.NormalizeWikiName(ctx.Params(":page"))
  333. newWikiName := models.NormalizeWikiName(form.Title)
  334. if err := ctx.Repo.Repository.EditWikiPage(ctx.User, oldWikiName, newWikiName, form.Content, form.Message); err != nil {
  335. ctx.Handle(500, "EditWikiPage", err)
  336. return
  337. }
  338. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToFilename(newWikiName))
  339. }
  340. // DeleteWikiPagePost delete wiki page
  341. func DeleteWikiPagePost(ctx *context.Context) {
  342. wikiName := models.NormalizeWikiName(ctx.Params(":page"))
  343. if len(wikiName) == 0 {
  344. wikiName = "Home"
  345. }
  346. if err := ctx.Repo.Repository.DeleteWikiPage(ctx.User, wikiName); err != nil {
  347. ctx.Handle(500, "DeleteWikiPage", err)
  348. return
  349. }
  350. ctx.JSON(200, map[string]interface{}{
  351. "redirect": ctx.Repo.RepoLink + "/wiki/",
  352. })
  353. }