Unfortunately there is no attribute you could use in your models to decorate Xhtml properties with. However youcould probably create one on your own. It's actually quite simple to create settings from code and add them to properties. I will probably write a blog post about it soon and make it as an attribute. In the meanwhile you could use following code, e.g. in an initializable module.
First create the setting:
var defaultSettings = new TinyMCESettings()
{
Width = 580,
Height = 300,
ToolbarRows = new List()
{
new ToolbarRow(new []
{
"bold",
"italic",
"separator",
"numlist",
"bullist",
"outdent",
"indent",
"epiquote",
"separator",
"undo",
"redo",
"separator",
"epilink",
"unlink",
"anchor",
"separator",
"image",
"epiimageeditor",
"separator",
"pastetext",
"cleanup",
"removeformat",
"separator",
"code",
"fullscreen"
}),
new ToolbarRow(new []
{
"formatselect",
"styleselect",
"table",
"row_props",
"cell_props",
"separator",
"row_before",
"row_after",
"delete_row",
"separator",
"col_before",
"col_after",
"delete_col",
"separator",
"split_cells",
"merge_cells"
})
}
};
Then you need to save it, in this case we will save it as a global setting and not just on a property:
var propertySettingsRepo = ServiceLocator.Current.GetInstance();
var settingsWrapper = propertySettingsRepo.GetGlobals(typeof(TinyMCESettings))
.FirstOrDefault(s => s.DisplayName == "Default");
if (settingsWrapper == null)
{
settingsWrapper = new PropertySettingsWrapper();
}
settingsWrapper.DisplayName = "Default";
settingsWrapper.IsDefault = true;
settingsWrapper.IsGlobal = true;
settingsWrapper.PropertySettings = defaultSsettings;
propertySettingsRepo.SaveGlobal(settingsWrapper);
Then we need to apply it to a property, this is however not necessary in our case because the IsDefault is set to true and will apply to all Xhtml properties anyways:
var propertyNames = new[] { "MainBody", "MainIntro" };
var propertyDefinitionRepo = ServiceLocator.Current.GetInstance();
var contentTypes = ServiceLocator.Current.GetInstance().List();
foreach (var contentType in contentTypes)
{
var propertyDefinitions = contentType.PropertyDefinitions.Where(pd =>
propertyNames.Any(propertyName => propertyName.Equals(pd.Name, StringComparison.OrdinalIgnoreCase)));
foreach (var property in propertyDefinitions.Where(definition => definition.SettingsID == Guid.Empty))
{
var settingsContainer = new PropertySettingsContainer(Guid.Empty);
settingsContainer.AddSettings(settingsWrapper);
propertySettingsRepo.Save(settingsContainer);
var writeableClone = property.CreateWritableClone();
writeableClone.SettingsID = settingsContainer.Id;
propertyDefinitionRepo.Save(writeableClone);
}
}
Hope this helps!
This could also be solved by only creating an EditorDescriptor that sets your programmatically created setting. No need to save it.
A blog post about this will come up this weekend.
Thanks Johan and Alf - we'll give this a go and let you know.
In the meantime you guys might like to see the latest EPiServer 7.5 sites's we've released:
...all on the latest 7.11 drop, using MVC 4 and extensive use of blocks, with an automated testing framework that mocks the various repositories in EPiServer to allow us to automate the functionality of our pages and blocks.
Loving EPiServer :)
Hi!
I can mention that we have been working on adding native support for this in the platform and unless we find some blocking issues during testing, this should be added during the coming month or so.
Hi Alf
Any news on the blog post on creating EditorDiscriptor for TinyMCE custom settings?
Hi, Sorry I ran into some problems about it in 7.5 comparing to my approach in 7.0/7.1
Need to gather my thoughts to EPiServer to see how the approach could for 7.5
Thanks for the reminder.
The code for the main product functionality is pretty much done. However, since the CMS core and UI packages has been split into separate releases and the new functionality affects both, it's currently in the process of being added to both packages. This might take a few weeks but hopefully, it will be out in September.
Hi Linus, Alf
Thank you both for the update. I have managed to do it using a Custom Editor Descriptor.
Has this been added to EPiServer now? If so, how do I do it, some kind of attribute?
Currently i've implemented it with an EditorDescriptor but I get an 404 on epiexternaltoolbar, if I remove that plugin with code, everything works as it should. When looking in the CMS.zip I can see that the folder epiexternaltoolbar doesn't exists in the location epi/TinyMCE is looking, the folder is located in the CMS.ZIP/Util/Editor/tinymce/plugins/epiexternaltoolbar folder but Epi is looking in the CMS.zip/9.4.4.0/ClientResources/editor/tiny_mce/plugins folder(where it doesnt exists).
Heres my code(with the epiexternaltoolbar workaround), basically a modified version of this https://gist.github.com/mattiasnorell/0daf1d851358ecb01f8c.
public class EditorButtonEditorDescriptor : EditorDescriptor { private const string Plugins = "plugins"; private const string EpiExternalToolBar = "epiexternaltoolbar"; public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes) { var attributeValue = metadata.Attributes.OfType<EditorButtonsAttribute>().SingleOrDefault(); if (attributeValue == null) return; var property = (PropertyData) metadata.Model; var content = metadata.FindOwnerContent(); var settings = content.GetPropertySettings<TinyMCESettings>(property.Name); settings.ToolbarRows.Clear(); settings.ToolbarRows.Add(attributeValue.ToolbarRow); var options = new TinyMCEInitOptions(TinyMCEInitOptions.InitType.EditMode, settings, content); if (options.InitOptions.ContainsKey(Plugins)) { var plugins = (string)options.InitOptions[Plugins]; options.InitOptions[Plugins] = RemoveEpiExternalToolbar(plugins); } metadata.EditorConfiguration[Plugins] = options.InitOptions; base.ModifyMetadata(metadata, attributes); } private static string RemoveEpiExternalToolbar(string plugins) { if (plugins.Contains(EpiExternalToolBar)) { var pluginsList = plugins.Split(',').ToList(); pluginsList.Remove(EpiExternalToolBar); return string.Join(",", pluginsList); } return plugins; } }
[AttributeUsage(AttributeTargets.Property)] public class EditorButtonsAttribute : Attribute { private ToolbarRow _toolbarRow = new ToolbarRow(); public EditorButtonsAttribute(params string[] toolbarRowButtons) { foreach (var toolbarRowButton in toolbarRowButtons) { _toolbarRow.Buttons.Add(toolbarRowButton); } } public ToolbarRow ToolbarRow { get { return _toolbarRow; } set { _toolbarRow = value; } } }
public class EditorialBlock : SiteBlockData { [Display(GroupName = SystemTabNames.Content)] [CultureSpecific] [EditorButtons(new[] { "bold" })] public virtual XhtmlString MainBody { get; set; } }
Did anyone ever manage to achieve this?
Josef-Ottosson's code isn't working for me - the TinyMCE renders with the default toolbar. The "epiexternaltoolbar" plugin wasn't being loaded for me (guessing it's a bug resolved in a later version of the CMS).
Also, this code didn't work either: https://gist.github.com/mattiasnorell/0daf1d851358ecb01f8c
It just renders a readonly version of the TinyMCE editor, with no toolbar options (regardless of what you pass in via the attribute).
Yes, this should work OOTB now.
[ServiceConfiguration(ServiceType = typeof(PropertySettings))] public class SimpleTinyMceSettings : PropertySettings<TinyMCESettings> { public SimpleTinyMceSettings() { this.DisplayName = "Simple settings"; } public override Guid ID { get { return new Guid("326c8ae8-685d-4387-872a-006fb45818d5"); } } public override TinyMCESettings GetPropertySettings() { return new TinyMCESettings { ContentCss = "/css/editor.css", Width = 580, Height = 300, //// NonVisualPlugins = new[] { "optimizededitor", "contextmenu" }, ToolbarRows = new List<ToolbarRow> { new ToolbarRow(new[] { TinyMCEButtons.Bold, TinyMCEButtons.SuperScript, TinyMCEButtons.SubScript, TinyMCEButtons.Separator, TinyMCEButtons.Undo, TinyMCEButtons.Redo, TinyMCEButtons.Separator, TinyMCEButtons.EPiServerLink, TinyMCEButtons.Unlink, TinyMCEButtons.Anchor, TinyMCEButtons.Separator, TinyMCEButtons.PasteText, TinyMCEButtons.CleanUp, TinyMCEButtons.RemoveFormat, TinyMCEButtons.Separator, TinyMCEButtons.Search, TinyMCEButtons.Replace, TinyMCEButtons.Separator, TinyMCEButtons.Code, TinyMCEButtons.Fullscreen }) } }; } }
And then decorate your properties like this:
[PropertySettings(typeof(SimpleTinyMceSettings))] [Display] public virtual XhtmlString TeaserBody { get; set; }
You can also make the setting default for all XHTML properties (those without PropertySettings attribute) by setting this.IsDefault = true; in the constructor.
That works, excellent.
Thanks Johan - may be worth you writing a blog post about that :-)
Or just read the manual https://world.episerver.com/documentation/developer-guides/CMS/Content/Properties/Property-settings/ ;)
Hi, I see from http://webhelp.episerver.com/cms/7.1/en/Content/CustomizeEditMode/SS_GlobalSettings.htm that through soft config in the CMS user interface it is possible to customise the toolbar for XHTMLString properties.
Is it possible to have this level of control in the C# code of my website? I.e. in some app startup code or even better declaratively through attributes on a defined page or block content type?
Thanks.