builder.go 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984
  1. package pagebuilder
  2. import (
  3. "errors"
  4. "fmt"
  5. "html/template"
  6. "net/http"
  7. "net/url"
  8. "path"
  9. "reflect"
  10. "sort"
  11. "strconv"
  12. "strings"
  13. "github.com/qor5/admin/activity"
  14. "github.com/qor5/admin/l10n"
  15. l10n_view "github.com/qor5/admin/l10n/views"
  16. mediav "github.com/qor5/admin/media/views"
  17. "github.com/qor5/admin/note"
  18. "github.com/qor5/admin/presets"
  19. "github.com/qor5/admin/presets/actions"
  20. "github.com/qor5/admin/presets/gorm2op"
  21. "github.com/qor5/admin/publish"
  22. "github.com/qor5/admin/publish/views"
  23. pv "github.com/qor5/admin/publish/views"
  24. "github.com/qor5/admin/seo"
  25. "github.com/qor5/admin/utils"
  26. . "github.com/qor5/ui/vuetify"
  27. vx "github.com/qor5/ui/vuetifyx"
  28. "github.com/qor5/web"
  29. "github.com/qor5/x/i18n"
  30. "github.com/qor5/x/perm"
  31. "github.com/sunfmin/reflectutils"
  32. h "github.com/theplant/htmlgo"
  33. "goji.io/pat"
  34. "golang.org/x/text/language"
  35. "gorm.io/gorm"
  36. )
  37. type RenderInput struct {
  38. IsEditor bool
  39. IsReadonly bool
  40. Device string
  41. }
  42. type RenderFunc func(obj interface{}, input *RenderInput, ctx *web.EventContext) h.HTMLComponent
  43. type PageLayoutFunc func(body h.HTMLComponent, input *PageLayoutInput, ctx *web.EventContext) h.HTMLComponent
  44. type PageLayoutInput struct {
  45. Page *Page
  46. SeoTags template.HTML
  47. CanonicalLink template.HTML
  48. StructuredData template.HTML
  49. FreeStyleCss []string
  50. FreeStyleTopJs []string
  51. FreeStyleBottomJs []string
  52. Header h.HTMLComponent
  53. Footer h.HTMLComponent
  54. IsEditor bool
  55. EditorCss []h.HTMLComponent
  56. IsPreview bool
  57. }
  58. type Builder struct {
  59. prefix string
  60. wb *web.Builder
  61. db *gorm.DB
  62. containerBuilders []*ContainerBuilder
  63. ps *presets.Builder
  64. mb *presets.ModelBuilder
  65. pageStyle h.HTMLComponent
  66. pageLayoutFunc PageLayoutFunc
  67. preview http.Handler
  68. images http.Handler
  69. imagesPrefix string
  70. defaultDevice string
  71. publishBtnColor string
  72. duplicateBtnColor string
  73. templateEnabled bool
  74. }
  75. const (
  76. openTemplateDialogEvent = "openTemplateDialogEvent"
  77. selectTemplateEvent = "selectTemplateEvent"
  78. // clearTemplateEvent = "clearTemplateEvent"
  79. republishRelatedOnlinePagesEvent = "republish_related_online_pages"
  80. schedulePublishDialogEvent = "schedulePublishDialogEvent"
  81. schedulePublishEvent = "schedulePublishEvent"
  82. createNoteDialogEvent = "createNoteDialogEvent"
  83. createNoteEvent = "createNoteEvent"
  84. editSEODialogEvent = "editSEODialogEvent"
  85. updateSEOEvent = "updateSEOEvent"
  86. selectVersionEvent = "selectVersionEvent"
  87. renameVersionDialogEvent = "renameVersionDialogEvent"
  88. renameVersionEvent = "renameVersionEvent"
  89. deleteVersionDialogEvent = "deleteVersionDialogEvent"
  90. paramOpenFromSharedContainer = "open_from_shared_container"
  91. )
  92. func New(prefix string, db *gorm.DB, i18nB *i18n.Builder) *Builder {
  93. err := db.AutoMigrate(
  94. &Page{},
  95. &Template{},
  96. &Container{},
  97. &DemoContainer{},
  98. &Category{},
  99. )
  100. if err != nil {
  101. panic(err)
  102. }
  103. // https://github.com/go-gorm/sqlite/blob/64917553e84d5482e252c7a0c8f798fb672d7668/ddlmod.go#L16
  104. // fxxk: newline is not allowed
  105. err = db.Exec(`
  106. create unique index if not exists uidx_page_builder_demo_containers_model_name_locale_code on page_builder_demo_containers (model_name, locale_code) where deleted_at is null;
  107. `).Error
  108. if err != nil {
  109. panic(err)
  110. }
  111. r := &Builder{
  112. db: db,
  113. wb: web.New(),
  114. prefix: prefix,
  115. defaultDevice: DeviceComputer,
  116. publishBtnColor: "primary",
  117. duplicateBtnColor: "primary",
  118. templateEnabled: true,
  119. }
  120. r.ps = presets.New().
  121. BrandTitle("Page Builder").
  122. DataOperator(gorm2op.DataOperator(db)).
  123. URIPrefix(prefix).
  124. LayoutFunc(r.pageEditorLayout).
  125. SetI18n(i18nB)
  126. type Editor struct {
  127. }
  128. r.ps.Model(&Editor{}).
  129. Detailing().
  130. PageFunc(r.Editor)
  131. r.ps.GetWebBuilder().RegisterEventFunc(AddContainerDialogEvent, r.AddContainerDialog)
  132. r.ps.GetWebBuilder().RegisterEventFunc(AddContainerEvent, r.AddContainer)
  133. r.ps.GetWebBuilder().RegisterEventFunc(DeleteContainerConfirmationEvent, r.DeleteContainerConfirmation)
  134. r.ps.GetWebBuilder().RegisterEventFunc(DeleteContainerEvent, r.DeleteContainer)
  135. r.ps.GetWebBuilder().RegisterEventFunc(MoveContainerEvent, r.MoveContainer)
  136. r.ps.GetWebBuilder().RegisterEventFunc(ToggleContainerVisibilityEvent, r.ToggleContainerVisibility)
  137. r.ps.GetWebBuilder().RegisterEventFunc(MarkAsSharedContainerEvent, r.MarkAsSharedContainer)
  138. r.ps.GetWebBuilder().RegisterEventFunc(RenameContainerDialogEvent, r.RenameContainerDialog)
  139. r.ps.GetWebBuilder().RegisterEventFunc(RenameContainerEvent, r.RenameContainer)
  140. r.preview = r.ps.GetWebBuilder().Page(r.Preview)
  141. return r
  142. }
  143. func (b *Builder) Prefix(v string) (r *Builder) {
  144. b.ps.URIPrefix(v)
  145. b.prefix = v
  146. return b
  147. }
  148. func (b *Builder) PageStyle(v h.HTMLComponent) (r *Builder) {
  149. b.pageStyle = v
  150. return b
  151. }
  152. func (b *Builder) PageLayout(v PageLayoutFunc) (r *Builder) {
  153. b.pageLayoutFunc = v
  154. return b
  155. }
  156. func (b *Builder) Images(v http.Handler, imagesPrefix string) (r *Builder) {
  157. b.images = v
  158. b.imagesPrefix = imagesPrefix
  159. return b
  160. }
  161. func (b *Builder) DefaultDevice(v string) (r *Builder) {
  162. b.defaultDevice = v
  163. return b
  164. }
  165. func (b *Builder) GetPresetsBuilder() (r *presets.Builder) {
  166. return b.ps
  167. }
  168. func (b *Builder) PublishBtnColor(v string) (r *Builder) {
  169. b.publishBtnColor = v
  170. return b
  171. }
  172. func (b *Builder) DuplicateBtnColor(v string) (r *Builder) {
  173. b.duplicateBtnColor = v
  174. return b
  175. }
  176. func (b *Builder) TemplateEnabled(v bool) (r *Builder) {
  177. b.templateEnabled = v
  178. return b
  179. }
  180. func (b *Builder) Configure(pb *presets.Builder, db *gorm.DB, l10nB *l10n.Builder, activityB *activity.ActivityBuilder, publisher *publish.Builder, seoCollection *seo.Collection) (pm *presets.ModelBuilder) {
  181. pb.I18n().
  182. RegisterForModule(language.English, I18nPageBuilderKey, Messages_en_US).
  183. RegisterForModule(language.SimplifiedChinese, I18nPageBuilderKey, Messages_zh_CN).
  184. RegisterForModule(language.Japanese, I18nPageBuilderKey, Messages_ja_JP)
  185. pm = pb.Model(&Page{})
  186. templateM := presets.NewModelBuilder(pb, &Template{})
  187. if b.templateEnabled {
  188. templateM = b.ConfigTemplate(pb, db)
  189. }
  190. b.mb = pm
  191. pm.Listing("ID", "Online", "Title", "Slug")
  192. dp := pm.Detailing("Overview")
  193. dp.Field("Overview").ComponentFunc(settings(db, pm))
  194. oldDetailLayout := pb.GetDetailLayoutFunc()
  195. pb.DetailLayoutFunc(func(in web.PageFunc, cfg *presets.LayoutConfig) (out web.PageFunc) {
  196. return func(ctx *web.EventContext) (pr web.PageResponse, err error) {
  197. if !strings.Contains(ctx.R.RequestURI, "/"+pm.Info().URIName()+"/") && !strings.Contains(ctx.R.RequestURI, "/"+templateM.Info().URIName()+"/") {
  198. pr, err = oldDetailLayout(in, cfg)(ctx)
  199. return
  200. }
  201. pb.InjectAssets(ctx)
  202. // call createMenus before in(ctx) to fill the menuGroupName for modelBuilders first
  203. menu := pb.CreateMenus(ctx)
  204. var profile h.HTMLComponent
  205. if pb.GetProfileFunc() != nil {
  206. profile = pb.GetProfileFunc()(ctx)
  207. }
  208. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
  209. utilsMsgr := i18n.MustGetModuleMessages(ctx.R, utils.I18nUtilsKey, utils.Messages_en_US).(*utils.Messages)
  210. pvMsgr := i18n.MustGetModuleMessages(ctx.R, pv.I18nPublishKey, utils.Messages_en_US).(*pv.Messages)
  211. id := pat.Param(ctx.R, "id")
  212. if id == "" {
  213. return pb.DefaultNotFoundPageFunc(ctx)
  214. }
  215. isPage := strings.Contains(ctx.R.RequestURI, "/"+pm.Info().URIName()+"/")
  216. isTemplate := strings.Contains(ctx.R.RequestURI, "/"+templateM.Info().URIName()+"/")
  217. var obj interface{}
  218. var dmb *presets.ModelBuilder
  219. if isPage {
  220. dmb = pm
  221. } else {
  222. dmb = templateM
  223. }
  224. obj = dmb.NewModel()
  225. obj, err = dmb.Detailing().GetFetchFunc()(obj, id, ctx)
  226. if err != nil {
  227. if errors.Is(err, presets.ErrRecordNotFound) {
  228. return pb.DefaultNotFoundPageFunc(ctx)
  229. }
  230. return
  231. }
  232. if l, ok := obj.(l10n.L10nInterface); ok {
  233. locale := ctx.R.FormValue("locale")
  234. if ctx.R.FormValue(web.EventFuncIDName) == "__reload__" && locale != "" && locale != l.GetLocale() {
  235. // redirect to list page when change locale
  236. http.Redirect(ctx.W, ctx.R, dmb.Info().ListingHref(), http.StatusSeeOther)
  237. return
  238. }
  239. }
  240. var tabContent web.PageResponse
  241. tab := ctx.R.FormValue("tab")
  242. isContent := tab == "content"
  243. activeTabIndex := 0
  244. isVersion := false
  245. if _, ok := obj.(publish.VersionInterface); ok {
  246. isVersion = true
  247. }
  248. if isContent {
  249. activeTabIndex = 1
  250. if v, ok := obj.(interface {
  251. GetID() uint
  252. }); ok {
  253. ctx.R.Form.Set("id", strconv.Itoa(int(v.GetID())))
  254. }
  255. if v, ok := obj.(publish.VersionInterface); ok {
  256. ctx.R.Form.Set("version", v.GetVersion())
  257. }
  258. if l, ok := obj.(l10n.L10nInterface); ok {
  259. ctx.R.Form.Set("locale", l.GetLocale())
  260. }
  261. if isTemplate {
  262. ctx.R.Form.Set("tpl", "1")
  263. }
  264. tabContent, err = b.PageContent(ctx)
  265. } else {
  266. tabContent, err = in(ctx)
  267. }
  268. if errors.Is(err, perm.PermissionDenied) {
  269. pr.Body = h.Text(perm.PermissionDenied.Error())
  270. return pr, nil
  271. }
  272. if err != nil {
  273. panic(err)
  274. }
  275. device := ctx.R.FormValue("device")
  276. editAction := web.POST().
  277. EventFunc(actions.Edit).
  278. URL(web.Var("\""+b.prefix+"/\"+arr[0]")).
  279. Query(presets.ParamOverlay, actions.Dialog).
  280. Query(presets.ParamID, web.Var("arr[1]")).
  281. Go()
  282. var publishBtn h.HTMLComponent
  283. var duplicateBtn h.HTMLComponent
  284. var versionSwitch h.HTMLComponent
  285. primarySlug := ""
  286. if v, ok := obj.(presets.SlugEncoder); ok {
  287. primarySlug = v.PrimarySlug()
  288. }
  289. if isVersion {
  290. p := obj.(*Page)
  291. versionCount := versionCount(db, p)
  292. switch p.GetStatus() {
  293. case publish.StatusDraft, publish.StatusOffline:
  294. publishBtn = VBtn(pvMsgr.Publish).Small(true).Color(b.publishBtnColor).Height(40).Attr("@click", fmt.Sprintf(`locals.action="%s";locals.commonConfirmDialog = true`, pv.PublishEvent))
  295. case publish.StatusOnline:
  296. publishBtn = VBtn(pvMsgr.Republish).Small(true).Color(b.publishBtnColor).Height(40).Attr("@click", fmt.Sprintf(`locals.action="%s";locals.commonConfirmDialog = true`, pv.RepublishEvent))
  297. }
  298. duplicateBtn = VBtn(msgr.Duplicate).
  299. Small(true).Color(b.duplicateBtnColor).Height(40).Class("rounded-l-0 mr-3").
  300. Attr("@click", web.Plaid().
  301. EventFunc(pv.DuplicateVersionEvent).
  302. URL(pm.Info().ListingHref()).
  303. Query(presets.ParamID, primarySlug).
  304. Query("tab", tab).
  305. QueryIf("device", device, device != "").
  306. FieldValue("Title", p.Title).FieldValue("Slug", p.Slug).FieldValue("CategoryID", p.CategoryID).
  307. Go(),
  308. )
  309. versionSwitch = VChip(
  310. VChip(h.Text(fmt.Sprintf("%d", versionCount))).Label(true).Color("#E0E0E0").Small(true).Class("px-1 mx-1").TextColor("black").Attr("style", "height:20px"),
  311. h.Text(p.GetVersionName()+" | "),
  312. VChip(h.Text(pv.GetStatusText(p.GetStatus(), pvMsgr))).Label(true).Color(pv.GetStatusColor(p.GetStatus())).Small(true).Class("px-1 mx-1").TextColor("black").Attr("style", "height:20px"),
  313. VIcon("chevron_right"),
  314. ).Label(true).Outlined(true).Class("px-1 ml-8 rounded-r-0").Attr("style", "height:40px;background-color:#FFFFFF!important;").TextColor("black").
  315. Attr("@click", web.Plaid().EventFunc(actions.OpenListingDialog).
  316. URL(b.ps.GetURIPrefix()+"/version-list-dialog").
  317. Query("select_id", primarySlug).
  318. Go())
  319. }
  320. pr.Body = VApp(
  321. VNavigationDrawer(
  322. pb.RunBrandProfileSwitchLanguageDisplayFunc(pb.RunBrandFunc(ctx), profile, pb.RunSwitchLanguageFunc(ctx), ctx),
  323. VDivider(),
  324. menu,
  325. ).App(true).
  326. Fixed(true).
  327. Value(true).
  328. Attr("v-model", "vars.navDrawer").
  329. Attr(web.InitContextVars, `{navDrawer: null}`),
  330. VAppBar(
  331. VAppBarNavIcon().On("click.stop", "vars.navDrawer = !vars.navDrawer"),
  332. VTabs(
  333. VTab(h.Text("{{item.label}}")).Attr("@click", web.Plaid().Query("tab", web.Var("item.query")).PushState(true).Go()).
  334. Attr("v-for", "(item, index) in locals.tabs", ":key", "index"),
  335. ).Centered(true).FixedTabs(true).Attr("v-model", `locals.activeTab`).Attr("style", "width:400px"),
  336. // h.If(isContent, VAppBarNavIcon().On("click.stop", "vars.pbEditorDrawer = !vars.pbEditorDrawer")),
  337. h.If(isVersion, versionSwitch, duplicateBtn, publishBtn),
  338. ).Dark(true).
  339. Color(presets.ColorPrimary).
  340. App(true).
  341. ClippedRight(true).
  342. Fixed(true),
  343. web.Portal().Name(presets.RightDrawerPortalName),
  344. web.Portal().Name(presets.DialogPortalName),
  345. web.Portal().Name(presets.DeleteConfirmPortalName),
  346. web.Portal().Name(presets.DefaultConfirmDialogPortalName),
  347. web.Portal().Name(presets.ListingDialogPortalName),
  348. web.Portal().Name(dialogPortalName),
  349. utils.ConfirmDialog(pvMsgr.Areyousure, web.Plaid().EventFunc(web.Var("locals.action")).
  350. Query(presets.ParamID, primarySlug).Go(),
  351. utilsMsgr),
  352. h.If(isContent, h.Script(`
  353. (function(){
  354. let scrollLeft = 0;
  355. let scrollTop = 0;
  356. function pause(duration) {
  357. return new Promise(res => setTimeout(res, duration));
  358. }
  359. function backoff(retries, fn, delay = 100) {
  360. fn().catch(err => retries > 1
  361. ? pause(delay).then(() => backoff(retries - 1, fn, delay * 2))
  362. : Promise.reject(err));
  363. }
  364. function restoreScroll() {
  365. window.scroll({left: scrollLeft, top: scrollTop, behavior: "auto"});
  366. if (window.scrollX == scrollLeft && window.scrollY == scrollTop) {
  367. return Promise.resolve();
  368. }
  369. return Promise.reject();
  370. }
  371. window.addEventListener('fetchStart', (event) => {
  372. scrollLeft = window.scrollX;
  373. scrollTop = window.scrollY;
  374. });
  375. window.addEventListener('fetchEnd', (event) => {
  376. backoff(5, restoreScroll, 100);
  377. });
  378. })()
  379. `),
  380. vx.VXMessageListener().ListenFunc(fmt.Sprintf(`
  381. function(e){
  382. if (!e.data.split) {
  383. return
  384. }
  385. let arr = e.data.split("_");
  386. if (arr.length != 2) {
  387. console.log(arr);
  388. return
  389. }
  390. %s
  391. }`, editAction))),
  392. VProgressLinear().
  393. Attr(":active", "isFetching").
  394. Attr("style", "position: fixed; z-index: 99").
  395. Indeterminate(true).
  396. Height(2).
  397. Color(pb.GetProgressBarColor()),
  398. h.Template(
  399. VSnackbar(h.Text("{{vars.presetsMessage.message}}")).
  400. Attr("v-model", "vars.presetsMessage.show").
  401. Attr(":color", "vars.presetsMessage.color").
  402. Timeout(1000).
  403. Top(true),
  404. ).Attr("v-if", "vars.presetsMessage"),
  405. VMain(
  406. tabContent.Body.(h.HTMLComponent),
  407. ),
  408. ).Id("vt-app").
  409. Attr(web.InitContextVars, `{presetsRightDrawer: false, presetsDialog: false, dialogPortalName: false, presetsListingDialog: false, presetsMessage: {show: false, color: "success", message: ""}}`).
  410. Attr(web.InitContextLocals, fmt.Sprintf(`{action: "", commonConfirmDialog: false, activeTab:%d, tabs: [{label:"SETTINGS",query:"settings"},{label:"CONTENT",query:"content"}]}`, activeTabIndex))
  411. return
  412. }
  413. })
  414. configureVersionListDialog(db, b.ps, pm)
  415. if b.templateEnabled {
  416. pm.RegisterEventFunc(openTemplateDialogEvent, openTemplateDialog(db, b.prefix))
  417. pm.RegisterEventFunc(selectTemplateEvent, selectTemplate(db))
  418. // pm.RegisterEventFunc(clearTemplateEvent, clearTemplate(db))
  419. }
  420. pm.RegisterEventFunc(schedulePublishDialogEvent, schedulePublishDialog(db, pm))
  421. pm.RegisterEventFunc(schedulePublishEvent, schedulePublish(db, pm))
  422. pm.RegisterEventFunc(createNoteDialogEvent, createNoteDialog(db, pm))
  423. pm.RegisterEventFunc(createNoteEvent, createNote(db, pm))
  424. pm.RegisterEventFunc(editSEODialogEvent, editSEODialog(db, pm, seoCollection))
  425. pm.RegisterEventFunc(updateSEOEvent, updateSEO(db, pm))
  426. eb := pm.Editing("TemplateSelection", "Title", "CategoryID", "Slug")
  427. eb.ValidateFunc(func(obj interface{}, ctx *web.EventContext) (err web.ValidationErrors) {
  428. c := obj.(*Page)
  429. err = pageValidator(c, db, l10nB)
  430. return
  431. })
  432. eb.Field("Slug").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  433. var vErr web.ValidationErrors
  434. if ve, ok := ctx.Flash.(*web.ValidationErrors); ok {
  435. vErr = *ve
  436. }
  437. return VTextField().
  438. FieldName(field.Name).
  439. Prefix("/").
  440. Label(field.Label).Value(strings.TrimPrefix(field.Value(obj).(string), "/")).
  441. ErrorMessages(vErr.GetFieldErrors("Page.Slug")...)
  442. }).SetterFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) (err error) {
  443. m := obj.(*Page)
  444. m.Slug = path.Join("/", m.Slug)
  445. return nil
  446. })
  447. eb.Field("CategoryID").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  448. p := obj.(*Page)
  449. categories := []*Category{}
  450. locale, _ := l10n.IsLocalizableFromCtx(ctx.R.Context())
  451. if err := db.Model(&Category{}).Where("locale_code = ?", locale).Find(&categories).Error; err != nil {
  452. panic(err)
  453. }
  454. var vErr web.ValidationErrors
  455. if ve, ok := ctx.Flash.(*web.ValidationErrors); ok {
  456. vErr = *ve
  457. }
  458. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
  459. return vx.VXAutocomplete().Label(msgr.Category).FieldName(field.Name).
  460. Multiple(false).Chips(false).
  461. Items(categories).Value(p.CategoryID).ItemText("Path").ItemValue("ID").
  462. ErrorMessages(vErr.GetFieldErrors("Page.Category")...)
  463. })
  464. eb.Field("TemplateSelection").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  465. if !b.templateEnabled {
  466. return nil
  467. }
  468. p := obj.(*Page)
  469. selectedID := ctx.R.FormValue(templateSelectedID)
  470. body, err := getTplPortalComp(ctx, db, selectedID)
  471. if err != nil {
  472. panic(err)
  473. }
  474. // Display template selection only when creating a new page
  475. if p.ID == 0 {
  476. return h.Div(
  477. web.Portal().Name(templateSelectPortal),
  478. web.Portal(
  479. body,
  480. ).Name(selectedTemplatePortal),
  481. ).Class("my-2").Attr(web.InitContextVars, `{showTemplateDialog: false}`)
  482. }
  483. return nil
  484. })
  485. eb.Field("EditContainer").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  486. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
  487. p := obj.(*Page)
  488. if p.ID == 0 {
  489. return nil
  490. }
  491. if p.GetStatus() == publish.StatusDraft {
  492. var href = fmt.Sprintf("%s/editors/%d?version=%s", b.prefix, p.ID, p.GetVersion())
  493. if locale, isLocalizable := l10n.IsLocalizableFromCtx(ctx.R.Context()); isLocalizable && l10nON {
  494. href = fmt.Sprintf("%s/editors/%d?version=%s&locale=%s", b.prefix, p.ID, p.GetVersion(), locale)
  495. }
  496. return h.Div(
  497. VBtn(msgr.EditPageContent).
  498. Target("_blank").
  499. Href(href).
  500. Color("secondary"),
  501. )
  502. } else {
  503. var href = fmt.Sprintf("%s/preview?id=%d&version=%s", b.prefix, p.ID, p.GetVersion())
  504. if locale, isLocalizable := l10n.IsLocalizableFromCtx(ctx.R.Context()); isLocalizable && l10nON {
  505. href = fmt.Sprintf("%s/preview?id=%d&version=%s&locale=%s", b.prefix, p.ID, p.GetVersion(), locale)
  506. }
  507. return h.Div(
  508. VBtn(msgr.Preview).
  509. Target("_blank").
  510. Href(href).
  511. Color("secondary"),
  512. )
  513. }
  514. return nil
  515. })
  516. eb.SaveFunc(func(obj interface{}, id string, ctx *web.EventContext) (err error) {
  517. localeCode, _ := l10n.IsLocalizableFromCtx(ctx.R.Context())
  518. p := obj.(*Page)
  519. if p.Slug != "" {
  520. p.Slug = path.Clean(p.Slug)
  521. }
  522. funcName := ctx.R.FormValue(web.EventFuncIDName)
  523. if funcName == pv.DuplicateVersionEvent {
  524. id := ctx.R.FormValue(presets.ParamID)
  525. var fromPage Page
  526. eb.Fetcher(&fromPage, id, ctx)
  527. p.SEO = fromPage.SEO
  528. }
  529. err = db.Transaction(func(tx *gorm.DB) (inerr error) {
  530. if inerr = gorm2op.DataOperator(tx).Save(obj, id, ctx); inerr != nil {
  531. return
  532. }
  533. if strings.Contains(ctx.R.RequestURI, pv.SaveNewVersionEvent) || strings.Contains(ctx.R.RequestURI, pv.DuplicateVersionEvent) {
  534. if inerr = b.copyContainersToNewPageVersion(tx, int(p.ID), p.GetLocale(), p.ParentVersion, p.GetVersion()); inerr != nil {
  535. return
  536. }
  537. return
  538. }
  539. if v := ctx.R.FormValue(templateSelectedID); v != "" {
  540. var tplID int
  541. tplID, inerr = strconv.Atoi(v)
  542. if inerr != nil {
  543. return
  544. }
  545. if !l10nON {
  546. localeCode = ""
  547. }
  548. if inerr = b.copyContainersToAnotherPage(tx, tplID, templateVersion, localeCode, int(p.ID), p.GetVersion(), localeCode); inerr != nil {
  549. panic(inerr)
  550. return
  551. }
  552. }
  553. if l10nON && strings.Contains(ctx.R.RequestURI, l10n_view.DoLocalize) {
  554. fromID := ctx.R.Context().Value(l10n_view.FromID).(string)
  555. fromVersion := ctx.R.Context().Value(l10n_view.FromVersion).(string)
  556. fromLocale := ctx.R.Context().Value(l10n_view.FromLocale).(string)
  557. var fromIDInt int
  558. fromIDInt, err = strconv.Atoi(fromID)
  559. if err != nil {
  560. return
  561. }
  562. if inerr = b.localizeContainersToAnotherPage(tx, fromIDInt, fromVersion, fromLocale, int(p.ID), p.GetVersion(), p.GetLocale()); inerr != nil {
  563. panic(inerr)
  564. return
  565. }
  566. return
  567. }
  568. return
  569. })
  570. return
  571. })
  572. sharedContainerM := b.ConfigSharedContainer(pb, db)
  573. demoContainerM := b.ConfigDemoContainer(pb, db)
  574. categoryM := b.ConfigCategory(pb, db, l10nB)
  575. if activityB != nil {
  576. activityB.RegisterModels(pm, sharedContainerM, demoContainerM, templateM, categoryM)
  577. }
  578. if l10nB != nil {
  579. l10n_view.Configure(pb, db, l10nB, activityB, pm, demoContainerM, templateM, categoryM)
  580. }
  581. if publisher != nil {
  582. publisher.WithPageBuilder(b)
  583. pv.Configure(pb, db, activityB, publisher, pm)
  584. pm.Editing().SidePanelFunc(nil).ActionsFunc(nil)
  585. }
  586. if seoCollection != nil {
  587. seoCollection.RegisterSEO(&Page{}).RegisterContextVariables(
  588. "Title",
  589. func(object interface{}, _ *seo.Setting, _ *http.Request) string {
  590. if p, ok := object.(Page); ok {
  591. return p.Title
  592. }
  593. return ""
  594. },
  595. )
  596. }
  597. note.Configure(db, pb, pm)
  598. eb.CleanTabsPanels()
  599. dp.CleanTabsPanels()
  600. mediav.Configure(b.GetPresetsBuilder(), db)
  601. return
  602. }
  603. func versionCount(db *gorm.DB, p *Page) (count int64) {
  604. db.Model(&Page{}).Where("id = ? and locale_code = ?", p.ID, p.LocaleCode).Count(&count)
  605. return
  606. }
  607. func configureVersionListDialog(db *gorm.DB, pb *presets.Builder, pm *presets.ModelBuilder) {
  608. mb := pb.Model(&Page{}).
  609. URIName("version-list-dialog").
  610. InMenu(false)
  611. searcher := mb.Listing().Searcher
  612. lb := mb.Listing("Version", "State", "Notes", "Option").
  613. SearchColumns("version", "version_name").
  614. PerPage(10).
  615. SearchFunc(func(model interface{}, params *presets.SearchParams, ctx *web.EventContext) (r interface{}, totalCount int, err error) {
  616. id := ctx.R.FormValue("select_id")
  617. if id == "" {
  618. id = ctx.R.FormValue("f_select_id")
  619. }
  620. if id != "" {
  621. cs := mb.NewModel().(presets.SlugDecoder).PrimaryColumnValuesBySlug(id)
  622. con := presets.SQLCondition{
  623. Query: "id = ? and locale_code = ?",
  624. Args: []interface{}{cs["id"], cs["locale_code"]},
  625. }
  626. params.SQLConditions = append(params.SQLConditions, &con)
  627. }
  628. params.OrderBy = "created_at DESC"
  629. return searcher(model, params, ctx)
  630. })
  631. lb.Field("Version").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  632. versionName := obj.(publish.VersionInterface).GetVersionName()
  633. return h.Td(
  634. h.Text(versionName),
  635. VBtn("").Icon(true).Children(VIcon("edit")).Attr("@click", web.Plaid().
  636. URL(pb.GetURIPrefix()+"/version-list-dialog").
  637. EventFunc(renameVersionDialogEvent).
  638. Queries(ctx.Queries()).
  639. Query(presets.ParamOverlay, actions.Dialog).
  640. Query("rename_id", obj.(presets.SlugEncoder).PrimarySlug()).
  641. Query("version_name", versionName).
  642. Go()),
  643. )
  644. })
  645. lb.Field("State").ComponentFunc(pv.StatusListFunc())
  646. lb.Field("Notes").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  647. p := obj.(*Page)
  648. rt := pm.Info().Label()
  649. ri := p.PrimarySlug()
  650. userID, _ := note.GetUserData(ctx)
  651. count := note.GetUnreadNotesCount(db, userID, rt, ri)
  652. return h.Td(
  653. h.If(count > 0,
  654. VBadge().Content(count).Color("red"),
  655. ).Else(
  656. h.Text(""),
  657. ),
  658. )
  659. }).Label("Unread Notes")
  660. lb.Field("Option").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  661. p := obj.(*Page)
  662. id := ctx.R.FormValue("select_id")
  663. if id == "" {
  664. id = ctx.R.FormValue("f_select_id")
  665. }
  666. versionName := p.GetVersionName()
  667. if p.PrimarySlug() == id {
  668. return h.Td(h.Text("current"))
  669. }
  670. if p.GetStatus() != publish.StatusDraft {
  671. return h.Td()
  672. }
  673. return h.Td(VBtn("").Icon(true).Children(VIcon("delete")).Attr("@click", web.Plaid().
  674. URL(pb.GetURIPrefix()+"/version-list-dialog").
  675. EventFunc(deleteVersionDialogEvent).
  676. Queries(ctx.Queries()).
  677. Query(presets.ParamOverlay, actions.Dialog).
  678. Query("delete_id", obj.(presets.SlugEncoder).PrimarySlug()).
  679. Query("version_name", versionName).
  680. Go()))
  681. })
  682. lb.NewButtonFunc(func(ctx *web.EventContext) h.HTMLComponent { return nil })
  683. lb.RowMenu().Empty()
  684. mb.RegisterEventFunc(selectVersionEvent, func(ctx *web.EventContext) (r web.EventResponse, err error) {
  685. id := ctx.R.FormValue("select_id")
  686. refer, _ := url.Parse(ctx.R.Referer())
  687. newQueries := refer.Query()
  688. r.PushState = web.Location(newQueries).URL(pm.Info().DetailingHref(id))
  689. return
  690. })
  691. mb.RegisterEventFunc(renameVersionDialogEvent, renameVersionDialog(mb))
  692. mb.RegisterEventFunc(renameVersionEvent, renameVersion(mb))
  693. mb.RegisterEventFunc(deleteVersionDialogEvent, deleteVersionDialog(mb))
  694. lb.CellWrapperFunc(func(cell h.MutableAttrHTMLComponent, id string, obj interface{}, dataTableID string) h.HTMLComponent {
  695. cell.SetAttr("@click.self", web.Plaid().
  696. Query("select_id", obj.(*Page).PrimarySlug()).
  697. URL(pb.GetURIPrefix()+"/version-list-dialog").
  698. EventFunc(selectVersionEvent).
  699. Go(),
  700. )
  701. return cell
  702. })
  703. lb.FilterDataFunc(func(ctx *web.EventContext) vx.FilterData {
  704. return []*vx.FilterItem{
  705. {
  706. Key: "all",
  707. Invisible: true,
  708. SQLCondition: ``,
  709. },
  710. {
  711. Key: "online_version",
  712. Invisible: true,
  713. SQLCondition: `status = 'online'`,
  714. },
  715. {
  716. Key: "named_versions",
  717. Invisible: true,
  718. SQLCondition: `version <> version_name`,
  719. },
  720. }
  721. })
  722. lb.FilterTabsFunc(func(ctx *web.EventContext) []*presets.FilterTab {
  723. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
  724. id := ctx.R.FormValue("select_id")
  725. if id == "" {
  726. id = ctx.R.FormValue("f_select_id")
  727. }
  728. return []*presets.FilterTab{
  729. {
  730. Label: msgr.FilterTabAllVersions,
  731. ID: "all",
  732. Query: url.Values{"all": []string{"1"}, "select_id": []string{id}},
  733. },
  734. {
  735. Label: msgr.FilterTabOnlineVersion,
  736. ID: "online_version",
  737. Query: url.Values{"online_version": []string{"1"}, "select_id": []string{id}},
  738. },
  739. {
  740. Label: msgr.FilterTabNamedVersions,
  741. ID: "named_versions",
  742. Query: url.Values{"named_versions": []string{"1"}, "select_id": []string{id}},
  743. },
  744. }
  745. })
  746. }
  747. // cats should be ordered by path
  748. func fillCategoryIndentLevels(cats []*Category) {
  749. for i, cat := range cats {
  750. if cat.Path == "/" {
  751. continue
  752. }
  753. for j := i - 1; j >= 0; j-- {
  754. if strings.HasPrefix(cat.Path, cats[j].Path+"/") {
  755. cat.IndentLevel = cats[j].IndentLevel + 1
  756. break
  757. }
  758. }
  759. }
  760. }
  761. func (b *Builder) ConfigCategory(pb *presets.Builder, db *gorm.DB, l10nB *l10n.Builder) (pm *presets.ModelBuilder) {
  762. pm = pb.Model(&Category{}).URIName("page_categories").Label("Categories")
  763. lb := pm.Listing("Name", "Path", "Description")
  764. oldSearcher := lb.Searcher
  765. lb.SearchFunc(func(model interface{}, params *presets.SearchParams, ctx *web.EventContext) (r interface{}, totalCount int, err error) {
  766. r, totalCount, err = oldSearcher(model, params, ctx)
  767. cats := r.([]*Category)
  768. sort.Slice(cats, func(i, j int) bool {
  769. return cats[i].Path < cats[j].Path
  770. })
  771. fillCategoryIndentLevels(cats)
  772. return
  773. })
  774. lb.Field("Name").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  775. cat := obj.(*Category)
  776. icon := "folder"
  777. if cat.IndentLevel != 0 {
  778. icon = "insert_drive_file"
  779. }
  780. return h.Td(
  781. h.Div(
  782. VIcon(icon).Small(true).Class("mb-1"),
  783. h.Text(cat.Name),
  784. ).Style(fmt.Sprintf("padding-left: %dpx;", cat.IndentLevel*32)),
  785. )
  786. })
  787. eb := pm.Editing("Name", "Path", "Description")
  788. eb.Field("Path").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  789. var vErr web.ValidationErrors
  790. if ve, ok := ctx.Flash.(*web.ValidationErrors); ok {
  791. vErr = *ve
  792. }
  793. return VTextField().Label("Path").
  794. Value(strings.TrimPrefix(field.Value(obj).(string), "/")).
  795. FieldName("Path").
  796. Prefix("/").
  797. ErrorMessages(vErr.GetFieldErrors("Category.Category")...)
  798. }).SetterFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) (err error) {
  799. m := obj.(*Category)
  800. p := ctx.R.FormValue(field.Name)
  801. p = path.Join("/", p)
  802. m.Path = p
  803. return nil
  804. })
  805. eb.DeleteFunc(func(obj interface{}, id string, ctx *web.EventContext) (err error) {
  806. cs := obj.(presets.SlugDecoder).PrimaryColumnValuesBySlug(id)
  807. ID := cs["id"]
  808. Locale := cs["locale_code"]
  809. var count int64
  810. if err = db.Model(&Page{}).Where("category_id = ? AND locale_code = ?", ID, Locale).Count(&count).Error; err != nil {
  811. return
  812. }
  813. if count > 0 {
  814. err = errors.New(unableDeleteCategoryMsg)
  815. return
  816. }
  817. if err = db.Model(&Category{}).Where("id = ? AND locale_code = ?", ID, Locale).Delete(&Category{}).Error; err != nil {
  818. return
  819. }
  820. return
  821. })
  822. eb.ValidateFunc(func(obj interface{}, ctx *web.EventContext) (err web.ValidationErrors) {
  823. c := obj.(*Category)
  824. err = categoryValidator(c, db, l10nB)
  825. return
  826. })
  827. eb.SaveFunc(func(obj interface{}, id string, ctx *web.EventContext) (err error) {
  828. c := obj.(*Category)
  829. c.Path = path.Clean(c.Path)
  830. err = db.Save(c).Error
  831. return
  832. })
  833. return
  834. }
  835. const (
  836. templateSelectPortal = "templateSelectPortal"
  837. selectedTemplatePortal = "selectedTemplatePortal"
  838. templateSelectedID = "TemplateSelectedID"
  839. templateID = "TemplateID"
  840. templateBlankVal = "blank"
  841. )
  842. func selectTemplate(db *gorm.DB) web.EventFunc {
  843. return func(ctx *web.EventContext) (er web.EventResponse, err error) {
  844. defer func() {
  845. web.AppendVarsScripts(&er, "vars.showTemplateDialog=false")
  846. }()
  847. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
  848. id := ctx.R.FormValue(templateID)
  849. if id == templateBlankVal {
  850. er.UpdatePortals = append(er.UpdatePortals, &web.PortalUpdate{
  851. Name: selectedTemplatePortal,
  852. Body: VRow(
  853. VCol(
  854. h.Input("").Type("hidden").Value("").Attr(web.VFieldName(templateSelectedID)...),
  855. VTextField().Readonly(true).Label(msgr.SelectedTemplateLabel).Value(msgr.Blank).Dense(true).Outlined(true),
  856. ).Cols(5),
  857. VCol(
  858. VBtn(msgr.ChangeTemplate).Color("primary").
  859. Attr("@click", web.Plaid().Query(templateSelectedID, "").EventFunc(openTemplateDialogEvent).Go()),
  860. ).Cols(5),
  861. ),
  862. })
  863. return
  864. }
  865. var body h.HTMLComponent
  866. if body, err = getTplPortalComp(ctx, db, id); err != nil {
  867. return
  868. }
  869. er.UpdatePortals = append(er.UpdatePortals, &web.PortalUpdate{
  870. Name: selectedTemplatePortal,
  871. Body: body,
  872. })
  873. return
  874. }
  875. }
  876. func getTplPortalComp(ctx *web.EventContext, db *gorm.DB, selectedID string) (h.HTMLComponent, error) {
  877. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
  878. locale, _ := l10n.IsLocalizableFromCtx(ctx.R.Context())
  879. name := msgr.Blank
  880. if selectedID != "" {
  881. if err := db.Model(&Template{}).Where("id = ? AND locale_code = ?", selectedID, locale).Pluck("name", &name).Error; err != nil {
  882. return nil, err
  883. }
  884. }
  885. return VRow(
  886. VCol(
  887. h.Input("").Type("hidden").Value(selectedID).Attr(web.VFieldName(templateSelectedID)...),
  888. VTextField().Readonly(true).Label(msgr.SelectedTemplateLabel).Value(name).Dense(true).Outlined(true),
  889. ).Cols(5),
  890. VCol(
  891. VBtn(msgr.ChangeTemplate).Color("primary").
  892. Attr("@click", web.Plaid().Query(templateSelectedID, selectedID).EventFunc(openTemplateDialogEvent).Go()),
  893. ).Cols(5),
  894. ), nil
  895. }
  896. // Unused
  897. func clearTemplate(db *gorm.DB) web.EventFunc {
  898. return func(ctx *web.EventContext) (er web.EventResponse, err error) {
  899. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
  900. er.UpdatePortals = append(er.UpdatePortals, &web.PortalUpdate{
  901. Name: selectedTemplatePortal,
  902. Body: VRow(
  903. VCol(
  904. h.Input("").Type("hidden").Value("").Attr(web.VFieldName(templateSelectedID)...),
  905. VTextField().Readonly(true).Label(msgr.SelectedTemplateLabel).Value(msgr.Blank).Dense(true).Outlined(true),
  906. ).Cols(5),
  907. VCol(
  908. VBtn(msgr.ChangeTemplate).Color("primary").
  909. Attr("@click", web.Plaid().Query(templateSelectedID, "").EventFunc(openTemplateDialogEvent).Go()),
  910. ).Cols(5),
  911. ),
  912. })
  913. return
  914. }
  915. }
  916. func openTemplateDialog(db *gorm.DB, prefix string) web.EventFunc {
  917. return func(ctx *web.EventContext) (er web.EventResponse, err error) {
  918. gmsgr := presets.MustGetMessages(ctx.R)
  919. locale, _ := l10n.IsLocalizableFromCtx(ctx.R.Context())
  920. selectedID := ctx.R.FormValue(templateSelectedID)
  921. tpls := []*Template{}
  922. if err := db.Model(&Template{}).Where("locale_code = ?", locale).Find(&tpls).Error; err != nil {
  923. panic(err)
  924. }
  925. msgrPb := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
  926. var tplHTMLComponents []h.HTMLComponent
  927. tplHTMLComponents = append(tplHTMLComponents,
  928. VCol(
  929. VCard(
  930. h.Div(
  931. h.Iframe().
  932. Attr("width", "100%", "height", "150", "frameborder", "no").
  933. Style("transform-origin: left top; transform: scale(1, 1); pointer-events: none;"),
  934. ),
  935. VCardTitle(h.Text(msgrPb.Blank)),
  936. VCardSubtitle(h.Text("")),
  937. h.Div(
  938. h.Input(templateID).Type("radio").
  939. Value(templateBlankVal).
  940. Attr(web.VFieldName(templateID)...).
  941. AttrIf("checked", "checked", selectedID == "").
  942. Style("width: 18px; height: 18px"),
  943. ).Class("mr-4 float-right"),
  944. ).Height(280).Class("text-truncate").Outlined(true),
  945. ).Cols(3),
  946. )
  947. for _, tpl := range tpls {
  948. tplHTMLComponents = append(tplHTMLComponents,
  949. getTplColComponent(ctx, prefix, tpl, selectedID),
  950. )
  951. }
  952. if len(tpls) == 0 {
  953. tplHTMLComponents = append(tplHTMLComponents,
  954. h.Div(h.Text(gmsgr.ListingNoRecordToShow)).Class("pl-4 text-center grey--text text--darken-2"),
  955. )
  956. }
  957. er.UpdatePortals = append(er.UpdatePortals, &web.PortalUpdate{
  958. Name: templateSelectPortal,
  959. Body: VDialog(
  960. VCard(
  961. VCardTitle(
  962. h.Text(msgrPb.CreateFromTemplate),
  963. VSpacer(),
  964. VBtn("").Icon(true).
  965. Children(VIcon("close")).
  966. Large(true).
  967. On("click", fmt.Sprintf("vars.showTemplateDialog=false")),
  968. ),
  969. VCardActions(
  970. VRow(tplHTMLComponents...),
  971. ),
  972. VCardActions(
  973. VSpacer(),
  974. VBtn(gmsgr.Cancel).Attr("@click", "vars.showTemplateDialog=false"),
  975. VBtn(gmsgr.OK).Color("primary").
  976. Attr("@click", web.Plaid().EventFunc(selectTemplateEvent).
  977. Go(),
  978. ),
  979. ).Class("pb-4"),
  980. ).Tile(true),
  981. ).MaxWidth("80%").
  982. Attr("v-model", fmt.Sprintf("vars.showTemplateDialog")).
  983. Attr(web.InitContextVars, fmt.Sprintf(`{showTemplateDialog: false}`)),
  984. })
  985. er.VarsScript = `setTimeout(function(){ vars.showTemplateDialog = true }, 100)`
  986. return
  987. }
  988. }
  989. func getTplColComponent(ctx *web.EventContext, prefix string, tpl *Template, selectedID string) h.HTMLComponent {
  990. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
  991. // Avoid layout errors
  992. var name string
  993. var desc string
  994. if tpl.Name == "" {
  995. name = msgr.Unnamed
  996. } else {
  997. name = tpl.Name
  998. }
  999. if tpl.Description == "" {
  1000. desc = msgr.NotDescribed
  1001. } else {
  1002. desc = tpl.Description
  1003. }
  1004. id := fmt.Sprintf("%d", tpl.ID)
  1005. src := fmt.Sprintf("./%s/preview?id=%d&tpl=1&locale=%s", prefix, tpl.ID, tpl.LocaleCode)
  1006. return VCol(
  1007. VCard(
  1008. h.Div(
  1009. h.Iframe().Src(src).
  1010. Attr("width", "100%", "height", "150", "frameborder", "no").
  1011. Style("transform-origin: left top; transform: scale(1, 1); pointer-events: none;"),
  1012. ),
  1013. VCardTitle(h.Text(name)),
  1014. VCardSubtitle(h.Text(desc)),
  1015. VBtn(msgr.Preview).Text(true).XSmall(true).Class("ml-2 mb-4").
  1016. Href(src).
  1017. Target("_blank").Color("primary"),
  1018. h.Div(
  1019. h.Input(templateID).Type("radio").
  1020. Value(id).
  1021. Attr(web.VFieldName(templateID)...).
  1022. AttrIf("checked", "checked", selectedID == id).
  1023. Style("width: 18px; height: 18px"),
  1024. ).Class("mr-4 float-right"),
  1025. ).Height(280).Class("text-truncate").Outlined(true),
  1026. ).Cols(3)
  1027. }
  1028. func schedulePublishDialog(db *gorm.DB, mb *presets.ModelBuilder) web.EventFunc {
  1029. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  1030. paramID := ctx.R.FormValue(presets.ParamID)
  1031. obj := mb.NewModel()
  1032. obj, err = mb.Editing().Fetcher(obj, paramID, ctx)
  1033. if err != nil {
  1034. return
  1035. }
  1036. s, ok := obj.(publish.ScheduleInterface)
  1037. if !ok {
  1038. return
  1039. }
  1040. var start, end string
  1041. if s.GetScheduledStartAt() != nil {
  1042. start = s.GetScheduledStartAt().Format("2006-01-02 15:04")
  1043. }
  1044. if s.GetScheduledEndAt() != nil {
  1045. end = s.GetScheduledEndAt().Format("2006-01-02 15:04")
  1046. }
  1047. msgr := i18n.MustGetModuleMessages(ctx.R, pv.I18nPublishKey, Messages_en_US).(*pv.Messages)
  1048. cmsgr := i18n.MustGetModuleMessages(ctx.R, presets.CoreI18nModuleKey, Messages_en_US).(*presets.Messages)
  1049. updateBtn := VBtn(cmsgr.Update).
  1050. Color("primary").
  1051. Attr(":disabled", "isFetching").
  1052. Attr(":loading", "isFetching").
  1053. Attr("@click", web.Plaid().
  1054. EventFunc(schedulePublishEvent).
  1055. // Queries(queries).
  1056. Query(presets.ParamID, paramID).
  1057. Query(presets.ParamOverlay, actions.Dialog).
  1058. URL(mb.Info().ListingHref()).
  1059. Go())
  1060. r.UpdatePortals = append(r.UpdatePortals, &web.PortalUpdate{
  1061. Name: dialogPortalName,
  1062. Body: web.Scope(
  1063. VDialog(
  1064. VCard(
  1065. VCardTitle(h.Text("Schedule Publish Time")),
  1066. VCardText(
  1067. VRow(
  1068. VCol(
  1069. vx.VXDateTimePicker().FieldName("ScheduledStartAt").Label(msgr.ScheduledStartAt).Value(start).
  1070. TimePickerProps(vx.TimePickerProps{Format: "24hr", Scrollable: true}).
  1071. ClearText(msgr.DateTimePickerClearText).OkText(msgr.DateTimePickerOkText),
  1072. // h.RawHTML(fmt.Sprintf(`<vx-datetimepicker label="ScheduledStartAt" value="%s" v-field-name='"ScheduledStartAt"'> </vx-datetimepicker>`, start)),
  1073. ).Cols(6),
  1074. VCol(
  1075. vx.VXDateTimePicker().FieldName("ScheduledEndAt").Label(msgr.ScheduledEndAt).Value(end).
  1076. TimePickerProps(vx.TimePickerProps{Format: "24hr", Scrollable: true}).
  1077. ClearText(msgr.DateTimePickerClearText).OkText(msgr.DateTimePickerOkText),
  1078. // h.RawHTML(fmt.Sprintf(`<vx-datetimepicker label="ScheduledEndAt" value="%s" v-field-name='"ScheduledEndAt"'> </vx-datetimepicker>`, end)),
  1079. ).Cols(6),
  1080. ),
  1081. ),
  1082. VCardActions(
  1083. VSpacer(),
  1084. updateBtn,
  1085. ),
  1086. ),
  1087. ).MaxWidth("480px").
  1088. Attr("v-model", "locals.schedulePublishDialog"),
  1089. ).Init("{schedulePublishDialog:true}").VSlot("{locals}"),
  1090. })
  1091. return
  1092. }
  1093. }
  1094. func schedulePublish(db *gorm.DB, mb *presets.ModelBuilder) web.EventFunc {
  1095. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  1096. paramID := ctx.R.FormValue(presets.ParamID)
  1097. obj := mb.NewModel()
  1098. obj, err = mb.Editing().Fetcher(obj, paramID, ctx)
  1099. if err != nil {
  1100. return
  1101. }
  1102. err = pv.ScheduleEditSetterFunc(obj, nil, ctx)
  1103. if err != nil {
  1104. mb.Editing().UpdateOverlayContent(ctx, &r, obj, "", err)
  1105. return
  1106. }
  1107. err = mb.Editing().Saver(obj, paramID, ctx)
  1108. if err != nil {
  1109. mb.Editing().UpdateOverlayContent(ctx, &r, obj, "", err)
  1110. return
  1111. }
  1112. r.PushState = web.Location(nil)
  1113. return
  1114. }
  1115. }
  1116. func createNoteDialog(db *gorm.DB, mb *presets.ModelBuilder) web.EventFunc {
  1117. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  1118. paramID := ctx.R.FormValue(presets.ParamID)
  1119. okAction := web.Plaid().
  1120. URL(mb.Info().ListingHref()).
  1121. EventFunc(createNoteEvent).
  1122. Query("resource_id", paramID).
  1123. Query("resource_type", mb.Info().Label()).
  1124. Query(presets.ParamOverlay, actions.Dialog).Go()
  1125. r.UpdatePortals = append(r.UpdatePortals, &web.PortalUpdate{
  1126. Name: dialogPortalName,
  1127. Body: web.Scope(
  1128. VDialog(
  1129. VCard(
  1130. VCardTitle(h.Text("Note")),
  1131. VCardText(
  1132. VTextField().FieldName("Content").Value(""),
  1133. ),
  1134. VCardActions(
  1135. VSpacer(),
  1136. VBtn("Cancel").
  1137. Depressed(true).
  1138. Class("ml-2").
  1139. On("click", "locals.createNoteDialog = false"),
  1140. VBtn("OK").
  1141. Color("primary").
  1142. Depressed(true).
  1143. Dark(true).
  1144. Attr("@click", okAction),
  1145. ),
  1146. ),
  1147. ).MaxWidth("420px").Attr("v-model", "locals.createNoteDialog"),
  1148. ).Init("{createNoteDialog:true}").VSlot("{locals}"),
  1149. })
  1150. return
  1151. }
  1152. }
  1153. func createNote(db *gorm.DB, mb *presets.ModelBuilder) web.EventFunc {
  1154. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  1155. ri := ctx.R.FormValue("resource_id")
  1156. rt := ctx.R.FormValue("resource_type")
  1157. content := ctx.R.FormValue("Content")
  1158. userID, creator := note.GetUserData(ctx)
  1159. nt := note.QorNote{
  1160. UserID: userID,
  1161. Creator: creator,
  1162. ResourceID: ri,
  1163. ResourceType: rt,
  1164. Content: content,
  1165. }
  1166. if err = db.Save(&nt).Error; err != nil {
  1167. presets.ShowMessage(&r, err.Error(), "error")
  1168. err = nil
  1169. return
  1170. }
  1171. userNote := note.UserNote{UserID: userID, ResourceType: rt, ResourceID: ri}
  1172. db.Where(userNote).FirstOrCreate(&userNote)
  1173. var total int64
  1174. db.Model(&note.QorNote{}).Where("resource_type = ? AND resource_id = ?", rt, ri).Count(&total)
  1175. db.Model(&userNote).UpdateColumn("Number", total)
  1176. r.PushState = web.Location(nil)
  1177. return
  1178. }
  1179. }
  1180. func editSEODialog(db *gorm.DB, mb *presets.ModelBuilder, seoCollection *seo.Collection) web.EventFunc {
  1181. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  1182. paramID := ctx.R.FormValue(presets.ParamID)
  1183. obj := mb.NewModel()
  1184. obj, err = mb.Editing().Fetcher(obj, paramID, ctx)
  1185. if err != nil {
  1186. return
  1187. }
  1188. // msgr := i18n.MustGetModuleMessages(ctx.R, pv.I18nPublishKey, Messages_en_US).(*pv.Messages)
  1189. cmsgr := i18n.MustGetModuleMessages(ctx.R, presets.CoreI18nModuleKey, Messages_en_US).(*presets.Messages)
  1190. updateBtn := VBtn(cmsgr.Update).
  1191. Color("primary").
  1192. Attr(":disabled", "isFetching").
  1193. Attr(":loading", "isFetching").
  1194. Attr("@click", web.Plaid().
  1195. EventFunc(updateSEOEvent).
  1196. // Queries(queries).
  1197. Query(presets.ParamID, paramID).
  1198. // Query(presets.ParamOverlay, actions.Dialog).
  1199. URL(mb.Info().ListingHref()).
  1200. Go())
  1201. ctx.R.Form.Set("openCustomizePanel", "0")
  1202. seoForm := seoCollection.EditingComponentFunc(obj, nil, ctx)
  1203. r.UpdatePortals = append(r.UpdatePortals, &web.PortalUpdate{
  1204. Name: dialogPortalName,
  1205. Body: web.Scope(
  1206. VDialog(
  1207. VCard(
  1208. VCardTitle(h.Text("")),
  1209. VCardText(
  1210. seoForm,
  1211. ),
  1212. VCardActions(
  1213. VSpacer(),
  1214. updateBtn,
  1215. ),
  1216. ),
  1217. ).MaxWidth("650px").
  1218. Attr("v-model", "locals.editSEODialog"),
  1219. ).Init("{editSEODialog:true}").VSlot("{locals}"),
  1220. })
  1221. return
  1222. }
  1223. }
  1224. func updateSEO(db *gorm.DB, mb *presets.ModelBuilder) web.EventFunc {
  1225. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  1226. paramID := ctx.R.FormValue(presets.ParamID)
  1227. obj := mb.NewModel()
  1228. obj, err = mb.Editing().Fetcher(obj, paramID, ctx)
  1229. if err != nil {
  1230. return
  1231. }
  1232. err = seo.EditSetterFunc(obj, &presets.FieldContext{Name: "SEO"}, ctx)
  1233. if err != nil {
  1234. mb.Editing().UpdateOverlayContent(ctx, &r, obj, "", err)
  1235. return
  1236. }
  1237. err = mb.Editing().Saver(obj, paramID, ctx)
  1238. if err != nil {
  1239. mb.Editing().UpdateOverlayContent(ctx, &r, obj, "", err)
  1240. return
  1241. }
  1242. r.PushState = web.Location(nil)
  1243. return
  1244. }
  1245. }
  1246. func renameVersionDialog(mb *presets.ModelBuilder) web.EventFunc {
  1247. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  1248. id := ctx.R.FormValue("rename_id")
  1249. versionName := ctx.R.FormValue("version_name")
  1250. okAction := web.Plaid().
  1251. URL(mb.Info().ListingHref()).
  1252. EventFunc(renameVersionEvent).
  1253. Queries(ctx.Queries()).
  1254. Query("rename_id", id).Go()
  1255. r.UpdatePortals = append(r.UpdatePortals, &web.PortalUpdate{
  1256. Name: dialogPortalName,
  1257. Body: web.Scope(
  1258. VDialog(
  1259. VCard(
  1260. VCardTitle(h.Text("Version")),
  1261. VCardText(
  1262. VTextField().FieldName("VersionName").Value(versionName),
  1263. ),
  1264. VCardActions(
  1265. VSpacer(),
  1266. VBtn("Cancel").
  1267. Depressed(true).
  1268. Class("ml-2").
  1269. On("click", "locals.renameVersionDialog = false"),
  1270. VBtn("OK").
  1271. Color("primary").
  1272. Depressed(true).
  1273. Dark(true).
  1274. Attr("@click", "locals.renameVersionDialog = false; "+okAction),
  1275. ),
  1276. ),
  1277. ).MaxWidth("420px").Attr("v-model", "locals.renameVersionDialog"),
  1278. ).Init("{renameVersionDialog:true}").VSlot("{locals}"),
  1279. })
  1280. return
  1281. }
  1282. }
  1283. func renameVersion(mb *presets.ModelBuilder) web.EventFunc {
  1284. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  1285. paramID := ctx.R.FormValue("rename_id")
  1286. obj := mb.NewModel()
  1287. obj, err = mb.Editing().Fetcher(obj, paramID, ctx)
  1288. if err != nil {
  1289. return
  1290. }
  1291. name := ctx.R.FormValue("VersionName")
  1292. if err = reflectutils.Set(obj, "Version.VersionName", name); err != nil {
  1293. return
  1294. }
  1295. if err = mb.Editing().Saver(obj, paramID, ctx); err != nil {
  1296. return
  1297. }
  1298. qs := ctx.Queries()
  1299. delete(qs, "version_name")
  1300. delete(qs, "rename_id")
  1301. r.VarsScript = web.Plaid().URL(ctx.R.RequestURI).Queries(qs).EventFunc(actions.UpdateListingDialog).Go()
  1302. return
  1303. }
  1304. }
  1305. func deleteVersionDialog(mb *presets.ModelBuilder) web.EventFunc {
  1306. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  1307. id := ctx.R.FormValue("delete_id")
  1308. versionName := ctx.R.FormValue("version_name")
  1309. r.UpdatePortals = append(r.UpdatePortals, &web.PortalUpdate{
  1310. Name: presets.DeleteConfirmPortalName,
  1311. Body: VDialog(
  1312. VCard(
  1313. VCardTitle(h.Text(fmt.Sprintf("Are you sure you want to delete %s?", versionName))),
  1314. VCardActions(
  1315. VSpacer(),
  1316. VBtn("Cancel").
  1317. Depressed(true).
  1318. Class("ml-2").
  1319. On("click", "vars.deleteConfirmation = false"),
  1320. VBtn("Delete").
  1321. Color("primary").
  1322. Depressed(true).
  1323. Dark(true).
  1324. Attr("@click", web.Plaid().
  1325. URL(mb.Info().ListingHref()).
  1326. EventFunc(actions.DoDelete).
  1327. Queries(ctx.Queries()).
  1328. Query(presets.ParamInDialog, true).
  1329. Query(presets.ParamID, id).Go()),
  1330. ),
  1331. ),
  1332. ).MaxWidth("580px").
  1333. Attr("v-model", "vars.deleteConfirmation").
  1334. Attr(web.InitContextVars, `{deleteConfirmation: false}`),
  1335. })
  1336. r.VarsScript = "setTimeout(function(){ vars.deleteConfirmation = true }, 100)"
  1337. return
  1338. }
  1339. }
  1340. func (b *Builder) ConfigSharedContainer(pb *presets.Builder, db *gorm.DB) (pm *presets.ModelBuilder) {
  1341. pm = pb.Model(&Container{}).URIName("shared_containers").Label("Shared Containers")
  1342. pm.RegisterEventFunc(republishRelatedOnlinePagesEvent, republishRelatedOnlinePages(b.mb.Info().ListingHref()))
  1343. listing := pm.Listing("DisplayName").SearchColumns("display_name")
  1344. listing.RowMenu("").Empty()
  1345. // ed := pm.Editing("SelectContainer")
  1346. // ed.Field("SelectContainer").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  1347. // var containers []h.HTMLComponent
  1348. // for _, builder := range b.containerBuilders {
  1349. // cover := builder.cover
  1350. // if cover == "" {
  1351. // cover = path.Join(b.prefix, b.imagesPrefix, strings.ReplaceAll(builder.name, " ", "")+".png")
  1352. // }
  1353. // containers = append(containers,
  1354. // VCol(
  1355. // VCard(
  1356. // VImg().Src(cover).Height(200),
  1357. // VCardActions(
  1358. // VCardTitle(h.Text(builder.name)),
  1359. // VSpacer(),
  1360. // VBtn("Select").
  1361. // Text(true).
  1362. // Color("primary").Attr("@click",
  1363. // web.Plaid().
  1364. // EventFunc(actions.New).
  1365. // URL(builder.GetModelBuilder().Info().ListingHref()).
  1366. // Go()),
  1367. // ),
  1368. // ),
  1369. // ).Cols(6),
  1370. // )
  1371. // }
  1372. // return VSheet(
  1373. // VContainer(
  1374. // VRow(
  1375. // containers...,
  1376. // ),
  1377. // ),
  1378. // )
  1379. // })
  1380. if permB := pb.GetPermission(); permB != nil {
  1381. permB.CreatePolicies(
  1382. perm.PolicyFor(perm.Anybody).WhoAre(perm.Denied).ToDo(presets.PermCreate).On("*:shared_containers:*"),
  1383. )
  1384. }
  1385. listing.Field("DisplayName").Label("Name")
  1386. listing.SearchFunc(sharedContainerSearcher(db, pm))
  1387. listing.CellWrapperFunc(func(cell h.MutableAttrHTMLComponent, id string, obj interface{}, dataTableID string) h.HTMLComponent {
  1388. tdbind := cell
  1389. c := obj.(*Container)
  1390. tdbind.SetAttr("@click.self",
  1391. web.Plaid().
  1392. EventFunc(actions.Edit).
  1393. URL(b.ContainerByName(c.ModelName).GetModelBuilder().Info().ListingHref()).
  1394. Query(presets.ParamID, c.ModelID).
  1395. Query(paramOpenFromSharedContainer, 1).
  1396. Go()+fmt.Sprintf(`; vars.currEditingListItemID="%s-%d"`, dataTableID, c.ModelID))
  1397. return tdbind
  1398. })
  1399. return
  1400. }
  1401. func (b *Builder) ConfigDemoContainer(pb *presets.Builder, db *gorm.DB) (pm *presets.ModelBuilder) {
  1402. pm = pb.Model(&DemoContainer{}).URIName("demo_containers").Label("Demo Containers")
  1403. pm.RegisterEventFunc("addDemoContainer", func(ctx *web.EventContext) (r web.EventResponse, err error) {
  1404. modelID := ctx.QueryAsInt(presets.ParamOverlayUpdateID)
  1405. modelName := ctx.R.FormValue("ModelName")
  1406. locale, _ := l10n.IsLocalizableFromCtx(ctx.R.Context())
  1407. var existID uint
  1408. {
  1409. m := DemoContainer{}
  1410. db.Where("model_name = ?", modelName).First(&m)
  1411. existID = m.ID
  1412. }
  1413. db.Assign(DemoContainer{
  1414. Model: gorm.Model{
  1415. ID: existID,
  1416. },
  1417. ModelID: uint(modelID),
  1418. }).FirstOrCreate(&DemoContainer{}, map[string]interface{}{
  1419. "model_name": modelName,
  1420. "locale_code": locale,
  1421. })
  1422. r.Reload = true
  1423. return
  1424. })
  1425. listing := pm.Listing("ModelName").SearchColumns("ModelName")
  1426. listing.Field("ModelName").Label("Name")
  1427. ed := pm.Editing("SelectContainer").ActionsFunc(func(obj interface{}, ctx *web.EventContext) h.HTMLComponent { return nil })
  1428. ed.Field("ModelName")
  1429. ed.Field("ModelID")
  1430. ed.Field("SelectContainer").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  1431. locale, localizable := l10n.IsLocalizableFromCtx(ctx.R.Context())
  1432. var demoContainers []DemoContainer
  1433. db.Find(&demoContainers)
  1434. var containers []h.HTMLComponent
  1435. for _, builder := range b.containerBuilders {
  1436. cover := builder.cover
  1437. if cover == "" {
  1438. cover = path.Join(b.prefix, b.imagesPrefix, strings.ReplaceAll(builder.name, " ", "")+".png")
  1439. }
  1440. c := VCol(
  1441. VCard(
  1442. VImg().Src(cover).Height(200),
  1443. VCardActions(
  1444. VCardTitle(h.Text(builder.name)),
  1445. VSpacer(),
  1446. VBtn("Select").
  1447. Text(true).
  1448. Color("primary").Attr("@click",
  1449. web.Plaid().
  1450. EventFunc(actions.New).
  1451. URL(builder.GetModelBuilder().Info().ListingHref()).
  1452. Query(presets.ParamOverlayAfterUpdateScript, web.POST().Query("ModelName", builder.name).EventFunc("addDemoContainer").Go()).
  1453. Go()),
  1454. ),
  1455. ),
  1456. ).Cols(6)
  1457. var isExists bool
  1458. var modelID uint
  1459. for _, dc := range demoContainers {
  1460. if dc.ModelName == builder.name {
  1461. if localizable && dc.GetLocale() != locale {
  1462. continue
  1463. }
  1464. isExists = true
  1465. modelID = dc.ModelID
  1466. break
  1467. }
  1468. }
  1469. if isExists {
  1470. c = VCol(
  1471. VCard(
  1472. VImg().Src(cover).Height(200),
  1473. VCardActions(
  1474. VCardTitle(h.Text(builder.name)),
  1475. VSpacer(),
  1476. VBtn("Edit").
  1477. Text(true).
  1478. Color("primary").Attr("@click",
  1479. web.Plaid().
  1480. EventFunc(actions.Edit).
  1481. URL(builder.GetModelBuilder().Info().ListingHref()).
  1482. Query(presets.ParamID, fmt.Sprint(modelID)).
  1483. Go()),
  1484. ),
  1485. ),
  1486. ).Cols(6)
  1487. }
  1488. containers = append(containers, c)
  1489. }
  1490. return VSheet(
  1491. VContainer(
  1492. VRow(
  1493. containers...,
  1494. ),
  1495. ),
  1496. )
  1497. })
  1498. listing.CellWrapperFunc(func(cell h.MutableAttrHTMLComponent, id string, obj interface{}, dataTableID string) h.HTMLComponent {
  1499. tdbind := cell
  1500. c := obj.(*DemoContainer)
  1501. tdbind.SetAttr("@click.self",
  1502. web.Plaid().
  1503. EventFunc(actions.Edit).
  1504. URL(b.ContainerByName(c.ModelName).GetModelBuilder().Info().ListingHref()).
  1505. Query(presets.ParamID, c.ModelID).
  1506. Go()+fmt.Sprintf(`; vars.currEditingListItemID="%s-%d"`, dataTableID, c.ModelID))
  1507. return tdbind
  1508. })
  1509. ed.SaveFunc(func(obj interface{}, id string, ctx *web.EventContext) (err error) {
  1510. this := obj.(*DemoContainer)
  1511. err = db.Transaction(func(tx *gorm.DB) (inerr error) {
  1512. if l10nON && strings.Contains(ctx.R.RequestURI, l10n_view.DoLocalize) {
  1513. if inerr = b.createModelAfterLocalizeDemoContainer(tx, this); inerr != nil {
  1514. panic(inerr)
  1515. return
  1516. }
  1517. }
  1518. if inerr = gorm2op.DataOperator(tx).Save(this, id, ctx); inerr != nil {
  1519. return
  1520. }
  1521. return
  1522. })
  1523. return
  1524. })
  1525. return
  1526. }
  1527. func (b *Builder) ConfigTemplate(pb *presets.Builder, db *gorm.DB) (pm *presets.ModelBuilder) {
  1528. pm = pb.Model(&Template{}).URIName("page_templates").Label("Templates")
  1529. pm.Listing("ID", "Name", "Description")
  1530. dp := pm.Detailing("Overview")
  1531. dp.Field("Overview").ComponentFunc(templateSettings(db, pm))
  1532. eb := pm.Editing("Name", "Description")
  1533. eb.Field("EditContainer").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  1534. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
  1535. m := obj.(*Template)
  1536. if m.ID == 0 {
  1537. return nil
  1538. }
  1539. var href = fmt.Sprintf("%s/editors/%d?tpl=1", b.prefix, m.ID)
  1540. if locale, isLocalizable := l10n.IsLocalizableFromCtx(ctx.R.Context()); isLocalizable && l10nON {
  1541. href = fmt.Sprintf("%s/editors/%d?tpl=1&locale=%s", b.prefix, m.ID, locale)
  1542. }
  1543. return h.Div(
  1544. VBtn(msgr.EditPageContent).
  1545. Target("_blank").
  1546. Href(href).
  1547. Color("secondary"),
  1548. )
  1549. })
  1550. eb.SaveFunc(func(obj interface{}, id string, ctx *web.EventContext) (err error) {
  1551. this := obj.(*Template)
  1552. err = db.Transaction(func(tx *gorm.DB) (inerr error) {
  1553. if inerr = gorm2op.DataOperator(tx).Save(obj, id, ctx); inerr != nil {
  1554. return
  1555. }
  1556. if l10nON && strings.Contains(ctx.R.RequestURI, l10n_view.DoLocalize) {
  1557. fromID := ctx.R.Context().Value(l10n_view.FromID).(string)
  1558. fromLocale := ctx.R.Context().Value(l10n_view.FromLocale).(string)
  1559. var fromIDInt int
  1560. fromIDInt, err = strconv.Atoi(fromID)
  1561. if err != nil {
  1562. return
  1563. }
  1564. if inerr = b.localizeContainersToAnotherPage(tx, fromIDInt, "tpl", fromLocale, int(this.ID), "tpl", this.GetLocale()); inerr != nil {
  1565. panic(inerr)
  1566. return
  1567. }
  1568. return
  1569. }
  1570. return
  1571. })
  1572. return
  1573. })
  1574. return
  1575. }
  1576. func sharedContainerSearcher(db *gorm.DB, mb *presets.ModelBuilder) presets.SearchFunc {
  1577. return func(obj interface{}, params *presets.SearchParams, ctx *web.EventContext) (r interface{}, totalCount int, err error) {
  1578. ilike := "ILIKE"
  1579. if db.Dialector.Name() == "sqlite" {
  1580. ilike = "LIKE"
  1581. }
  1582. wh := db.Model(obj)
  1583. if len(params.KeywordColumns) > 0 && len(params.Keyword) > 0 {
  1584. var segs []string
  1585. var args []interface{}
  1586. for _, c := range params.KeywordColumns {
  1587. segs = append(segs, fmt.Sprintf("%s %s ?", c, ilike))
  1588. args = append(args, fmt.Sprintf("%%%s%%", params.Keyword))
  1589. }
  1590. wh = wh.Where(strings.Join(segs, " OR "), args...)
  1591. }
  1592. for _, cond := range params.SQLConditions {
  1593. wh = wh.Where(strings.Replace(cond.Query, " ILIKE ", " "+ilike+" ", -1), cond.Args...)
  1594. }
  1595. locale, _ := l10n.IsLocalizableFromCtx(ctx.R.Context())
  1596. var c int64
  1597. if err = wh.Select("count(display_name)").Where("shared = true AND locale_code = ?", locale).Group("display_name,model_name,model_id").Count(&c).Error; err != nil {
  1598. return
  1599. }
  1600. totalCount = int(c)
  1601. if params.PerPage > 0 {
  1602. wh = wh.Limit(int(params.PerPage))
  1603. page := params.Page
  1604. if page == 0 {
  1605. page = 1
  1606. }
  1607. offset := (page - 1) * params.PerPage
  1608. wh = wh.Offset(int(offset))
  1609. }
  1610. if err = wh.Select("display_name,model_name,model_id").Find(obj).Error; err != nil {
  1611. return
  1612. }
  1613. r = reflect.ValueOf(obj).Elem().Interface()
  1614. return
  1615. }
  1616. }
  1617. func (b *Builder) ContainerByName(name string) (r *ContainerBuilder) {
  1618. for _, cb := range b.containerBuilders {
  1619. if cb.name == name {
  1620. return cb
  1621. }
  1622. }
  1623. panic(fmt.Sprintf("No container: %s", name))
  1624. }
  1625. type ContainerBuilder struct {
  1626. builder *Builder
  1627. name string
  1628. mb *presets.ModelBuilder
  1629. model interface{}
  1630. modelType reflect.Type
  1631. renderFunc RenderFunc
  1632. cover string
  1633. }
  1634. func (b *Builder) RegisterContainer(name string) (r *ContainerBuilder) {
  1635. r = &ContainerBuilder{
  1636. name: name,
  1637. builder: b,
  1638. }
  1639. b.containerBuilders = append(b.containerBuilders, r)
  1640. return
  1641. }
  1642. func (b *ContainerBuilder) Model(m interface{}) *ContainerBuilder {
  1643. b.model = m
  1644. b.mb = b.builder.ps.Model(m)
  1645. val := reflect.ValueOf(m)
  1646. if val.Kind() != reflect.Ptr {
  1647. panic("model pointer type required")
  1648. }
  1649. b.modelType = val.Elem().Type()
  1650. b.configureRelatedOnlinePagesTab()
  1651. return b
  1652. }
  1653. func (b *ContainerBuilder) URIName(uri string) *ContainerBuilder {
  1654. if b.mb == nil {
  1655. return b
  1656. }
  1657. b.mb.URIName(uri)
  1658. return b
  1659. }
  1660. func (b *ContainerBuilder) GetModelBuilder() *presets.ModelBuilder {
  1661. return b.mb
  1662. }
  1663. func (b *ContainerBuilder) RenderFunc(v RenderFunc) *ContainerBuilder {
  1664. b.renderFunc = v
  1665. return b
  1666. }
  1667. func (b *ContainerBuilder) Cover(v string) *ContainerBuilder {
  1668. b.cover = v
  1669. return b
  1670. }
  1671. func (b *ContainerBuilder) NewModel() interface{} {
  1672. return reflect.New(b.modelType).Interface()
  1673. }
  1674. func (b *ContainerBuilder) ModelTypeName() string {
  1675. return b.modelType.String()
  1676. }
  1677. func (b *ContainerBuilder) Editing(vs ...interface{}) *presets.EditingBuilder {
  1678. return b.mb.Editing(vs...)
  1679. }
  1680. func (b *ContainerBuilder) configureRelatedOnlinePagesTab() {
  1681. eb := b.mb.Editing()
  1682. eb.AppendTabsPanelFunc(func(obj interface{}, ctx *web.EventContext) h.HTMLComponent {
  1683. if ctx.R.FormValue(paramOpenFromSharedContainer) != "1" {
  1684. return nil
  1685. }
  1686. pmsgr := i18n.MustGetModuleMessages(ctx.R, presets.CoreI18nModuleKey, Messages_en_US).(*presets.Messages)
  1687. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
  1688. id, err := reflectutils.Get(obj, "id")
  1689. if err != nil {
  1690. panic(err)
  1691. }
  1692. pages := []*Page{}
  1693. pageTable := (&Page{}).TableName()
  1694. containerTable := (&Container{}).TableName()
  1695. err = b.builder.db.Model(&Page{}).
  1696. Joins(fmt.Sprintf(`inner join %s on
  1697. %s.id = %s.page_id
  1698. and %s.version = %s.page_version
  1699. and %s.locale_code = %s.locale_code`,
  1700. containerTable,
  1701. pageTable, containerTable,
  1702. pageTable, containerTable,
  1703. pageTable, containerTable,
  1704. )).
  1705. // FIXME: add container locale condition after container supports l10n
  1706. Where(fmt.Sprintf(`%s.status = ? and %s.model_id = ? and %s.model_name = ?`,
  1707. pageTable,
  1708. containerTable,
  1709. containerTable,
  1710. ), publish.StatusOnline, id, b.name).
  1711. Group(fmt.Sprintf(`%s.id,%s.version,%s.locale_code`, pageTable, pageTable, pageTable)).
  1712. Find(&pages).
  1713. Error
  1714. if err != nil {
  1715. panic(err)
  1716. }
  1717. var pageIDs []string
  1718. var pageListComps h.HTMLComponents
  1719. for _, p := range pages {
  1720. pid := p.PrimarySlug()
  1721. pageIDs = append(pageIDs, pid)
  1722. statusVar := fmt.Sprintf(`republish_status_%s`, strings.Replace(pid, "-", "_", -1))
  1723. pageListComps = append(pageListComps, VListItem(
  1724. h.Text(fmt.Sprintf("%s (%s)", p.Title, pid)),
  1725. VSpacer(),
  1726. VIcon(fmt.Sprintf(`{{vars.%s}}`, statusVar)),
  1727. ).
  1728. Dense(true).
  1729. Attr(web.InitContextVars, fmt.Sprintf(`{%s: ""}`, statusVar)))
  1730. }
  1731. return h.Components(
  1732. VTab(h.Text(msgr.RelatedOnlinePages)),
  1733. VTabItem(
  1734. h.If(len(pages) > 0,
  1735. VList(pageListComps),
  1736. h.Div(
  1737. VSpacer(),
  1738. VBtn(msgr.RepublishAllRelatedOnlinePages).
  1739. Color("primary").
  1740. Attr("@click",
  1741. web.Plaid().
  1742. EventFunc(presets.OpenConfirmDialog).
  1743. Query(presets.ConfirmDialogConfirmEvent,
  1744. web.Plaid().
  1745. EventFunc(republishRelatedOnlinePagesEvent).
  1746. Query("ids", strings.Join(pageIDs, ",")).
  1747. Go(),
  1748. ).
  1749. Go(),
  1750. ),
  1751. ).Class("d-flex"),
  1752. ).Else(
  1753. h.Div(h.Text(pmsgr.ListingNoRecordToShow)).Class("text-center grey--text text--darken-2 mt-8"),
  1754. ),
  1755. ),
  1756. )
  1757. })
  1758. }
  1759. func republishRelatedOnlinePages(pageURL string) web.EventFunc {
  1760. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  1761. ids := strings.Split(ctx.R.FormValue("ids"), ",")
  1762. for _, id := range ids {
  1763. statusVar := fmt.Sprintf(`republish_status_%s`, strings.Replace(id, "-", "_", -1))
  1764. web.AppendVarsScripts(&r,
  1765. web.Plaid().
  1766. URL(pageURL).
  1767. EventFunc(views.RepublishEvent).
  1768. Query("id", id).
  1769. Query(views.ParamScriptAfterPublish, fmt.Sprintf(`vars.%s = "done"`, statusVar)).
  1770. Query("status_var", statusVar).
  1771. Go(),
  1772. fmt.Sprintf(`vars.%s = "pending"`, statusVar),
  1773. )
  1774. }
  1775. return r, nil
  1776. }
  1777. }
  1778. func (b *Builder) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  1779. if strings.Index(r.RequestURI, b.prefix+"/preview") >= 0 {
  1780. b.preview.ServeHTTP(w, r)
  1781. return
  1782. }
  1783. if strings.Index(r.RequestURI, path.Join(b.prefix, b.imagesPrefix)) >= 0 {
  1784. b.images.ServeHTTP(w, r)
  1785. return
  1786. }
  1787. b.ps.ServeHTTP(w, r)
  1788. }