Piranha CMS - 自定义块不会保存
Posted
技术标签:
【中文标题】Piranha CMS - 自定义块不会保存【英文标题】:Piranha CMS - Custom block won't save 【发布时间】:2021-03-06 16:04:50 【问题描述】:对于 Piranha 和 Vue 来说非常陌生,但对于 .Net Core 来说却不是。试图让我了解如何创建自定义块。我创建了一个新块,试图将 htmlBlock 和 ImageBlock 结合起来:
using Piranha.Extend;
using Piranha.Extend.Blocks;
using Piranha.Extend.Fields;
namespace YR.Models.Piranha.Blocks
[BlockType(Name = "Card", Category = "Content", Icon = "fas fa-address-card", Component = "card-block")]
public class CardBlock : Block
public ImageField ImgBody get; set;
public SelectField<ImageAspect> Aspect get; set; = new SelectField<ImageAspect>();
public HtmlField HtmlBody get; set;
public override string GetTitle()
if (ImgBody != null && ImgBody.Media != null)
return ImgBody.Media.Filename;
return "No image selected";
如果我在 BlockTypeAttribute 中省略了 Component 属性,则该块可以工作,将图像和内容保存到草稿中,并在发布时完美地更新站点。为了获得管理器的完整体验,我还尝试构建一个 Vue 组件,它只是结合了 html-block.vue 和 image-block.vue 组件。
这是 Vue 组件的内容:
Vue.component("card-block",
props: ["uid", "toolbar", "model"],
data: function ()
return
imgBody: this.model.imgBody.value,
htmlBody: this.model.htmlBody.value
;
,
methods:
clear: function ()
// clear media from block
,
onBlur: function (e)
this.model.htmlBody.value = e.target.innerHTML;
,
onChange: function (data)
this.model.htmlBody.value = data;
,
remove: function ()
this.model.imgBody.id = null;
this.model.imgBody.media = null;
,
select: function ()
if (this.model.imgBody.media != null)
piranha.mediapicker.open(this.update, "Image", this.model.imgBody.media.folderId);
else
piranha.mediapicker.openCurrentFolder(this.update, "Image");
,
update: function (media)
if (media.type === "Image")
this.model.imgBody.id = media.id;
this.model.imgBody.media = media;
// Tell parent that title has been updated
this.$emit('update-title',
uid: this.uid,
title: this.model.imgBody.media.filename
);
else
console.log("No image was selected");
,
computed:
isEmpty: function ()
return
htmlBody: piranha.utils.isEmptyHtml(this.model.htmlBody.value),
imgBody: this.model.imgBody.media == null
,
mediaUrl: function ()
if (this.model.imgBody.media != null)
return piranha.utils.formatUrl(this.model.imgBody.media.publicUrl);
else
return piranha.utils.formatUrl("~/manager/assets/img/empty-image.png");
,
mounted: function ()
piranha.editor.addInline(this.uid, this.toolbar, this.onChange);
this.model.imgBody.getTitle = function ()
if (this.model.imgBody.media != null)
return this.model.imgBody.media.filename;
else
return "No image selected";
;
,
beforeDestroy: function ()
piranha.editor.remove(this.uid);
,
template:
"<div class='block-body has-media-picker rounded' :class=' empty: isEmpty '>" +
" <img class='rounded' :src='mediaUrl'>" +
" <div class='media-picker'>" +
" <div class='btn-group float-right'>" +
" <button :id='uid' class='btn btn-info btn-aspect text-center' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>" +
" <i v-if='model.aspect.value === 0' class='fas fa-cog'></i>" +
" <img v-else :src='iconUrl'>" +
" </button>" +
" <div class='dropdown-menu aspect-menu' :aria-labelledby='uid'>" +
" <label class='mb-0'> piranha.resources.texts.aspectLabel </label>" +
" <div class='dropdown-divider'></div>" +
" <a v-on:click.prevent='selectAspect(0)' class='dropdown-item' :class=' active: isAspectSelected(0) ' href='#'>" +
" <img :src='piranha.utils.formatUrl('~/manager/assets/img/icons/img-original.svg')'><span> piranha.resources.texts.aspectOriginal </span>" +
" </a>" +
" <a v-on:click.prevent='selectAspect(1)' class='dropdown-item' :class=' active: isAspectSelected(1) ' href='#'>" +
" <img :src='piranha.utils.formatUrl('~/manager/assets/img/icons/img-original.svg')'><span> piranha.resources.texts.aspectOriginal </span>" +
" </a>" +
" <a v-on:click.prevent='selectAspect(2)' class='dropdown-item' :class=' active: isAspectSelected(2) ' href='#'>" +
" <img :src='piranha.utils.formatUrl('~/manager/assets/img/icons/img-original.svg')'><span> piranha.resources.texts.aspectOriginal </span>" +
" </a>" +
" <a v-on:click.prevent='selectAspect(3)' class='dropdown-item' :class=' active: isAspectSelected(3) ' href='#'>" +
" <img :src='piranha.utils.formatUrl('~/manager/assets/img/icons/img-original.svg')'><span> piranha.resources.texts.aspectOriginal </span>" +
" </a>" +
" <a v-on:click.prevent='selectAspect(4)' class='dropdown-item' :class=' active: isAspectSelected(4) ' href='#'>" +
" <img :src='piranha.utils.formatUrl('~/manager/assets/img/icons/img-original.svg')'><span> piranha.resources.texts.aspectOriginal </span>" +
" </a>" +
" </div>" +
" <button v-on:click.prevent='select' class='btn btn-primary text-center'>" +
" <i class='fas fa-plus'></i>" +
" </button>" +
" <button v-on:click.prevent='remove' class='btn btn-danger text-center'>" +
" <i class='fas fa-times'></i>" +
" </button>" +
" </div>" +
" <div class='card text-left'>" +
" <div class='card-body' v-if='isEmpty'>" +
" " +
" </div>" +
" <div class='card-body' v-else>" +
" model.body.media.filename " +
" </div>" +
" </div>" +
"</div>" +
" <div contenteditable='true' :id='uid' spellcheck='false' v-html='htmlBody' v-on:blur='onBlur'></div>" +
"</div>"
);
它基本上是我在 GitHub 上的 repo 中找到的两个 Vue 组件的融合,但我已经对其进行了一些调整,以使其不会在 DevTools 控制台中出现错误。如果我能通过保存这些项目,我会重新审视这些项目。
所以,这是我对@tidyui 或任何成功实现类似这样的人的问题:
我这样做的方式是否正确?我只想要三列,每列将包含我的 CardBlock,它下面有一张图片和一个内容简介,但我希望 CardBlock 是一个单元(有点像引导卡)。有没有办法在不创建我自己的块的情况下做到这一点?我研究了嵌套 BlockGroup,但很快发现这是不可能的。
如果我在正确的轨道上,我需要帮助来解决我在尝试保存草稿时遇到的错误。该错误与Save ImageBlock error #1117 相同,似乎已在 8.2 中修复。我在 8.4.2。
我非常喜欢为 .Net Core 构建 CMS 的想法,而 Piranha 让我为自己创建的 CMS 大吃一惊。拜托,我只需要朝正确的方向一点点推动,我大部分时间都在这样做。
提前致谢, D
【问题讨论】:
在 SO 上找到另一个解决保存问题的帖子,但暂时让这个问题悬而未决,看看我的方法是否得到验证,或者我是否偏离了轨道。另外,只是想提请注意这个问题似乎已经从 8.2 到 8.4.2 重新出现,除非我弄错了。我找到的帖子是:***.com/questions/61922839/… 【参考方案1】:以防万一这有助于某人。事实证明,我在将两个块合并在一起时做得很差。将此归结为对 Piranha 和 Vue.js 的缺乏经验。我将文档中的代码与 repo 中的代码混合在一起。不要那样做——可以理解的是,文档仍然有点落后。我不会向开发人员扔石头,我真的很喜欢他们创造的东西,并将继续努力熟练地使用它。
下面是我想出的 Vue 组件。可能仍然需要进行一些调整以更好地分离 Image-Block 和 Html-Block 代码,但它现在可以工作、保存并且不会在控制台中引发错误。
/*global
piranha
*/
Vue.component("card-block",
props: ["uid", "toolbar", "model"],
data: function ()
return
imgBody: this.model.imgBody.value,
htmlBody: this.model.htmlBody.value
;
,
methods:
clear: function ()
// clear media from block
,
onBlur: function (e)
this.model.htmlBody.value = e.target.innerHTML;
,
onChange: function (data)
this.model.htmlBody.value = data;
,
select: function ()
if (this.model.imgBody.media != null)
piranha.mediapicker.open(this.update, "Image", this.model.imgBody.media.folderId);
else
piranha.mediapicker.openCurrentFolder(this.update, "Image");
,
remove: function ()
this.model.imgBody.id = null;
this.model.imgBody.media = null;
,
update: function (media)
if (media.type === "Image")
this.model.imgBody.id = media.id;
this.model.imgBody.media =
id: media.id,
folderId: media.folderId,
type: media.type,
filename: media.filename,
contentType: media.contentType,
publicUrl: media.publicUrl,
;
// Tell parent that title has been updated
this.$emit('update-title',
uid: this.uid,
title: this.model.imgBody.media.filename
);
else
console.log("No image was selected");
,
selectAspect: function (val)
this.model.aspect.value = val;
,
isAspectSelected(val)
return this.model.aspect.value === val;
,
computed:
isImgEmpty: function (e)
return this.model.imgBody.media == null;
,
isHtmlEmpty: function ()
return piranha.utils.isEmptyHtml(this.model.htmlBody.value);
,
mediaUrl: function ()
if (this.model.imgBody.media != null)
return piranha.utils.formatUrl(this.model.imgBody.media.publicUrl);
else
return piranha.utils.formatUrl("~/manager/assets/img/empty-image.png");
,
iconUrl: function ()
if (this.model.aspect.value > 0)
if (this.model.aspect.value === 1 || this.model.aspect.value === 3)
return piranha.utils.formatUrl("~/manager/assets/img/icons/img-landscape.svg");
else if (this.model.aspect.value == 2)
return piranha.utils.formatUrl("~/manager/assets/img/icons/img-portrait.svg");
else if (this.model.aspect.value == 4)
return piranha.utils.formatUrl("~/manager/assets/img/icons/img-square.svg");
return null;
,
mounted: function ()
piranha.editor.addInline(this.uid, this.toolbar, this.onChange);
this.model.getTitle = function ()
if (this.model.imgBody.media != null)
return this.model.imgBody.media.filename;
else
return "No image selected";
;
,
beforeDestroy: function ()
piranha.editor.remove(this.uid);
,
template:
"<div class='block-body has-media-picker rounded' :class=' empty: isImgEmpty '>" +
" <div class='image-block'>" +
" <img class='rounded' :src='mediaUrl'>" +
" <div class='media-picker'>" +
" <div class='btn-group float-right'>" +
" <button :id='uid + \"-aspect\"' class='btn btn-info btn-aspect text-center' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>" +
" <i v-if='model.aspect.value === 0' class='fas fa-cog'></i>" +
" <img v-else :src='iconUrl'>" +
" </button>" +
" <div class='dropdown-menu aspect-menu' :aria-labelledby='uid + \"-aspect\"'>" +
" <label class='mb-0'> piranha.resources.texts.aspectLabel </label>" +
" <div class='dropdown-divider'></div>" +
" <a v-on:click.prevent='selectAspect(0)' class='dropdown-item' :class=' active: isAspectSelected(0) ' href='#'>" +
" <img :src='piranha.utils.formatUrl(\"~/manager/assets/img/icons/img-original.svg\")'><span> piranha.resources.texts.aspectOriginal </span>" +
" </a>" +
" <a v-on:click.prevent='selectAspect(1)' class='dropdown-item' :class=' active: isAspectSelected(1) ' href='#'>" +
" <img :src='piranha.utils.formatUrl(\"~/manager/assets/img/icons/img-landscape.svg\")'><span> piranha.resources.texts.aspectLandscape </span>" +
" </a>" +
" <a v-on:click.prevent='selectAspect(2)' class='dropdown-item' :class=' active: isAspectSelected(2) ' href='#'>" +
" <img :src='piranha.utils.formatUrl(\"~/manager/assets/img/icons/img-portrait.svg\")'><span> piranha.resources.texts.aspectPortrait </span>" +
" </a>" +
" <a v-on:click.prevent='selectAspect(3)' class='dropdown-item' :class=' active: isAspectSelected(3) ' href='#'>" +
" <img :src='piranha.utils.formatUrl(\"~/manager/assets/img/icons/img-landscape.svg\")'><span> piranha.resources.texts.aspectWidescreen </span>" +
" </a>" +
" <a v-on:click.prevent='selectAspect(4)' class='dropdown-item' :class=' active: isAspectSelected(4) ' href='#'>" +
" <img :src='piranha.utils.formatUrl(\"~/manager/assets/img/icons/img-square.svg\")'><span> piranha.resources.texts.aspectSquare </span>" +
" </a>" +
" </div>" +
" <button v-on:click.prevent='select' class='btn btn-primary text-center'>" +
" <i class='fas fa-plus'></i>" +
" </button>" +
" <button v-on:click.prevent='remove' class='btn btn-danger text-center'>" +
" <i class='fas fa-times'></i>" +
" </button>" +
" </div>" +
" <div class='card text-left'>" +
" <div class='card-body' v-if='isImgEmpty'>" +
" " +
" </div>" +
" <div class='card-body' v-else>" +
" model.imgBody.media.filename " +
" </div>" +
" </div>" +
" </div>" +
" </div>" +
" <br />" +
" <div class='html-block'>" +
" <div class='block-body border rounded' :class=' empty: isHtmlEmpty ' >" +
" <div contenteditable='true' :id='uid' v-html='htmlBody' v-on:blur='onBlur'></div> " +
" </div>" +
" </div>" +
"</div>"
);
我仍然很想得到一些确认,我没有做所有这些不必要的事情。如果有更好的方法,请不要犹豫。
【讨论】:
以上是关于Piranha CMS - 自定义块不会保存的主要内容,如果未能解决你的问题,请参考以下文章
有人可以解释 Piranha.vNext CMS 中的“块”概念吗?
Magento 2 - 如何在另一个 phtml 文件、xml 布局、静态块和 cms 页面中调用自定义 phtml 文件?