DEV: Upgrade our widget handlebars compiler
Now supports subexpressions such as i18n and concat, plus automatic attaching of widgets similar to ember.
This commit is contained in:
parent
e696903c31
commit
3cb0d27d38
|
@ -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)
|
||||
};
|
||||
});
|
||||
|
|
|
@ -63,15 +63,12 @@ createWidget("small-user-list", {
|
|||
|
||||
createWidget("action-link", {
|
||||
tagName: "span.action-link",
|
||||
template: hbs`<a>{{attrs.text}}. </a>`,
|
||||
|
||||
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}}
|
||||
<div class='clearfix'></div>
|
||||
{{/each}}
|
||||
{{#if attrs.deleted_at}}
|
||||
|
|
|
@ -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}}
|
||||
<div class="panel clearfix">{{yield}}</div>
|
||||
`
|
||||
|
|
|
@ -34,9 +34,9 @@ createWidget("pm-map-user-group", {
|
|||
<span class="group-name">{{attrs.group.name}}</span>
|
||||
</a>
|
||||
{{#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}}
|
||||
`
|
||||
});
|
||||
|
|
|
@ -109,15 +109,12 @@ createWidget("user-menu-dismiss-link", {
|
|||
template: hbs`
|
||||
<ul class='menu-links'>
|
||||
<li>
|
||||
{{attach
|
||||
widget="link"
|
||||
attrs=(hash
|
||||
action="dismissNotifications"
|
||||
className="dismiss"
|
||||
tabindex="0"
|
||||
icon="check"
|
||||
label="user.dismiss"
|
||||
title="user.dismiss_notifications_tooltip")}}
|
||||
{{link action="dismissNotifications"
|
||||
className="dismiss"
|
||||
tabindex="0"
|
||||
icon="check"
|
||||
label="user.dismiss"
|
||||
title="user.dismiss_notifications_tooltip"}}
|
||||
</li>
|
||||
</ul>
|
||||
`
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -53,7 +53,7 @@ widgetTest("deferFlags", {
|
|||
{
|
||||
action: "off_topic",
|
||||
description: "very off topic",
|
||||
canDeferFlags: true,
|
||||
canIgnoreFlags: true,
|
||||
count: 1
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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}}`,
|
||||
|
||||
|
|
Loading…
Reference in New Issue