module_blog.go 12 KB

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