builder.go 60 KB

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