Skip to content

Commit 19ffc13

Browse files
committed
Topic Widgets: topics now can transformed to widgets and placed in any widget zone
1 parent 9f8b905 commit 19ffc13

18 files changed

Lines changed: 391 additions & 53 deletions

File tree

src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
14
using SmartStore.Core.Domain.Localization;
25
using SmartStore.Core.Domain.Stores;
36

@@ -57,5 +60,44 @@ public partial class Topic : BaseEntity, ILocalizedEntity, IStoreMappingSupporte
5760
/// Gets or sets a value indicating whether the entity is limited/restricted to certain stores
5861
/// </summary>
5962
public bool LimitedToStores { get; set; }
63+
64+
/// <summary>
65+
/// Gets or sets a value indicating whether the topic should also be rendered as a generic html widget
66+
/// </summary>
67+
public bool RenderAsWidget { get; set; }
68+
69+
/// <summary>
70+
/// Gets or sets the widget zone name
71+
/// </summary>
72+
public string WidgetZone { get; set; }
73+
74+
/// <summary>
75+
/// Gets or sets a value indicating whether the title should be displayed in the widget block
76+
/// </summary>
77+
public bool WidgetShowTitle { get; set; }
78+
79+
/// <summary>
80+
/// Gets or sets a value indicating whether the widget block should have borders
81+
/// </summary>
82+
public bool WidgetBordered { get; set; }
83+
84+
/// <summary>
85+
/// Gets or sets the sort order (relevant for widgets)
86+
/// </summary>
87+
public int Priority { get; set; }
88+
89+
/// <summary>
90+
/// Helper function which gets the comma-separated <c>WidgetZone</c> property as list of strings
91+
/// </summary>
92+
/// <returns></returns>
93+
public IEnumerable<string> GetWidgetZones()
94+
{
95+
if (this.WidgetZone.IsEmpty())
96+
{
97+
return Enumerable.Empty<string>();
98+
}
99+
100+
return this.WidgetZone.Trim().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
101+
}
60102
}
61103
}

src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,49 @@ public static string Prettify(this string value, bool allowSpace = false, char[]
856856
return (res.Length > 0 ? res : "null");
857857
}
858858

859+
public static string SanitizeHtmlId(this string value)
860+
{
861+
if (string.IsNullOrWhiteSpace(value))
862+
{
863+
return null;
864+
}
865+
StringBuilder builder = new StringBuilder(value.Length);
866+
int index = value.IndexOf("#");
867+
int num2 = value.LastIndexOf("#");
868+
if (num2 > index)
869+
{
870+
ReplaceInvalidHtmlIdCharacters(value.Substring(0, index), builder);
871+
builder.Append(value.Substring(index, (num2 - index) + 1));
872+
ReplaceInvalidHtmlIdCharacters(value.Substring(num2 + 1), builder);
873+
}
874+
else
875+
{
876+
ReplaceInvalidHtmlIdCharacters(value, builder);
877+
}
878+
return builder.ToString();
879+
}
880+
881+
private static bool IsValidHtmlIdCharacter(char c)
882+
{
883+
return (((c != '?') && (c != '!')) && ((c != '#') && (c != '.')) && (c != ' '));
884+
}
885+
886+
private static void ReplaceInvalidHtmlIdCharacters(string part, StringBuilder builder)
887+
{
888+
for (int i = 0; i < part.Length; i++)
889+
{
890+
char c = part[i];
891+
if (IsValidHtmlIdCharacter(c))
892+
{
893+
builder.Append(c);
894+
}
895+
else
896+
{
897+
builder.Append('_');
898+
}
899+
}
900+
}
901+
859902
public static string Sha(this string value)
860903
{
861904
if (value.HasValue())

src/Libraries/SmartStore.Services/Catalog/ProductService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,10 @@ public virtual IList<Product> GetProductsByIds(int[] productIds)
254254
where productIds.Contains(p.Id)
255255
select p;
256256
var products = query.ToList();
257+
257258
//sort by passed identifiers
258259
var sortedProducts = new List<Product>();
260+
sortedProducts.AddRange(products);
259261
foreach (int id in productIds)
260262
{
261263
var product = products.Find(x => x.Id == id);

src/Libraries/SmartStore.Services/Catalog/RecentlyViewedProductsService.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,12 @@ public virtual IList<Product> GetRecentlyViewedProducts(int number)
9191
{
9292
var products = new List<Product>();
9393
var productIds = GetRecentlyViewedProductsIds(number);
94-
foreach (var product in _productService.GetProductsByIds(productIds.ToArray()))
95-
if (product.Published && !product.Deleted)
96-
products.Add(product);
94+
var recentlyViewedProducts = _productService.GetProductsByIds(productIds.ToArray()).Where(x => x.Published && !x.Deleted);
95+
96+
foreach (var product in recentlyViewedProducts)
97+
{
98+
products.Add(product);
99+
}
97100
return products;
98101
}
99102

src/Libraries/SmartStore.Services/Topics/TopicService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ orderby tGroup.Key
107107
public virtual IList<Topic> GetAllTopics(int storeId)
108108
{
109109
var query = _topicRepository.Table;
110-
query = query.OrderBy(t => t.SystemName);
110+
query = query.OrderBy(t => t.Priority).ThenBy(t => t.SystemName);
111111

112112
//Store mapping
113113
if (storeId > 0)

src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentRenderer.cs

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -84,45 +84,7 @@ public string ToHtmlString()
8484

8585
protected string SanitizeId(string id)
8686
{
87-
if (string.IsNullOrWhiteSpace(id))
88-
{
89-
return null;
90-
}
91-
StringBuilder builder = new StringBuilder(id.Length);
92-
int index = id.IndexOf("#");
93-
int num2 = id.LastIndexOf("#");
94-
if (num2 > index)
95-
{
96-
ReplaceInvalidCharacters(id.Substring(0, index), builder);
97-
builder.Append(id.Substring(index, (num2 - index) + 1));
98-
ReplaceInvalidCharacters(id.Substring(num2 + 1), builder);
99-
}
100-
else
101-
{
102-
ReplaceInvalidCharacters(id, builder);
103-
}
104-
return builder.ToString();
105-
}
106-
107-
private static bool IsValidCharacter(char c)
108-
{
109-
return (((c != '?') && (c != '!')) && ((c != '#') && (c != '.')));
110-
}
111-
112-
private static void ReplaceInvalidCharacters(string part, StringBuilder builder)
113-
{
114-
for (int i = 0; i < part.Length; i++)
115-
{
116-
char c = part[i];
117-
if (IsValidCharacter(c))
118-
{
119-
builder.Append(c);
120-
}
121-
else
122-
{
123-
builder.Append(HtmlHelper.IdAttributeDotReplacement);
124-
}
125-
}
87+
return id.SanitizeHtmlId();
12688
}
12789

12890
}

src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicModel.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,27 @@ namespace SmartStore.Admin.Models.Topics
1212
[Validator(typeof(TopicValidator))]
1313
public class TopicModel : EntityModelBase, ILocalizedModel<TopicLocalizedModel>
1414
{
15+
#region widget zone names
16+
private readonly static string[] s_widgetZones = new string[] {
17+
"main_column_before",
18+
"main_column_after",
19+
"left_side_column_before",
20+
"left_side_column_before",
21+
"right_side_column_before",
22+
"right_side_column_before",
23+
"notifications",
24+
"body_start_html_tag_after",
25+
"content_before",
26+
"content_after",
27+
"body_end_html_tag_before"
28+
};
29+
#endregion
30+
1531
public TopicModel()
1632
{
1733
Locales = new List<TopicLocalizedModel>();
1834
AvailableStores = new List<StoreModel>();
35+
AvailableWidgetZones = s_widgetZones;
1936
}
2037

2138
//Store mapping
@@ -61,7 +78,24 @@ public TopicModel()
6178
[SmartResourceDisplayName("Admin.ContentManagement.Topics.Fields.MetaTitle")]
6279
[AllowHtml]
6380
public string MetaTitle { get; set; }
64-
81+
82+
[SmartResourceDisplayName("Admin.ContentManagement.Topics.Fields.RenderAsWidget")]
83+
public bool RenderAsWidget { get; set; }
84+
85+
[SmartResourceDisplayName("Admin.ContentManagement.Topics.Fields.WidgetZone")]
86+
public string WidgetZone { get; set; }
87+
88+
[SmartResourceDisplayName("Admin.ContentManagement.Topics.Fields.WidgetShowTitle")]
89+
public bool WidgetShowTitle { get; set; }
90+
91+
[SmartResourceDisplayName("Admin.ContentManagement.Topics.Fields.WidgetBordered")]
92+
public bool WidgetBordered { get; set; }
93+
94+
[SmartResourceDisplayName("Admin.ContentManagement.Topics.Fields.Priority")]
95+
public int Priority { get; set; }
96+
97+
public string[] AvailableWidgetZones { get; private set; }
98+
6599
public IList<TopicLocalizedModel> Locales { get; set; }
66100
}
67101

src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@
5454
.Template(item => @Html.SymbolForBool(item.IncludeInSitemap))
5555
.ClientTemplate(@Html.SymbolForBool("IncludeInSitemap"))
5656
.Centered();
57+
columns.Bound(x => x.RenderAsWidget)
58+
.Width(100)
59+
.Template(item => @Html.SymbolForBool(item.RenderAsWidget))
60+
.ClientTemplate(@Html.SymbolForBool("RenderAsWidget"))
61+
.Centered();
62+
columns.Bound(x => x.WidgetZone);
63+
columns.Bound(x => x.Priority);
5764
columns.Bound(x => x.Id)
5865
.Width(50)
5966
.Centered()

src/Presentation/SmartStore.Web/Administration/Views/Topic/_CreateOrUpdate.cshtml

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,35 @@
88
$(document).ready(function () {
99
$("#@Html.FieldIdFor(model => model.IsPasswordProtected)").click(togglePassword);
1010
togglePassword();
11+
12+
$("#@Html.FieldIdFor(model => model.RenderAsWidget)").click(toggleRenderAsWidget);
13+
toggleRenderAsWidget();
1114
});
15+
1216
function togglePassword() {
1317
if ($('#@Html.FieldIdFor(model => model.IsPasswordProtected)').is(':checked')) {
1418
$('#pnlPasswordEnabled').show();
1519
}
1620
else {
1721
$('#pnlPasswordEnabled').hide();
1822
}
19-
}
23+
}
24+
25+
function toggleRenderAsWidget() {
26+
if ($('#@Html.FieldIdFor(model => model.RenderAsWidget)').is(':checked')) {
27+
$('#pnlWidgetZone').show();
28+
$('#pnlWidgetShowTitle').show();
29+
$('#pnlWidgetBordered').show();
30+
$('#pnlPriority').show();
31+
}
32+
else {
33+
$('#pnlWidgetZone').hide();
34+
$('#pnlWidgetShowTitle').hide();
35+
$('#pnlWidgetBordered').hide();
36+
$('#pnlPriority').hide();
37+
}
38+
}
39+
2040
</script>
2141
@Html.HiddenFor(model => model.Id)
2242
@Html.SmartStore().TabStrip().Name("topic-edit").Items(x =>
@@ -78,7 +98,60 @@
7898
</td>
7999
</tr>
80100
}
101+
<tr>
102+
<td colspan="2">
103+
<hr />
104+
</td>
105+
</tr>
106+
<tr>
107+
<td class="adminTitle">
108+
@Html.SmartLabelFor(model => model.RenderAsWidget)
109+
</td>
110+
<td class="adminData">
111+
@Html.EditorFor(x => x.RenderAsWidget)
112+
@Html.ValidationMessageFor(model => model.RenderAsWidget)
113+
</td>
114+
</tr>
115+
<tr id="pnlWidgetZone">
116+
<td class="adminTitle">
117+
@Html.SmartLabelFor(model => model.WidgetZone)
118+
</td>
119+
<td class="adminData">
120+
@Html.TextBoxFor(x => x.WidgetZone, new { @style = "width:95%" })
121+
@Html.ValidationMessageFor(model => model.WidgetZone)
122+
</td>
123+
</tr>
124+
<tr id="pnlWidgetShowTitle">
125+
<td class="adminTitle">
126+
@Html.SmartLabelFor(model => model.WidgetShowTitle)
127+
</td>
128+
<td class="adminData">
129+
@Html.EditorFor(x => x.WidgetShowTitle)
130+
@Html.ValidationMessageFor(model => model.WidgetShowTitle)
131+
</td>
132+
</tr>
133+
<tr id="pnlWidgetBordered">
134+
<td class="adminTitle">
135+
@Html.SmartLabelFor(model => model.WidgetBordered)
136+
</td>
137+
<td class="adminData">
138+
@Html.EditorFor(x => x.WidgetBordered)
139+
@Html.ValidationMessageFor(model => model.WidgetBordered)
140+
</td>
141+
</tr>
142+
<tr id="pnlPriority">
143+
<td class="adminTitle">
144+
@Html.SmartLabelFor(model => model.Priority)
145+
</td>
146+
<td class="adminData">
147+
@Html.EditorFor(x => x.Priority)
148+
@Html.ValidationMessageFor(model => model.Priority)
149+
</td>
150+
</tr>
81151
</table>
152+
153+
<br />
154+
82155
@(Html.LocalizedEditor<TopicModel, TopicLocalizedModel>("topic-info-localized",
83156
@<table class="adminContent">
84157
<tr>

src/Presentation/SmartStore.Web/Administration/sitemap.config

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@
5353

5454
<siteMapNode id="cms" title="Content&#160;Management" resKey="Admin.ContentManagement" permissionNames="ManagePolls, ManageNews, ManageBlog, ManageWidgets, ManageTopics, ManageForums, ManageMessageTemplates"
5555
iconClass="sm-icon-folder" imageUrl="~/Administration/Content/images/ap-cms-white.png">
56-
<siteMapNode id="content-slider" title="Content slider" resKey="Admin.ContentManagement.ContentSlider" permissionNames="ContentSlider" controller="ContentSlider" action="Index" area="Admin" />
57-
<siteMapNode id="polls" title="Polls" resKey="Admin.ContentManagement.Polls" permissionNames="ManagePolls" controller="Poll" action="List" area="Admin" />
56+
<siteMapNode id="topics" title="Pages &amp; Topics" resKey="Admin.ContentManagement.Topics" permissionNames="ManageTopics" controller="Topic" action="List" area="Admin" />
5857
<siteMapNode id="news" title="News" resKey="Admin.ContentManagement.News" permissionNames="ManageNews" area="Admin" >
5958
<siteMapNode id="news-items" title="News items" resKey="Admin.ContentManagement.News.NewsItems" controller="News" action="List" area="Admin" />
6059
<siteMapNode id="news-comments" title="News comments" resKey="Admin.ContentManagement.News.Comments" controller="News" action="Comments" area="Admin" />
@@ -63,10 +62,11 @@
6362
<siteMapNode id="blog-posts" title="Blog posts" resKey="Admin.ContentManagement.Blog.BlogPosts" controller="Blog" action="List" area="Admin" />
6463
<siteMapNode id="blog-comments" title="Blog comments" resKey="Admin.ContentManagement.Blog.Comments" controller="Blog" action="Comments" area="Admin" />
6564
</siteMapNode>
66-
<siteMapNode id="widgets" title="Widgets" resKey="Admin.ContentManagement.Widgets" permissionNames="ManageWidgets" controller="Widget" action="List" area="Admin" />
67-
<siteMapNode id="topics" title="Topics" resKey="Admin.ContentManagement.Topics" permissionNames="ManageTopics" controller="Topic" action="List" area="Admin" />
65+
<siteMapNode id="content-slider" title="Content slider" resKey="Admin.ContentManagement.ContentSlider" permissionNames="ContentSlider" controller="ContentSlider" action="Index" area="Admin" />
6866
<siteMapNode id="forums" title="Manage forums" resKey="Admin.ContentManagement.Forums" permissionNames="ManageForums" controller="Forum" action="List" area="Admin" />
6967
<siteMapNode id="message-templates" title="Message templates" resKey="Admin.ContentManagement.MessageTemplates" permissionNames="ManageMessageTemplates" controller="MessageTemplate" action="List" area="Admin" />
68+
<siteMapNode id="polls" title="Polls" resKey="Admin.ContentManagement.Polls" permissionNames="ManagePolls" controller="Poll" action="List" area="Admin" />
69+
<siteMapNode id="widgets" title="Widgets" resKey="Admin.ContentManagement.Widgets" permissionNames="ManageWidgets" controller="Widget" action="List" area="Admin" />
7070
</siteMapNode>
7171

7272
<siteMapNode id="configuration" title="Configuration" resKey="Admin.Configuration" permissionNames="ManageCountries,ManageLanguages,ManageSettings,ManagePaymentMethods,ManageExternalAuthenticationMethods,ManageTaxSettings,ManageShippingSettings,ManageCurrencies,ManageDeliveryTimes,ManageMeasures,ManageActivityLog,ManageACL,ManageSMSProviders,ManageEmailAccounts,ManageThemes,ManagePlugins"

0 commit comments

Comments
 (0)