diff --git a/app/assets/javascripts/discourse/lib/transform-post.js.es6 b/app/assets/javascripts/discourse/lib/transform-post.js.es6
index 43aa804ca4..1aa662a853 100644
--- a/app/assets/javascripts/discourse/lib/transform-post.js.es6
+++ b/app/assets/javascripts/discourse/lib/transform-post.js.es6
@@ -222,7 +222,7 @@ export default function transformPost(
acted,
count,
canUndo: a.can_undo,
- canDeferFlags: a.can_defer_flags,
+ canIgnoreFlags: a.can_defer_flags,
description: actionDescription(action, acted, count)
};
});
diff --git a/app/assets/javascripts/discourse/widgets/actions-summary.js.es6 b/app/assets/javascripts/discourse/widgets/actions-summary.js.es6
index 50c6d8a9e2..d15a3d7ba7 100644
--- a/app/assets/javascripts/discourse/widgets/actions-summary.js.es6
+++ b/app/assets/javascripts/discourse/widgets/actions-summary.js.es6
@@ -63,15 +63,12 @@ createWidget("small-user-list", {
createWidget("action-link", {
tagName: "span.action-link",
+ template: hbs`{{attrs.text}}. `,
buildClasses(attrs) {
return attrs.className;
},
- html(attrs) {
- return h("a", [attrs.text, ". "]);
- },
-
click() {
this.sendWidgetAction(this.attrs.action);
}
@@ -82,56 +79,24 @@ createWidget("actions-summary-item", {
buildKey: attrs => `actions-summary-item-${attrs.id}`,
defaultState() {
- return { users: [] };
+ return { users: null };
},
- html(attrs, state) {
- const users = state.users;
+ template: hbs`
+ {{#if state.users}}
+ {{small-user-list users=state.users description=(concat "post.actions.people." attrs.action)}}
+ {{else}}
+ {{action-link action="whoActed" text=attrs.description}}
+ {{/if}}
- const result = [];
- const action = attrs.action;
+ {{#if attrs.canUndo}}
+ {{action-link action="undo" className="undo" text=(i18n (concat "post.actions.undo." attrs.action))}}
+ {{/if}}
- if (users.length === 0) {
- result.push(
- this.attach("action-link", {
- action: "whoActed",
- text: attrs.description
- })
- );
- } else {
- result.push(
- this.attach("small-user-list", {
- users,
- description: `post.actions.people.${action}`
- })
- );
- }
-
- if (attrs.canUndo) {
- result.push(
- this.attach("action-link", {
- action: "undo",
- className: "undo",
- text: I18n.t(`post.actions.undo.${action}`)
- })
- );
- }
-
- if (attrs.canDeferFlags) {
- const flagsDesc = I18n.t(`post.actions.defer_flags`, {
- count: attrs.count
- });
- result.push(
- this.attach("action-link", {
- action: "deferFlags",
- className: "defer-flags",
- text: flagsDesc
- })
- );
- }
-
- return result;
- },
+ {{#if attrs.canIgnoreFlags}}
+ {{action-link action="deferFlags" className="defer-flags" text=(i18n "post.actions.defer_flags" count=attrs.count)}}
+ {{/if}}
+ `,
whoActed() {
const attrs = this.attrs;
@@ -159,7 +124,7 @@ export default createWidget("actions-summary", {
tagName: "section.post-actions",
template: hbs`
{{#each attrs.actionsSummary as |as|}}
- {{attach widget="actions-summary-item" attrs=as}}
+ {{actions-summary-item attrs=as}}
{{/each}}
{{#if attrs.deleted_at}}
diff --git a/app/assets/javascripts/discourse/widgets/header-contents.js.es6 b/app/assets/javascripts/discourse/widgets/header-contents.js.es6
index 82bae85ec4..6f4c7089ec 100644
--- a/app/assets/javascripts/discourse/widgets/header-contents.js.es6
+++ b/app/assets/javascripts/discourse/widgets/header-contents.js.es6
@@ -4,9 +4,9 @@ import hbs from "discourse/widgets/hbs-compiler";
createWidget("header-contents", {
tagName: "div.contents.clearfix",
template: hbs`
- {{attach widget="home-logo" attrs=attrs}}
+ {{home-logo attrs=attrs}}
{{#if attrs.topic}}
- {{attach widget="header-topic-info" attrs=attrs}}
+ {{header-topic-info attrs=attrs}}
{{/if}}
{{yield}}
`
diff --git a/app/assets/javascripts/discourse/widgets/private-message-map.js.es6 b/app/assets/javascripts/discourse/widgets/private-message-map.js.es6
index 64274c4929..faf7ebe164 100644
--- a/app/assets/javascripts/discourse/widgets/private-message-map.js.es6
+++ b/app/assets/javascripts/discourse/widgets/private-message-map.js.es6
@@ -34,9 +34,9 @@ createWidget("pm-map-user-group", {
{{attrs.group.name}}
{{#if attrs.isEditing}}
- {{#if attrs.canRemoveAllowedUsers}}
- {{attach widget="pm-remove-group-link" attrs=attrs.group}}
- {{/if}}
+ {{#if attrs.canRemoveAllowedUsers}}
+ {{pm-remove-group-link attrs=attrs.group}}
+ {{/if}}
{{/if}}
`
});
diff --git a/app/assets/javascripts/discourse/widgets/user-menu.js.es6 b/app/assets/javascripts/discourse/widgets/user-menu.js.es6
index d1a37e4011..efd94baaa0 100644
--- a/app/assets/javascripts/discourse/widgets/user-menu.js.es6
+++ b/app/assets/javascripts/discourse/widgets/user-menu.js.es6
@@ -109,15 +109,12 @@ createWidget("user-menu-dismiss-link", {
template: hbs`
`
diff --git a/lib/javascripts/widget-hbs-compiler.js.es6 b/lib/javascripts/widget-hbs-compiler.js.es6
index a55dfaebf7..fc2280a335 100644
--- a/lib/javascripts/widget-hbs-compiler.js.es6
+++ b/lib/javascripts/widget-hbs-compiler.js.es6
@@ -5,27 +5,62 @@ function resolve(path) {
return path;
}
+function sexpValue(value) {
+ if (!value) {
+ return;
+ }
+
+ let pValue = value.original;
+ if (value.type === "StringLiteral") {
+ return JSON.stringify(pValue);
+ } else if (value.type === "SubExpression") {
+ return sexp(value);
+ }
+ return pValue;
+}
+
+function pairsToObj(pairs) {
+ let result = [];
+
+ pairs.forEach(p => {
+ result.push(`"${p.key}": ${sexpValue(p.value)}`);
+ });
+
+ return `{ ${result.join(", ")} }`;
+}
+
+function i18n(node) {
+ let key = sexpValue(node.params[0]);
+
+ let hash = node.hash;
+ if (hash.pairs.length) {
+ return `I18n.t(${key}, ${pairsToObj(hash.pairs)})`;
+ }
+
+ return `I18n.t(${key})`;
+}
+
function sexp(value) {
if (value.path.original === "hash") {
+ return pairsToObj(value.hash.pairs);
+ }
+
+ if (value.path.original === "concat") {
let result = [];
-
- value.hash.pairs.forEach(p => {
- let pValue = p.value.original;
- if (p.value.type === "StringLiteral") {
- pValue = JSON.stringify(pValue);
- }
-
- result.push(`"${p.key}": ${pValue}`);
+ value.params.forEach(p => {
+ result.push(sexpValue(p));
});
+ return result.join(" + ");
+ }
- return `{ ${result.join(", ")} }`;
+ if (value.path.original === "i18n") {
+ return i18n(value);
}
}
-function argValue(arg) {
- let value = arg.value;
+function valueOf(value) {
if (value.type === "SubExpression") {
- return sexp(arg.value);
+ return sexp(value);
} else if (value.type === "PathExpression") {
return value.original;
} else if (value.type === "StringLiteral") {
@@ -33,6 +68,10 @@ function argValue(arg) {
}
}
+function argValue(arg) {
+ return valueOf(arg.value);
+}
+
function useHelper(state, name) {
let id = state.helpersUsed[name];
if (!id) {
@@ -60,17 +99,7 @@ function mustacheValue(node, state) {
return `this.attrs.contents()`;
break;
case "i18n":
- let value;
- if (node.params[0].type === "StringLiteral") {
- value = `"${node.params[0].value}"`;
- } else if (node.params[0].type === "PathExpression") {
- value = resolve(node.params[0].original);
- }
-
- if (value) {
- return `I18n.t(${value})`;
- }
-
+ return i18n(node);
break;
case "avatar":
let template = argValue(node.hash.pairs.find(p => p.key === "template"));
@@ -82,14 +111,26 @@ function mustacheValue(node, state) {
)}(${size}, { template: ${template}, username: ${username} })`;
break;
case "date":
- value = resolve(node.params[0].original);
- return `${useHelper(state, "dateNode")}(${value})`;
+ return `${useHelper(state, "dateNode")}(${valueOf(node.params[0])})`;
break;
case "d-icon":
- let icon = node.params[0].value;
- return `${useHelper(state, "iconNode")}("${icon}")`;
+ return `${useHelper(state, "iconNode")}(${valueOf(node.params[0])})`;
break;
default:
+ // Shortcut: If our mustach has hash arguments, we can assume it's attaching.
+ // For example `{{home-logo count=123}}` can become `this.attach('home-logo, { "count": 123 });`
+ let hash = node.hash;
+ if (hash.pairs.length) {
+ let widgetString = JSON.stringify(path);
+ // magic: support applying of attrs. This is commonly done like `{{home-logo attrs=attrs}}`
+ let firstPair = hash.pairs[0];
+ if (firstPair.key === "attrs") {
+ return `this.attach(${widgetString}, ${firstPair.value.original})`;
+ }
+
+ return `this.attach(${widgetString}, ${pairsToObj(hash.pairs)})`;
+ }
+
if (node.escaped) {
return `${resolve(path)}`;
} else {
@@ -168,7 +209,7 @@ class Compiler {
case "MustacheStatement":
const value = mustacheValue(node, this.state);
if (value) {
- instructions.push(`${parentAcc}.push(${value})`);
+ instructions.push(`${parentAcc}.push(${value});`);
}
break;
case "BlockStatement":
diff --git a/script/test_hbs_compiler.rb b/script/test_hbs_compiler.rb
index e5dec469ad..192e036bf7 100644
--- a/script/test_hbs_compiler.rb
+++ b/script/test_hbs_compiler.rb
@@ -1,19 +1,9 @@
template = <<~HBS
- {{attach widget="widget-name" attrs=attrs}}
- {{a}}
- {{{htmlValue}}}
- {{#if state.category}}
- {{attach widget="category-display" attrs=(hash category=state.category someNumber=123 someString="wat")}}
- {{/if}}
- {{#each transformed.something as |s|}}
- {{s.wat}}
- {{/each}}
-
- {{attach widget=settings.widgetName}}
-
- {{#unless settings.hello}}
- XYZ
- {{/unless}}
+ {{attach widget="wat" attrs=(hash test="abc" text=(i18n "hello" count=attrs.wat))}}
+ {{action-link action="undo" className="undo" text=(i18n (concat "post.actions.undo." attrs.action))}}
+ {{actions-summary-item attrs=as}}
+ {{attach widget="actions-summary-item" attrs=as}}
+ {{testing value="hello"}}
HBS
ctx = MiniRacer::Context.new(timeout: 15000)
diff --git a/test/javascripts/widgets/actions-summary-test.js.es6 b/test/javascripts/widgets/actions-summary-test.js.es6
index b6e09b2c26..9dc926ab46 100644
--- a/test/javascripts/widgets/actions-summary-test.js.es6
+++ b/test/javascripts/widgets/actions-summary-test.js.es6
@@ -53,7 +53,7 @@ widgetTest("deferFlags", {
{
action: "off_topic",
description: "very off topic",
- canDeferFlags: true,
+ canIgnoreFlags: true,
count: 1
}
]
diff --git a/test/javascripts/widgets/widget-test.js.es6 b/test/javascripts/widgets/widget-test.js.es6
index 4ffcfa2ad3..061e2d5735 100644
--- a/test/javascripts/widgets/widget-test.js.es6
+++ b/test/javascripts/widgets/widget-test.js.es6
@@ -204,6 +204,45 @@ widgetTest("widget attaching", {
}
});
+widgetTest("magic attaching by name", {
+ template: `{{mount-widget widget="attach-test"}}`,
+
+ beforeEach() {
+ createWidget("test-embedded", { tagName: "div.embedded" });
+
+ createWidget("attach-test", {
+ tagName: "div.container",
+ template: hbs`{{test-embedded attrs=attrs}}`
+ });
+ },
+
+ test(assert) {
+ assert.ok(find(".container").length, "renders container");
+ assert.ok(find(".container .embedded").length, "renders attached");
+ }
+});
+
+widgetTest("custom attrs to a magic attached widget", {
+ template: `{{mount-widget widget="attach-test"}}`,
+
+ beforeEach() {
+ createWidget("testing", {
+ tagName: "span.value",
+ template: hbs`{{attrs.value}}`
+ });
+
+ createWidget("attach-test", {
+ tagName: "div.container",
+ template: hbs`{{testing value=(concat "hello" " " "world")}}`
+ });
+ },
+
+ test(assert) {
+ assert.ok(find(".container").length, "renders container");
+ assert.equal(find(".container .value").text(), "hello world");
+ }
+});
+
widgetTest("handlebars d-icon", {
template: `{{mount-widget widget="hbs-icon-test" args=args}}`,