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