builder.go 60 KB

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