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
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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.NotFound("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.ServerError("OpenRepository", err)
  61. return nil, nil, err
  62. }
  63. commit, err := wikiRepo.GetBranchCommit("master")
  64. if err != nil {
  65. ctx.ServerError("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.ServerError("Blob.Data", err)
  76. return nil
  77. }
  78. content, err := ioutil.ReadAll(reader)
  79. if err != nil {
  80. ctx.ServerError("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.ServerError("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.ServerError("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. if models.IsErrWikiInvalidFileName(err) {
  117. continue
  118. }
  119. ctx.ServerError("WikiFilenameToName", err)
  120. return nil, nil
  121. } else if wikiName == "_Sidebar" || wikiName == "_Footer" {
  122. continue
  123. }
  124. pages = append(pages, PageMeta{
  125. Name: wikiName,
  126. SubURL: models.WikiNameToSubURL(wikiName),
  127. })
  128. }
  129. ctx.Data["Pages"] = pages
  130. }
  131. pageName := models.NormalizeWikiName(ctx.Params(":page"))
  132. if len(pageName) == 0 {
  133. pageName = "Home"
  134. }
  135. ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
  136. ctx.Data["old_title"] = pageName
  137. ctx.Data["Title"] = pageName
  138. ctx.Data["title"] = pageName
  139. ctx.Data["RequireHighlightJS"] = true
  140. pageFilename := models.WikiNameToFilename(pageName)
  141. var entry *git.TreeEntry
  142. if entry, err = findEntryForFile(commit, pageFilename); err != nil {
  143. ctx.ServerError("findEntryForFile", err)
  144. return nil, nil
  145. } else if entry == nil {
  146. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages")
  147. return nil, nil
  148. }
  149. data := wikiContentsByEntry(ctx, entry)
  150. if ctx.Written() {
  151. return nil, nil
  152. }
  153. if isViewPage {
  154. sidebarContent, sidebarPresent := wikiContentsByName(ctx, commit, "_Sidebar")
  155. if ctx.Written() {
  156. return nil, nil
  157. }
  158. footerContent, footerPresent := wikiContentsByName(ctx, commit, "_Footer")
  159. if ctx.Written() {
  160. return nil, nil
  161. }
  162. metas := ctx.Repo.Repository.ComposeMetas()
  163. ctx.Data["content"] = markdown.RenderWiki(data, ctx.Repo.RepoLink, metas)
  164. ctx.Data["sidebarPresent"] = sidebarPresent
  165. ctx.Data["sidebarContent"] = markdown.RenderWiki(sidebarContent, ctx.Repo.RepoLink, metas)
  166. ctx.Data["footerPresent"] = footerPresent
  167. ctx.Data["footerContent"] = markdown.RenderWiki(footerContent, ctx.Repo.RepoLink, metas)
  168. } else {
  169. ctx.Data["content"] = string(data)
  170. ctx.Data["sidebarPresent"] = false
  171. ctx.Data["sidebarContent"] = ""
  172. ctx.Data["footerPresent"] = false
  173. ctx.Data["footerContent"] = ""
  174. }
  175. return wikiRepo, entry
  176. }
  177. // Wiki renders single wiki page
  178. func Wiki(ctx *context.Context) {
  179. ctx.Data["PageIsWiki"] = true
  180. if !ctx.Repo.Repository.HasWiki() {
  181. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  182. ctx.HTML(200, tplWikiStart)
  183. return
  184. }
  185. wikiRepo, entry := renderWikiPage(ctx, true)
  186. if ctx.Written() {
  187. return
  188. }
  189. if entry == nil {
  190. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  191. ctx.HTML(200, tplWikiStart)
  192. return
  193. }
  194. wikiPath := entry.Name()
  195. if markup.Type(wikiPath) != markdown.MarkupName {
  196. ext := strings.ToUpper(filepath.Ext(wikiPath))
  197. ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext)
  198. }
  199. // Get last change information.
  200. lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
  201. if err != nil {
  202. ctx.ServerError("GetCommitByPath", err)
  203. return
  204. }
  205. ctx.Data["Author"] = lastCommit.Author
  206. ctx.HTML(200, tplWikiView)
  207. }
  208. // WikiPages render wiki pages list page
  209. func WikiPages(ctx *context.Context) {
  210. ctx.Data["Title"] = ctx.Tr("repo.wiki.pages")
  211. ctx.Data["PageIsWiki"] = true
  212. if !ctx.Repo.Repository.HasWiki() {
  213. ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
  214. return
  215. }
  216. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  217. if err != nil {
  218. return
  219. }
  220. entries, err := commit.ListEntries()
  221. if err != nil {
  222. ctx.ServerError("ListEntries", err)
  223. return
  224. }
  225. pages := make([]PageMeta, 0, len(entries))
  226. for _, entry := range entries {
  227. if entry.Type != git.ObjectBlob {
  228. continue
  229. }
  230. c, err := wikiRepo.GetCommitByPath(entry.Name())
  231. if err != nil {
  232. ctx.ServerError("GetCommit", err)
  233. return
  234. }
  235. wikiName, err := models.WikiFilenameToName(entry.Name())
  236. if err != nil {
  237. if models.IsErrWikiInvalidFileName(err) {
  238. continue
  239. }
  240. ctx.ServerError("WikiFilenameToName", err)
  241. return
  242. }
  243. pages = append(pages, PageMeta{
  244. Name: wikiName,
  245. SubURL: models.WikiNameToSubURL(wikiName),
  246. UpdatedUnix: util.TimeStamp(c.Author.When.Unix()),
  247. })
  248. }
  249. ctx.Data["Pages"] = pages
  250. ctx.HTML(200, tplWikiPages)
  251. }
  252. // WikiRaw outputs raw blob requested by user (image for example)
  253. func WikiRaw(ctx *context.Context) {
  254. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  255. if err != nil {
  256. if wikiRepo != nil {
  257. return
  258. }
  259. }
  260. providedPath := ctx.Params("*")
  261. if strings.HasSuffix(providedPath, ".md") {
  262. providedPath = providedPath[:len(providedPath)-3]
  263. }
  264. wikiPath := models.WikiNameToFilename(providedPath)
  265. var entry *git.TreeEntry
  266. if commit != nil {
  267. entry, err = findEntryForFile(commit, wikiPath)
  268. }
  269. if err != nil {
  270. ctx.ServerError("findFile", err)
  271. return
  272. } else if entry == nil {
  273. ctx.NotFound("findEntryForFile", nil)
  274. return
  275. }
  276. if err = ServeBlob(ctx, entry.Blob()); err != nil {
  277. ctx.ServerError("ServeBlob", err)
  278. }
  279. }
  280. // NewWiki render wiki create page
  281. func NewWiki(ctx *context.Context) {
  282. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  283. ctx.Data["PageIsWiki"] = true
  284. ctx.Data["RequireSimpleMDE"] = true
  285. if !ctx.Repo.Repository.HasWiki() {
  286. ctx.Data["title"] = "Home"
  287. }
  288. ctx.HTML(200, tplWikiNew)
  289. }
  290. // NewWikiPost response for wiki create request
  291. func NewWikiPost(ctx *context.Context, form auth.NewWikiForm) {
  292. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  293. ctx.Data["PageIsWiki"] = true
  294. ctx.Data["RequireSimpleMDE"] = true
  295. if ctx.HasError() {
  296. ctx.HTML(200, tplWikiNew)
  297. return
  298. }
  299. wikiName := models.NormalizeWikiName(form.Title)
  300. if err := ctx.Repo.Repository.AddWikiPage(ctx.User, wikiName, form.Content, form.Message); err != nil {
  301. if models.IsErrWikiReservedName(err) {
  302. ctx.Data["Err_Title"] = true
  303. ctx.RenderWithErr(ctx.Tr("repo.wiki.reserved_page", wikiName), tplWikiNew, &form)
  304. } else if models.IsErrWikiAlreadyExist(err) {
  305. ctx.Data["Err_Title"] = true
  306. ctx.RenderWithErr(ctx.Tr("repo.wiki.page_already_exists"), tplWikiNew, &form)
  307. } else {
  308. ctx.ServerError("AddWikiPage", err)
  309. }
  310. return
  311. }
  312. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(wikiName))
  313. }
  314. // EditWiki render wiki modify page
  315. func EditWiki(ctx *context.Context) {
  316. ctx.Data["PageIsWiki"] = true
  317. ctx.Data["PageIsWikiEdit"] = true
  318. ctx.Data["RequireSimpleMDE"] = true
  319. if !ctx.Repo.Repository.HasWiki() {
  320. ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
  321. return
  322. }
  323. renderWikiPage(ctx, false)
  324. if ctx.Written() {
  325. return
  326. }
  327. ctx.HTML(200, tplWikiNew)
  328. }
  329. // EditWikiPost response for wiki modify request
  330. func EditWikiPost(ctx *context.Context, form auth.NewWikiForm) {
  331. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  332. ctx.Data["PageIsWiki"] = true
  333. ctx.Data["RequireSimpleMDE"] = true
  334. if ctx.HasError() {
  335. ctx.HTML(200, tplWikiNew)
  336. return
  337. }
  338. oldWikiName := models.NormalizeWikiName(ctx.Params(":page"))
  339. newWikiName := models.NormalizeWikiName(form.Title)
  340. if err := ctx.Repo.Repository.EditWikiPage(ctx.User, oldWikiName, newWikiName, form.Content, form.Message); err != nil {
  341. ctx.ServerError("EditWikiPage", err)
  342. return
  343. }
  344. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(newWikiName))
  345. }
  346. // DeleteWikiPagePost delete wiki page
  347. func DeleteWikiPagePost(ctx *context.Context) {
  348. wikiName := models.NormalizeWikiName(ctx.Params(":page"))
  349. if len(wikiName) == 0 {
  350. wikiName = "Home"
  351. }
  352. if err := ctx.Repo.Repository.DeleteWikiPage(ctx.User, wikiName); err != nil {
  353. ctx.ServerError("DeleteWikiPage", err)
  354. return
  355. }
  356. ctx.JSON(200, map[string]interface{}{
  357. "redirect": ctx.Repo.RepoLink + "/wiki/",
  358. })
  359. }