Skip to content

Commit 190fa1f

Browse files
frenckfdmarcin
andauthored
Introduce collections for triggers, conditions & actions (#44861)
Co-authored-by: Marcin Sędłak-Jakubowski <fdmarcin@gmail.com>
1 parent 0a5f5c3 commit 190fa1f

67 files changed

Lines changed: 3157 additions & 88 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

_config.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ collections:
7070
template_functions:
7171
output: true
7272
permalink: /template-functions/:path/
73+
actions:
74+
output: true
75+
permalink: /actions/:path/
76+
triggers:
77+
output: true
78+
permalink: /triggers/:path/
79+
conditions:
80+
output: true
81+
permalink: /conditions/:path/
7382
docs:
7483
output: true
7584
apps:
@@ -164,6 +173,21 @@ defaults:
164173
type: template_functions
165174
values:
166175
toc: true
176+
- scope:
177+
path: ""
178+
type: actions
179+
values:
180+
toc: true
181+
- scope:
182+
path: ""
183+
type: triggers
184+
values:
185+
toc: true
186+
- scope:
187+
path: ""
188+
type: conditions
189+
values:
190+
toc: true
167191
- scope:
168192
path: ""
169193
type: docs

plugins/alerts.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ def render(context)
3636
icon = "mdi:alert-outline"
3737
elsif @type == 'caution'
3838
icon = "mdi:alert-circle-outline"
39+
elsif @type == 'labs'
40+
icon = "mdi:flask-outline"
3941
else
4042
icon = "mdi:information-outline"
4143
end
@@ -81,3 +83,4 @@ def parse_options(input, context)
8183
Liquid::Template.register_tag('important', Jekyll::HomeAssistant::AlertBlock)
8284
Liquid::Template.register_tag('warning', Jekyll::HomeAssistant::AlertBlock)
8385
Liquid::Template.register_tag('caution', Jekyll::HomeAssistant::AlertBlock)
86+
Liquid::Template.register_tag('labs', Jekyll::HomeAssistant::AlertBlock)

plugins/doc_collections_data.rb

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
require 'json'
2+
require 'safe_yaml'
3+
4+
# Generate JSON lookups of all actions, triggers, and conditions (with
5+
# their data fields) for use by prism-doc-links.js. For each entry the
6+
# lookup exposes:
7+
#
8+
# d = description (from front matter, used as fallback tooltip text)
9+
# u = URL of the page
10+
# t = title (for fallback tooltip text)
11+
# p = parameters map { field_name: short_description }, extracted from
12+
# the first `{% fields %}` or `{% fields_yaml %}` block in the page
13+
# content. The former carries both UI and YAML sub-maps; we pick the
14+
# `yaml:` sub-map. The latter is a standalone YAML-only block.
15+
#
16+
# Three separate data keys are emitted, so the client JS can look up each
17+
# kind in its own namespace:
18+
# site.data.actions_json keyed by "<domain>.<name>"
19+
# site.data.triggers_json keyed by "<domain>.<name>"
20+
# site.data.conditions_json keyed by "<domain>.<name>"
21+
module Jekyll
22+
class DocCollectionsDataGenerator < Generator
23+
safe true
24+
priority :low
25+
26+
COLLECTIONS = [
27+
{ name: 'actions', front_matter_key: 'action', data_key: 'actions_json' },
28+
{ name: 'triggers', front_matter_key: 'trigger', data_key: 'triggers_json' },
29+
{ name: 'conditions', front_matter_key: 'condition', data_key: 'conditions_json' }
30+
].freeze
31+
32+
OPTIONS_YAML_PATTERN = /\{%\s*options_yaml\s*%\}(.*?)\{%\s*endoptions_yaml\s*%\}/m
33+
MAX_FIELD_DESCRIPTION_LENGTH = 160
34+
35+
def generate(site)
36+
COLLECTIONS.each do |info|
37+
entries = {}
38+
site.collections[info[:name]]&.docs&.each do |doc|
39+
name = doc.data[info[:front_matter_key]]
40+
next unless name
41+
42+
entry = {
43+
'd' => doc.data['description'].to_s,
44+
'u' => doc.url,
45+
't' => doc.data['title'].to_s
46+
}
47+
48+
params = extract_fields(doc, name)
49+
entry['p'] = params if params && !params.empty?
50+
51+
entries[name] = entry
52+
end
53+
54+
# Escape </ to prevent breaking out of <script> tags when embedded in HTML
55+
site.data[info[:data_key]] = JSON.generate(entries).gsub('</', '<\\/')
56+
end
57+
end
58+
59+
private
60+
61+
# Extract option descriptions from the page's `{% options_yaml %}` block.
62+
def extract_fields(doc, entry_name)
63+
match = doc.content.match(OPTIONS_YAML_PATTERN)
64+
return nil unless match
65+
66+
yaml_body = SafeYAML.load(match[1])
67+
return nil unless yaml_body.is_a?(Hash)
68+
69+
fields = {}
70+
yaml_body.each do |name, config|
71+
next unless config.is_a?(Hash) && config['description']
72+
desc = config['description'].to_s.strip.gsub(/\s+/, ' ')
73+
# Truncate to first sentence if too long for a tooltip.
74+
desc = desc.split(/(?<=\.)\s/)[0] if desc.length > MAX_FIELD_DESCRIPTION_LENGTH
75+
fields[name] = desc
76+
end
77+
fields
78+
rescue => e
79+
Jekyll.logger.warn "DocCollectionsData:", "Failed to parse fields for #{entry_name}: #{e.message}"
80+
nil
81+
end
82+
end
83+
end

plugins/options_ui.rb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
require 'safe_yaml'
2+
3+
# Render a list of options for the user-interface audience on action,
4+
# trigger, and condition pages. Shows a human-readable label, an
5+
# optional/required indicator, and a description. Emits the same
6+
# `config-vars basic` div structure as `configuration_basic` so existing
7+
# CSS applies without any new styling.
8+
module Jekyll
9+
class OptionsUiBlock < Liquid::Block
10+
def slug(key)
11+
key.downcase.strip.gsub(' ', '-').gsub(/[^\w\-]/, '')
12+
end
13+
14+
def render_fields(vars, converter)
15+
items = vars.map do |key, attr|
16+
required = attr['required']
17+
desc = attr['description'].to_s
18+
raise ArgumentError, "Option '#{key}' is missing a description" if desc.strip.empty?
19+
20+
label_html = +"<span class='config-vars-label-name'>#{key}</span>"
21+
if required == true || required == false
22+
klass = required == true ? 'true' : 'false'
23+
text = required == true ? 'Required' : 'Optional'
24+
label_html << "<span class='config-vars-required'> (<span class='#{klass}'>#{text}</span>)</span>"
25+
end
26+
27+
<<~ITEM
28+
<div class='config-vars-item'>
29+
<div class='config-vars-label'>
30+
#{label_html}
31+
<a name='#{slug(key)}' class='title-link' href='##{slug(key)}'></a>
32+
</div>
33+
<div class='config-vars-description-and-children'>
34+
<span class='config-vars-description'>#{converter.convert(desc)}</span>
35+
</div>
36+
</div>
37+
ITEM
38+
end
39+
"<div class='config-vars basic'>#{items.join}</div>"
40+
end
41+
42+
def render(context)
43+
contents = super(context)
44+
site = context.registers[:site]
45+
converter = site.find_converter_instance(::Jekyll::Converters::Markdown)
46+
vars = SafeYAML.load(contents)
47+
raise ArgumentError, "options_ui block must contain a YAML mapping" unless vars.is_a?(Hash)
48+
render_fields(vars, converter)
49+
end
50+
end
51+
end
52+
53+
Liquid::Template.register_tag('options_ui', Jekyll::OptionsUiBlock)

plugins/options_yaml.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
require 'safe_yaml'
2+
require_relative 'configuration'
3+
4+
# Render the options block for the YAML-audience section of action,
5+
# trigger, and condition pages. Reuses the same `config-vars` layout and
6+
# type-link helpers as the shared `ConfigurationBlock`, but only shows a
7+
# "Required" badge on required fields. Optional fields render with no
8+
# badge at all, so the reader quickly sees which options they must set.
9+
module Jekyll
10+
class OptionsYamlBlock < ConfigurationBlock
11+
def render(context)
12+
contents = Liquid::Block.instance_method(:render).bind_call(self, context)
13+
14+
site = context.registers[:site]
15+
converter = site.find_converter_instance(::Jekyll::Converters::Markdown)
16+
17+
vars = SafeYAML.load(contents)
18+
raise ArgumentError, "options_yaml block must contain a YAML mapping" unless vars.is_a?(Hash)
19+
20+
# Drop "required: false" so render_config_vars skips the badge entirely
21+
# for optional fields. Required fields keep their badge.
22+
vars.each_value do |attr|
23+
next unless attr.is_a?(Hash)
24+
attr.delete('required') if attr['required'] == false
25+
end
26+
27+
<<~MARKUP
28+
<div class="config-vars">
29+
#{render_config_vars(
30+
vars: vars,
31+
component: '',
32+
platform: '',
33+
converter: converter
34+
)}
35+
</div>
36+
MARKUP
37+
end
38+
end
39+
end
40+
41+
Liquid::Template.register_tag('options_yaml', Jekyll::OptionsYamlBlock)

sass/homeassistant/plugins/_alert.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,12 @@ div.alert {
5959
color: #d32f2f;
6060
}
6161
}
62+
63+
&.alert-labs {
64+
background-color: #e0f2f1;
65+
66+
p.alert-title {
67+
color: #00897b;
68+
}
69+
}
6270
}

sass/homeassistant/plugins/_template_functions.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,21 @@ $tf-code-font: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
104104
margin-bottom: -1em;
105105
}
106106

107+
/* Fields block: tabbed UI and YAML views on action, trigger, and
108+
condition pages. Adds bottom breathing room so the last field does not
109+
sit flush against the wrapping tab container. */
110+
.fields-block {
111+
margin-bottom: 1.5em;
112+
}
113+
114+
.fields-block .tabbed-content-block-content {
115+
padding-bottom: 0.75em;
116+
}
117+
118+
.fields-block .config-vars .config-vars-item:last-child {
119+
margin-bottom: 0;
120+
}
121+
107122
.template-example-input,
108123
.template-example-output {
109124
position: relative;

0 commit comments

Comments
 (0)