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'>" +
        "            &nbsp;" +
        "        </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'>" +
        "                   &nbsp;" +
        "               </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 CMS 图像扩展

未找到 Piranha CMS 管理自定义控制器

Piranha CMS 自定义实体

Piranha CMS 想用新数据扩展用户?

有人可以解释 Piranha.vNext CMS 中的“块”概念吗?

Magento 2 - 如何在另一个 phtml 文件、xml 布局、静态块和 cms 页面中调用自定义 phtml 文件?