module_blog.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. package modules
  2. import (
  3. "html"
  4. "net/http"
  5. "strings"
  6. "golang-fave/assets"
  7. "golang-fave/consts"
  8. "golang-fave/engine/builder"
  9. "golang-fave/engine/fetdata"
  10. "golang-fave/engine/sqlw"
  11. "golang-fave/engine/wrapper"
  12. "golang-fave/utils"
  13. )
  14. func (this *Modules) RegisterModule_Blog() *Module {
  15. return this.newModule(MInfo{
  16. WantDB: true,
  17. Mount: "blog",
  18. Name: "Blog",
  19. Order: 1,
  20. System: false,
  21. Icon: assets.SysSvgIconList,
  22. Sub: &[]MISub{
  23. {Mount: "default", Name: "List of posts", Show: true, Icon: assets.SysSvgIconList},
  24. {Mount: "add", Name: "Add new post", Show: true, Icon: assets.SysSvgIconPlus},
  25. {Mount: "modify", Name: "Modify post", Show: false},
  26. {Sep: true, Show: true},
  27. {Mount: "categories", Name: "List of categories", Show: true, Icon: assets.SysSvgIconList},
  28. {Mount: "categories-add", Name: "Add new category", Show: true, Icon: assets.SysSvgIconPlus},
  29. {Mount: "categories-modify", Name: "Modify category", Show: false},
  30. },
  31. }, func(wrap *wrapper.Wrapper) {
  32. if len(wrap.UrlArgs) == 3 && wrap.UrlArgs[0] == "blog" && wrap.UrlArgs[1] == "category" && wrap.UrlArgs[2] != "" {
  33. // Blog category
  34. row := &utils.MySql_blog_category{}
  35. err := wrap.DB.QueryRow(`
  36. SELECT
  37. id,
  38. user,
  39. name,
  40. alias,
  41. lft,
  42. rgt
  43. FROM
  44. blog_cats
  45. WHERE
  46. alias = ? AND
  47. id > 1
  48. LIMIT 1;`,
  49. wrap.UrlArgs[2],
  50. ).Scan(
  51. &row.A_id,
  52. &row.A_user,
  53. &row.A_name,
  54. &row.A_alias,
  55. &row.A_lft,
  56. &row.A_rgt,
  57. )
  58. if err != nil && err != wrapper.ErrNoRows {
  59. // System error 500
  60. utils.SystemErrorPageEngine(wrap.W, err)
  61. return
  62. } else if err == wrapper.ErrNoRows {
  63. // User error 404 page
  64. wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
  65. return
  66. }
  67. // Fix url
  68. if wrap.R.URL.Path[len(wrap.R.URL.Path)-1] != '/' {
  69. http.Redirect(wrap.W, wrap.R, wrap.R.URL.Path+"/"+utils.ExtractGetParams(wrap.R.RequestURI), 301)
  70. return
  71. }
  72. // Render template
  73. wrap.RenderFrontEnd("blog-category", fetdata.New(wrap, row, false), http.StatusOK)
  74. return
  75. } else if len(wrap.UrlArgs) == 2 && wrap.UrlArgs[0] == "blog" && wrap.UrlArgs[1] != "" {
  76. // Blog post
  77. row := &utils.MySql_blog_post{}
  78. err := wrap.DB.QueryRow(`
  79. SELECT
  80. id,
  81. user,
  82. name,
  83. alias,
  84. content,
  85. UNIX_TIMESTAMP(datetime) as datetime,
  86. active
  87. FROM
  88. blog_posts
  89. WHERE
  90. active = 1 and
  91. alias = ?
  92. LIMIT 1;`,
  93. wrap.UrlArgs[1],
  94. ).Scan(
  95. &row.A_id,
  96. &row.A_user,
  97. &row.A_name,
  98. &row.A_alias,
  99. &row.A_content,
  100. &row.A_datetime,
  101. &row.A_active,
  102. )
  103. if err != nil && err != wrapper.ErrNoRows {
  104. // System error 500
  105. utils.SystemErrorPageEngine(wrap.W, err)
  106. return
  107. } else if err == wrapper.ErrNoRows {
  108. // User error 404 page
  109. wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
  110. return
  111. }
  112. // Fix url
  113. if wrap.R.URL.Path[len(wrap.R.URL.Path)-1] != '/' {
  114. http.Redirect(wrap.W, wrap.R, wrap.R.URL.Path+"/"+utils.ExtractGetParams(wrap.R.RequestURI), 301)
  115. return
  116. }
  117. // Render template
  118. wrap.RenderFrontEnd("blog-post", fetdata.New(wrap, row, false), http.StatusOK)
  119. return
  120. } else if len(wrap.UrlArgs) == 1 && wrap.UrlArgs[0] == "blog" {
  121. // Blog
  122. // Fix url
  123. if wrap.R.URL.Path[len(wrap.R.URL.Path)-1] != '/' {
  124. http.Redirect(wrap.W, wrap.R, wrap.R.URL.Path+"/"+utils.ExtractGetParams(wrap.R.RequestURI), 301)
  125. return
  126. }
  127. // Render template
  128. wrap.RenderFrontEnd("blog", fetdata.New(wrap, nil, false), http.StatusOK)
  129. return
  130. }
  131. // User error 404 page
  132. wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
  133. }, func(wrap *wrapper.Wrapper) (string, string, string) {
  134. content := ""
  135. sidebar := ""
  136. if wrap.CurrSubModule == "" || wrap.CurrSubModule == "default" {
  137. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  138. {Name: "List of posts"},
  139. })
  140. content += builder.DataTable(
  141. wrap,
  142. "blog_posts",
  143. "id",
  144. "DESC",
  145. &[]builder.DataTableRow{
  146. {
  147. DBField: "id",
  148. },
  149. {
  150. DBField: "name",
  151. NameInTable: "Post / URL",
  152. CallBack: func(values *[]string) string {
  153. name := `<a href="/cp/` + wrap.CurrModule + `/modify/` + (*values)[0] + `/">` + html.EscapeString((*values)[1]) + `</a>`
  154. alias := html.EscapeString((*values)[2])
  155. return `<div>` + name + `</div><div><small>/blog/` + alias + `/</small></div>`
  156. },
  157. },
  158. {
  159. DBField: "alias",
  160. },
  161. {
  162. DBField: "datetime",
  163. DBExp: "UNIX_TIMESTAMP(`datetime`)",
  164. NameInTable: "Date / Time",
  165. Classes: "d-none d-md-table-cell",
  166. CallBack: func(values *[]string) string {
  167. t := int64(utils.StrToInt((*values)[3]))
  168. return `<div>` + utils.UnixTimestampToFormat(t, "02.01.2006") + `</div>` +
  169. `<div><small>` + utils.UnixTimestampToFormat(t, "15:04:05") + `</small></div>`
  170. },
  171. },
  172. {
  173. DBField: "active",
  174. NameInTable: "Active",
  175. Classes: "d-none d-sm-table-cell",
  176. CallBack: func(values *[]string) string {
  177. return builder.CheckBox(utils.StrToInt((*values)[4]))
  178. },
  179. },
  180. },
  181. func(values *[]string) string {
  182. return builder.DataTableAction(&[]builder.DataTableActionRow{
  183. {
  184. Icon: assets.SysSvgIconView,
  185. Href: `/blog/` + (*values)[2] + `/`,
  186. Hint: "View",
  187. Target: "_blank",
  188. },
  189. {
  190. Icon: assets.SysSvgIconEdit,
  191. Href: "/cp/" + wrap.CurrModule + "/modify/" + (*values)[0] + "/",
  192. Hint: "Edit",
  193. },
  194. {
  195. Icon: assets.SysSvgIconRemove,
  196. Href: "javascript:fave.ActionDataTableDelete(this,'blog-delete','" +
  197. (*values)[0] + "','Are you sure want to delete post?');",
  198. Hint: "Delete",
  199. Classes: "delete",
  200. },
  201. })
  202. },
  203. "/cp/"+wrap.CurrModule+"/",
  204. nil,
  205. nil,
  206. true,
  207. )
  208. } else if wrap.CurrSubModule == "categories" {
  209. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  210. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/" + wrap.CurrSubModule + "/"},
  211. {Name: "List of categories"},
  212. })
  213. content += builder.DataTable(
  214. wrap,
  215. "blog_cats",
  216. "id",
  217. "ASC",
  218. &[]builder.DataTableRow{
  219. {
  220. DBField: "id",
  221. },
  222. {
  223. DBField: "user",
  224. },
  225. {
  226. DBField: "name",
  227. NameInTable: "Category",
  228. CallBack: func(values *[]string) string {
  229. depth := utils.StrToInt((*values)[4]) - 1
  230. if depth < 0 {
  231. depth = 0
  232. }
  233. sub := strings.Repeat("&mdash; ", depth)
  234. name := `<a href="/cp/` + wrap.CurrModule + `/categories-modify/` + (*values)[0] + `/">` + sub + html.EscapeString((*values)[2]) + `</a>`
  235. return `<div>` + name + `</div>`
  236. },
  237. },
  238. {
  239. DBField: "alias",
  240. },
  241. {
  242. DBField: "depth",
  243. },
  244. },
  245. func(values *[]string) string {
  246. return builder.DataTableAction(&[]builder.DataTableActionRow{
  247. {
  248. Icon: assets.SysSvgIconView,
  249. Href: `/blog/category/` + (*values)[3] + `/`,
  250. Hint: "View",
  251. Target: "_blank",
  252. },
  253. {
  254. Icon: assets.SysSvgIconEdit,
  255. Href: "/cp/" + wrap.CurrModule + "/categories-modify/" + (*values)[0] + "/",
  256. Hint: "Edit",
  257. },
  258. {
  259. Icon: assets.SysSvgIconRemove,
  260. Href: "javascript:fave.ActionDataTableDelete(this,'blog-categories-delete','" +
  261. (*values)[0] + "','Are you sure want to delete category?');",
  262. Hint: "Delete",
  263. Classes: "delete",
  264. },
  265. })
  266. },
  267. "/cp/"+wrap.CurrModule+"/"+wrap.CurrSubModule+"/",
  268. nil,
  269. func(limit_offset int, pear_page int) (*sqlw.Rows, error) {
  270. return wrap.DB.Query(
  271. `SELECT
  272. node.id,
  273. node.user,
  274. node.name,
  275. node.alias,
  276. (COUNT(parent.id) - 1) AS depth
  277. FROM
  278. blog_cats AS node,
  279. blog_cats AS parent
  280. WHERE
  281. node.lft BETWEEN parent.lft AND parent.rgt AND
  282. node.id > 1
  283. GROUP BY
  284. node.id
  285. ORDER BY
  286. node.lft ASC
  287. ;`,
  288. )
  289. },
  290. false,
  291. )
  292. } else if wrap.CurrSubModule == "add" || wrap.CurrSubModule == "modify" {
  293. if wrap.CurrSubModule == "add" {
  294. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  295. {Name: "Add new post"},
  296. })
  297. } else {
  298. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  299. {Name: "Modify post"},
  300. })
  301. }
  302. data := utils.MySql_blog_post{
  303. A_id: 0,
  304. A_user: 0,
  305. A_name: "",
  306. A_alias: "",
  307. A_content: "",
  308. A_datetime: 0,
  309. A_active: 0,
  310. }
  311. if wrap.CurrSubModule == "modify" {
  312. if len(wrap.UrlArgs) != 3 {
  313. return "", "", ""
  314. }
  315. if !utils.IsNumeric(wrap.UrlArgs[2]) {
  316. return "", "", ""
  317. }
  318. err := wrap.DB.QueryRow(`
  319. SELECT
  320. id,
  321. user,
  322. name,
  323. alias,
  324. briefly,
  325. content,
  326. active
  327. FROM
  328. blog_posts
  329. WHERE
  330. id = ?
  331. LIMIT 1;`,
  332. utils.StrToInt(wrap.UrlArgs[2]),
  333. ).Scan(
  334. &data.A_id,
  335. &data.A_user,
  336. &data.A_name,
  337. &data.A_alias,
  338. &data.A_briefly,
  339. &data.A_content,
  340. &data.A_active,
  341. )
  342. if err != nil {
  343. return "", "", ""
  344. }
  345. }
  346. // All post current categories
  347. var selids []int
  348. if data.A_id > 0 {
  349. rows, err := wrap.DB.Query("SELECT category_id FROM blog_cat_post_rel WHERE post_id = ?;", data.A_id)
  350. if err == nil {
  351. defer rows.Close()
  352. values := make([]int, 1)
  353. scan := make([]interface{}, len(values))
  354. for i := range values {
  355. scan[i] = &values[i]
  356. }
  357. for rows.Next() {
  358. err = rows.Scan(scan...)
  359. if err == nil {
  360. selids = append(selids, int(values[0]))
  361. }
  362. }
  363. }
  364. }
  365. btn_caption := "Add"
  366. if wrap.CurrSubModule == "modify" {
  367. btn_caption = "Save"
  368. }
  369. content += builder.DataForm(wrap, []builder.DataFormField{
  370. {
  371. Kind: builder.DFKHidden,
  372. Name: "action",
  373. Value: "blog-modify",
  374. },
  375. {
  376. Kind: builder.DFKHidden,
  377. Name: "id",
  378. Value: utils.IntToStr(data.A_id),
  379. },
  380. {
  381. Kind: builder.DFKText,
  382. Caption: "Post name",
  383. Name: "name",
  384. Value: data.A_name,
  385. },
  386. {
  387. Kind: builder.DFKText,
  388. Caption: "Post alias",
  389. Name: "alias",
  390. Value: data.A_alias,
  391. Hint: "Example: our-news",
  392. },
  393. {
  394. Kind: builder.DFKText,
  395. Caption: "Categories",
  396. Name: "cats",
  397. Value: "0",
  398. CallBack: func(field *builder.DataFormField) string {
  399. return `<div class="form-group n4">` +
  400. `<div class="row">` +
  401. `<div class="col-md-3">` +
  402. `<label for="lbl_parent">Categories</label>` +
  403. `</div>` +
  404. `<div class="col-md-9">` +
  405. `<div>` +
  406. `<select class="form-control" id="lbl_cats" name="cats[]" multiple>` +
  407. this.blog_GetCategorySelectOptions(wrap, 0, 0, selids) +
  408. `</select>` +
  409. `</div>` +
  410. `</div>` +
  411. `</div>` +
  412. `</div>`
  413. },
  414. },
  415. {
  416. Kind: builder.DFKTextArea,
  417. Caption: "Briefly",
  418. Name: "briefly",
  419. Value: data.A_briefly,
  420. Classes: "briefly autosize",
  421. },
  422. {
  423. Kind: builder.DFKTextArea,
  424. Caption: "Post content",
  425. Name: "content",
  426. Value: data.A_content,
  427. Classes: "autosize",
  428. },
  429. {
  430. Kind: builder.DFKCheckBox,
  431. Caption: "Active",
  432. Name: "active",
  433. Value: utils.IntToStr(data.A_active),
  434. },
  435. {
  436. Kind: builder.DFKMessage,
  437. },
  438. {
  439. Kind: builder.DFKSubmit,
  440. Value: btn_caption,
  441. Target: "add-edit-button",
  442. },
  443. })
  444. if wrap.CurrSubModule == "add" {
  445. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Add</button>`
  446. } else {
  447. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Save</button>`
  448. }
  449. } else if wrap.CurrSubModule == "categories-add" || wrap.CurrSubModule == "categories-modify" {
  450. if wrap.CurrSubModule == "categories-add" {
  451. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  452. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/categories/"},
  453. {Name: "Add new category"},
  454. })
  455. } else {
  456. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  457. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/categories/"},
  458. {Name: "Modify category"},
  459. })
  460. }
  461. data := utils.MySql_blog_category{
  462. A_id: 0,
  463. A_user: 0,
  464. A_name: "",
  465. A_alias: "",
  466. A_lft: 0,
  467. A_rgt: 0,
  468. }
  469. if wrap.CurrSubModule == "categories-modify" {
  470. if len(wrap.UrlArgs) != 3 {
  471. return "", "", ""
  472. }
  473. if !utils.IsNumeric(wrap.UrlArgs[2]) {
  474. return "", "", ""
  475. }
  476. err := wrap.DB.QueryRow(`
  477. SELECT
  478. id,
  479. user,
  480. name,
  481. alias,
  482. lft,
  483. rgt
  484. FROM
  485. blog_cats
  486. WHERE
  487. id = ?
  488. LIMIT 1;`,
  489. utils.StrToInt(wrap.UrlArgs[2]),
  490. ).Scan(
  491. &data.A_id,
  492. &data.A_user,
  493. &data.A_name,
  494. &data.A_alias,
  495. &data.A_lft,
  496. &data.A_rgt,
  497. )
  498. if err != nil {
  499. return "", "", ""
  500. }
  501. }
  502. btn_caption := "Add"
  503. if wrap.CurrSubModule == "categories-modify" {
  504. btn_caption = "Save"
  505. }
  506. parentId := 0
  507. if wrap.CurrSubModule == "categories-modify" {
  508. parentId = this.blog_GetCategoryParentId(wrap, data.A_id)
  509. }
  510. content += builder.DataForm(wrap, []builder.DataFormField{
  511. {
  512. Kind: builder.DFKHidden,
  513. Name: "action",
  514. Value: "blog-categories-modify",
  515. },
  516. {
  517. Kind: builder.DFKHidden,
  518. Name: "id",
  519. Value: utils.IntToStr(data.A_id),
  520. },
  521. {
  522. Kind: builder.DFKText,
  523. Caption: "Parent",
  524. Name: "parent",
  525. Value: "0",
  526. CallBack: func(field *builder.DataFormField) string {
  527. return `<div class="form-group n2">` +
  528. `<div class="row">` +
  529. `<div class="col-md-3">` +
  530. `<label for="lbl_parent">Parent</label>` +
  531. `</div>` +
  532. `<div class="col-md-9">` +
  533. `<div>` +
  534. `<select class="form-control" id="lbl_parent" name="parent">` +
  535. `<option value="0">&mdash;</option>` +
  536. this.blog_GetCategorySelectOptions(wrap, data.A_id, parentId, []int{}) +
  537. `</select>` +
  538. `</div>` +
  539. `</div>` +
  540. `</div>` +
  541. `</div>`
  542. },
  543. },
  544. {
  545. Kind: builder.DFKText,
  546. Caption: "Name",
  547. Name: "name",
  548. Value: data.A_name,
  549. },
  550. {
  551. Kind: builder.DFKText,
  552. Caption: "Alias",
  553. Name: "alias",
  554. Value: data.A_alias,
  555. Hint: "Example: popular-posts",
  556. },
  557. {
  558. Kind: builder.DFKMessage,
  559. },
  560. {
  561. Kind: builder.DFKSubmit,
  562. Value: btn_caption,
  563. Target: "add-edit-button",
  564. },
  565. })
  566. if wrap.CurrSubModule == "categories-add" {
  567. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Add</button>`
  568. } else {
  569. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Save</button>`
  570. }
  571. }
  572. return this.getSidebarModules(wrap), content, sidebar
  573. })
  574. }