├── lib ├── lunch_money │ ├── .DS_Store │ ├── previews │ │ ├── accounts.bmp │ │ └── budgets.bmp │ ├── views │ │ ├── common │ │ │ ├── _title_bar.html.erb │ │ │ ├── _headings.html.erb │ │ │ └── _chart.html.erb │ │ ├── full.html.erb │ │ ├── half_horizontal.html.erb │ │ ├── quadrant.html.erb │ │ └── half_vertical.html.erb │ └── lunch_money.rb ├── calendar │ ├── previews │ │ ├── week.bmp │ │ ├── default.bmp │ │ └── month.bmp │ ├── views │ │ ├── _title_bar.html.erb │ │ ├── _full.html.erb │ │ ├── _all_day_event.html.erb │ │ ├── _quadrant.html.erb │ │ ├── _half_horizontal.html.erb │ │ ├── _full_month.html.erb │ │ ├── _full_auto.html.erb │ │ ├── _full_week.html.erb │ │ └── _common.html.erb │ └── helpers │ │ └── base.rb ├── github_commit_graph │ ├── views │ │ ├── _common.html.erb │ │ ├── errors │ │ │ └── _full.html.erb │ │ ├── v1 │ │ │ ├── _common.html.erb │ │ │ ├── _quadrant.html.erb │ │ │ ├── _half_vertical.html.erb │ │ │ ├── _half_horizontal.html.erb │ │ │ └── _full.html.erb │ │ ├── quadrant.html.erb │ │ ├── half_vertical.html.erb │ │ ├── half_horizontal.html.erb │ │ └── full.html.erb │ ├── helpers.rb │ └── github_commit_graph.rb ├── google_analytics │ ├── previews │ │ ├── full.png │ │ ├── quadrant.png │ │ ├── half_vertical.png │ │ └── half_horizontal.png │ └── views │ │ ├── full.html.erb │ │ ├── quadrant.html.erb │ │ ├── half_vertical.html.erb │ │ └── half_horizontal.html.erb ├── hacker_news │ ├── previews │ │ └── hacker_news.bmp │ ├── hacker_news.rb │ └── views │ │ └── full.html.erb ├── mondrian │ ├── mondrian.rb │ └── views │ │ ├── full.html.erb │ │ ├── quadrant.html.erb │ │ ├── half_vertical.html.erb │ │ ├── half_horizontal.html.erb │ │ ├── _common.html.erb │ │ └── mondrian │ │ └── _common.html.erb ├── route_planner │ ├── previews │ │ ├── fullview.bmp │ │ ├── quadrantview.bmp │ │ ├── half_verticalview.bmp │ │ └── half_horizontalview.bmp │ ├── route_planner.rb │ └── views │ │ ├── quadrant.html.erb │ │ ├── half_vertical.html.erb │ │ ├── half_horizontal.html.erb │ │ └── full.html.erb ├── tempest_weather_station │ ├── previews │ │ └── tempest_weather_station.bmp │ └── views │ │ └── _common.html.erb ├── eight_sleep │ ├── views │ │ ├── quadrant.html.erb │ │ ├── half_horizontal.html.erb │ │ ├── half_vertical.html.erb │ │ ├── shared │ │ │ └── _title_bar.html.erb │ │ └── _chart_js.html.erb │ └── eight_sleep.rb ├── notion │ └── views │ │ ├── shared │ │ ├── _title_bar.html.erb │ │ ├── _error_section.html.erb │ │ ├── _database_item.html.erb │ │ └── _page_block.html.erb │ │ ├── quadrant.html.erb │ │ ├── half_vertical.html.erb │ │ ├── full.html.erb │ │ └── half_horizontal.html.erb ├── withings │ └── views │ │ ├── shared │ │ └── _title_bar.html.erb │ │ └── _chart_js.html.erb ├── parcel │ ├── views │ │ ├── shared │ │ │ ├── _title_bar.html.erb │ │ │ ├── _deliveries.html.erb │ │ │ ├── _header.html.erb │ │ │ └── _delivery.html.erb │ │ ├── parcel │ │ │ └── shared │ │ │ │ ├── _title_bar.html.erb │ │ │ │ ├── _deliveries.html.erb │ │ │ │ ├── _header.html.erb │ │ │ │ └── _delivery.html.erb │ │ ├── quadrant.html.erb │ │ ├── half_horizontal.html.erb │ │ ├── full.html.erb │ │ └── half_vertical.html.erb │ └── parcel.rb ├── todoist │ ├── views │ │ ├── shared │ │ │ └── _title_bar.html.erb │ │ ├── quadrant.html.erb │ │ ├── half_horizontal.html.erb │ │ ├── half_vertical.html.erb │ │ └── full.html.erb │ └── form_fields.yaml ├── chatgpt │ └── views │ │ ├── shared │ │ └── _title_bar.html.erb │ │ ├── quadrant.html.erb │ │ ├── full.html.erb │ │ ├── half_vertical.html.erb │ │ └── half_horizontal.html.erb ├── weather │ └── views │ │ ├── _common.html.erb │ │ ├── quadrant.html.erb │ │ ├── half_horizontal.html.erb │ │ └── half_vertical.html.erb ├── stock_price │ ├── views │ │ ├── shared │ │ │ ├── _ticker_xsmall.erb │ │ │ ├── _ticker_xxsmall.erb │ │ │ ├── _ticker_medium.erb │ │ │ ├── _ticker_small.erb │ │ │ ├── _ticker_xlarge.erb │ │ │ ├── _ticker_xxlarge.erb │ │ │ └── _ticker_large.erb │ │ ├── quadrant.html.erb │ │ ├── half_vertical.html.erb │ │ └── half_horizontal.html.erb │ └── stock_price.rb ├── days_left_until │ ├── views │ │ ├── _common.html.erb │ │ ├── _progress_bar.html.erb │ │ ├── quadrant.html.erb │ │ ├── full.html.erb │ │ ├── half_horizontal.html.erb │ │ └── half_vertical.html.erb │ └── days_left_until.rb ├── screenshot │ └── screenshot.rb ├── email_meter │ ├── email_meter.rb │ └── views │ │ ├── half_vertical.html.erb │ │ ├── full.html.erb │ │ ├── quadrant.html.erb │ │ └── half_horizontal.html.erb ├── lunar_calendar │ └── views │ │ ├── quadrant.html.erb │ │ ├── full.html.erb │ │ └── half_vertical.html.erb ├── youtube_analytics │ ├── views │ │ ├── quadrant.html.erb │ │ ├── full.html.erb │ │ ├── half_vertical.html.erb │ │ └── half_horizontal.html.erb │ └── youtube_analytics.rb └── usa_college_football_rankings.md └── .github └── ISSUE_TEMPLATE └── bounty---insert.md /lib/lunch_money/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/lunch_money/.DS_Store -------------------------------------------------------------------------------- /lib/calendar/previews/week.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/calendar/previews/week.bmp -------------------------------------------------------------------------------- /lib/calendar/previews/default.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/calendar/previews/default.bmp -------------------------------------------------------------------------------- /lib/calendar/previews/month.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/calendar/previews/month.bmp -------------------------------------------------------------------------------- /lib/github_commit_graph/views/_common.html.erb: -------------------------------------------------------------------------------- 1 | <%= stylesheet_link_tag 'plugins/github_commit_graph', media: 'all' %> -------------------------------------------------------------------------------- /lib/google_analytics/previews/full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/google_analytics/previews/full.png -------------------------------------------------------------------------------- /lib/lunch_money/previews/accounts.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/lunch_money/previews/accounts.bmp -------------------------------------------------------------------------------- /lib/lunch_money/previews/budgets.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/lunch_money/previews/budgets.bmp -------------------------------------------------------------------------------- /lib/hacker_news/previews/hacker_news.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/hacker_news/previews/hacker_news.bmp -------------------------------------------------------------------------------- /lib/mondrian/mondrian.rb: -------------------------------------------------------------------------------- 1 | module Plugins 2 | class Mondrian < Base 3 | def locals 4 | {} 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/route_planner/previews/fullview.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/route_planner/previews/fullview.bmp -------------------------------------------------------------------------------- /lib/google_analytics/previews/quadrant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/google_analytics/previews/quadrant.png -------------------------------------------------------------------------------- /lib/route_planner/previews/quadrantview.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/route_planner/previews/quadrantview.bmp -------------------------------------------------------------------------------- /lib/google_analytics/previews/half_vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/google_analytics/previews/half_vertical.png -------------------------------------------------------------------------------- /lib/route_planner/previews/half_verticalview.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/route_planner/previews/half_verticalview.bmp -------------------------------------------------------------------------------- /lib/google_analytics/previews/half_horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/google_analytics/previews/half_horizontal.png -------------------------------------------------------------------------------- /lib/route_planner/previews/half_horizontalview.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/route_planner/previews/half_horizontalview.bmp -------------------------------------------------------------------------------- /lib/mondrian/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | <%= render 'plugins/mondrian/common' %> -------------------------------------------------------------------------------- /lib/tempest_weather_station/previews/tempest_weather_station.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/plugins/HEAD/lib/tempest_weather_station/previews/tempest_weather_station.bmp -------------------------------------------------------------------------------- /lib/mondrian/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | <%= render 'plugins/mondrian/common' %> -------------------------------------------------------------------------------- /lib/mondrian/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | <%= render 'plugins/mondrian/common' %> -------------------------------------------------------------------------------- /lib/mondrian/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | <%= render 'plugins/mondrian/common' %> -------------------------------------------------------------------------------- /lib/eight_sleep/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | <%= render "plugins/#{@plugin.keyname}/shared/title_bar", instance_name: instance_name %> 6 |
7 | -------------------------------------------------------------------------------- /lib/eight_sleep/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | <%= render "plugins/#{@plugin.keyname}/shared/title_bar", instance_name: instance_name %> 6 |
7 | -------------------------------------------------------------------------------- /lib/eight_sleep/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | <%= render "plugins/#{@plugin.keyname}/shared/title_bar", instance_name: instance_name %> 6 |
7 | -------------------------------------------------------------------------------- /lib/notion/views/shared/_title_bar.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | <%= instance_name %> 4 |
-------------------------------------------------------------------------------- /lib/withings/views/shared/_title_bar.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | Withings 4 | <%= instance_name %> 5 |
6 | -------------------------------------------------------------------------------- /lib/parcel/views/shared/_title_bar.html.erb: -------------------------------------------------------------------------------- 1 |
2 | parcel--render.png"/> 3 |

Parcel

4 | <%= instance_name %> 5 |
-------------------------------------------------------------------------------- /lib/lunch_money/views/common/_title_bar.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Lunch Money

4 | <%= instance_name %> 5 |
6 | -------------------------------------------------------------------------------- /lib/parcel/views/parcel/shared/_title_bar.html.erb: -------------------------------------------------------------------------------- 1 |
2 | parcel--render.png"/> 3 |

Parcel

4 | <%= instance_name %> 5 |
-------------------------------------------------------------------------------- /lib/eight_sleep/views/shared/_title_bar.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= image_tag plugin_image_path("eight_sleep--render.svg"), class: "image", alt: "Eight Sleep plugin icon" %> 3 |

Eight Sleep

4 | <%= instance_name %> 5 |
6 | -------------------------------------------------------------------------------- /lib/calendar/views/_title_bar.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | Upcoming Events 4 | <%= instance_name %> 5 |
-------------------------------------------------------------------------------- /lib/todoist/views/shared/_title_bar.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | Todo List 4 | <%= instance_name %> 5 |
6 | 7 | -------------------------------------------------------------------------------- /lib/chatgpt/views/shared/_title_bar.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= image_tag plugin_image_path("chatgpt--render.svg"), class: "image image-stroke image-stroke--black image-stroke--small", alt: "ChatGPT plugin icon" %> 3 | ChatGPT 4 | <%= instance_name %> 5 |
6 | -------------------------------------------------------------------------------- /lib/parcel/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= render 'plugins/parcel/shared/deliveries', max_height: 190, max_columns: 2, deliveries:, error:, style: %> 4 |
5 | 6 | <%= render "plugins/parcel/shared/title_bar", instance_name: instance_name %> 7 |
-------------------------------------------------------------------------------- /lib/parcel/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= render 'plugins/parcel/shared/deliveries', max_height: 190, max_columns: 3, deliveries:, error:, style: %> 4 |
5 | 6 | <%= render "plugins/parcel/shared/title_bar", instance_name: instance_name %> 7 |
-------------------------------------------------------------------------------- /lib/chatgpt/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
<%= simple_format(answer) %>
5 |
6 |
7 | 8 | <%= render 'plugins/chatgpt/shared/title_bar', instance_name: instance_name %> 9 |
10 | -------------------------------------------------------------------------------- /lib/chatgpt/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
<%= simple_format(answer) %>
5 |
6 |
7 | 8 | <%= render 'plugins/chatgpt/shared/title_bar', instance_name: instance_name %> 9 |
10 | -------------------------------------------------------------------------------- /lib/calendar/views/_full.html.erb: -------------------------------------------------------------------------------- 1 | <% if event_layout == 'week' %> 2 | <%= render partial: 'plugins/calendars/full_week', locals: local_assigns %> 3 | <% elsif %w[month rolling_month].include?(event_layout) %> 4 | <%= render partial: 'plugins/calendars/full_month', locals: local_assigns %> 5 | <% else %> 6 | <%= render partial: 'plugins/calendars/full_auto', locals: local_assigns %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /lib/chatgpt/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
<%= simple_format(answer) %>
5 |
6 |
7 | 8 | <%= render 'plugins/chatgpt/shared/title_bar', instance_name: instance_name %> 9 |
10 | -------------------------------------------------------------------------------- /lib/chatgpt/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
<%= simple_format(answer) %>
5 |
6 |
7 | 8 | <%= render 'plugins/chatgpt/shared/title_bar', instance_name: instance_name %> 9 |
10 | -------------------------------------------------------------------------------- /lib/calendar/views/_all_day_event.html.erb: -------------------------------------------------------------------------------- 1 | <% heading_size ||= 'small' %> 2 | 3 |
4 |
5 | # 6 |
7 |
8 | <%= event[:summary] %> 9 | <% if include_description %> 10 | <%= event[:description] %> 11 | <% end %> 12 |
13 |
14 | -------------------------------------------------------------------------------- /lib/notion/views/shared/_error_section.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | <%= title_text %> 8 | <%= description_text %> 9 |
10 |
11 |
-------------------------------------------------------------------------------- /lib/weather/views/_common.html.erb: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /lib/lunch_money/views/common/_headings.html.erb: -------------------------------------------------------------------------------- 1 | <% top_items_count ||= 3 %> 2 | <% top_items = items.sort_by { |_, amount| -amount }.first(top_items_count) %> 3 | <% top_items.each do |item, amount| %> 4 |
5 |
6 |
7 | <%= number_to_currency(amount, precision: 0) %> 8 | <%= item %> 9 |
10 |
11 | <% end %> 12 | -------------------------------------------------------------------------------- /lib/tempest_weather_station/views/_common.html.erb: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /lib/parcel/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= render 'plugins/parcel/shared/header', deliveries:, filter_mode:, value_size: 'large' %> 4 | 5 |
6 | 7 | <%= render 'plugins/parcel/shared/deliveries', max_height: 270, max_columns: 3, deliveries:, error:, style: %> 8 |
9 | 10 | <%= render "plugins/parcel/shared/title_bar", instance_name: instance_name %> 11 |
-------------------------------------------------------------------------------- /lib/parcel/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= render 'plugins/parcel/shared/header', deliveries:, filter_mode: %> 4 | 5 |
6 | 7 | <%= render 'plugins/parcel/shared/deliveries', max_height: 360, max_columns: 1, deliveries:, error:, style: %> 8 |
9 | 10 | <%= render "plugins/parcel/shared/title_bar", instance_name: instance_name %> 11 |
-------------------------------------------------------------------------------- /lib/lunch_money/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= render partial: 'plugins/lunch_money/common/headings', locals: local_assigns %> 5 |
6 | 7 | <%= render partial: 'plugins/lunch_money/common/chart', locals: local_assigns.merge(graph_items_count: 16) %> 8 |
9 | 10 | <%= render 'plugins/lunch_money/common/title_bar', instance_name: instance_name %> 11 |
12 | -------------------------------------------------------------------------------- /lib/lunch_money/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= render partial: 'plugins/lunch_money/common/headings', locals: local_assigns %> 5 |
6 | 7 | <% chart_opts = { graph_items_count: 9, chart_size: { height: 180, width: 583 }, y_axis_labels: { enabled: false } } %> 8 | <%= render partial: 'plugins/lunch_money/common/chart', locals: local_assigns.merge(chart_opts) %> 9 |
10 | 11 | <%= render 'plugins/lunch_money/common/title_bar', instance_name: instance_name %> 12 |
13 | -------------------------------------------------------------------------------- /lib/stock_price/views/shared/_ticker_xsmall.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | <%= ticker[:symbol].gsub(' ', ' ').html_safe %> 6 | <%= ticker[:change] %> 7 |
8 |
9 |
10 |
11 |
12 | <%= number_to_currency(ticker[:price], unit: currency_symbol, separator: currency_separator) %> 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /lib/stock_price/views/shared/_ticker_xxsmall.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | <%= ticker[:symbol].gsub(' ', ' ').html_safe %> 6 | <%= ticker[:change] %> 7 |
8 |
9 |
10 |
11 |
12 | <%= number_to_currency(ticker[:price], unit: currency_symbol, separator: currency_separator) %> 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /lib/lunch_money/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= render partial: 'plugins/lunch_money/common/headings', locals: local_assigns.merge(top_items_count: 2) %> 5 |
6 | 7 | <% chart_opts = { graph_items_count: 5, chart_size: { height: 120 }, series_point_width: 5, y_axis_labels: { enabled: false } } %> 8 | <%= render partial: 'plugins/lunch_money/common/chart', locals: local_assigns.merge(chart_opts) %> 9 |
10 | 11 | <%= render 'plugins/lunch_money/common/title_bar', instance_name: instance_name %> 12 |
13 | -------------------------------------------------------------------------------- /lib/lunch_money/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= render partial: 'plugins/lunch_money/common/headings', locals: local_assigns.merge(top_items_count: 2) %> 5 |
6 | 7 | <% chart_opts = { graph_items_count: 16, chart_size: { height: 340 }, series_point_width: 5, y_axis_labels: { style: { fontSize: '16px' } }, tick_amount: 3 } %> 8 | <%= render partial: 'plugins/lunch_money/common/chart', locals: local_assigns.merge(chart_opts) %> 9 |
10 | 11 | <%= render 'plugins/lunch_money/common/title_bar', instance_name: instance_name %> 12 |
13 | -------------------------------------------------------------------------------- /lib/eight_sleep/views/_chart_js.html.erb: -------------------------------------------------------------------------------- 1 | <% libs = local_assigns[:libs] || [:highcharts, :chartkick] %> 2 | 3 | <% if libs.include?(:highcharts) %> 4 | 5 | <% end %> 6 | 7 | <% if libs.include?(:highcharts_more) %> 8 | 9 | <% end %> 10 | 11 | <% if libs.include?(:highcharts_pattern_fill) %> 12 | 13 | <% end %> 14 | 15 | <% if libs.include?(:chartkick) %> 16 | 17 | <% end %> 18 | -------------------------------------------------------------------------------- /lib/withings/views/_chart_js.html.erb: -------------------------------------------------------------------------------- 1 | <% libs = local_assigns[:libs] || [:highcharts, :chartkick] %> 2 | 3 | <% if libs.include?(:highcharts) %> 4 | 5 | <% end %> 6 | 7 | <% if libs.include?(:highcharts_more) %> 8 | 9 | <% end %> 10 | 11 | <% if libs.include?(:highcharts_pattern_fill) %> 12 | 13 | <% end %> 14 | 15 | <% if libs.include?(:chartkick) %> 16 | 17 | <% end %> 18 | -------------------------------------------------------------------------------- /lib/todoist/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <% todos = todos.first %> 5 | <% todos[:items].each_with_index do |item, idx| %> 6 |
7 |
8 | <%= idx + 1 %> 9 |
10 |
11 | <%= item %> 12 |
13 |
14 | <% end %> 15 |
16 |
17 | 18 | <%= render 'plugins/todo_list/shared/title_bar', instance_name: instance_name %> 19 |
20 | -------------------------------------------------------------------------------- /lib/github_commit_graph/views/errors/_full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <% if defined?(error_title) %> 5 | <%= error_title %> 6 | <% end %> 7 |
8 |
9 |

<%= error_message %>

10 |
11 |
12 |
13 |
14 | 15 |
16 | 17 | <%= title %> 18 | <%= instance %> 19 |
20 |
-------------------------------------------------------------------------------- /lib/parcel/views/shared/_deliveries.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
9 | <% if error.present? %> 10 |
11 | <%= error %> 12 |
13 | <% elsif deliveries.empty? %> 14 |
15 | <%= t('renders.parcel.empty', locale:) %> 16 |
17 | <% else %> 18 | <%= render partial: "plugins/parcel/shared/delivery", collection: deliveries, locals: { style: } %> 19 | <% end %> 20 |
21 |
-------------------------------------------------------------------------------- /lib/parcel/views/parcel/shared/_deliveries.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
9 | <% if error.present? %> 10 |
11 | <%= error %> 12 |
13 | <% elsif deliveries.empty? %> 14 |
15 | <%= t('renders.parcel.empty', locale:) %> 16 |
17 | <% else %> 18 | <%= render partial: "plugins/parcel/shared/delivery", collection: deliveries, locals: { style: } %> 19 | <% end %> 20 |
21 |
-------------------------------------------------------------------------------- /lib/lunch_money/views/common/_chart.html.erb: -------------------------------------------------------------------------------- 1 | <% chart_size ||= { height: 320 } %> 2 | <% series_point_width ||= 6 %> 3 | <% y_axis_labels ||= { style: { fontSize: '16px' } } %> 4 | <% tick_amount ||= 5 %> 5 | 6 | <% graph_items = items.sort_by { |_, amount| -amount }.first(graph_items_count) %> 7 | <%= bar_chart graph_items.to_h, adapter: :highcharts, 8 | prefix: '$', 9 | thousands: ',', 10 | colors: ['black'], 11 | library: { 12 | chart: chart_size, 13 | plotOptions: { series: { animation: false, pointWidth: series_point_width } }, 14 | yAxis: { labels: y_axis_labels, gridLineDashStyle: 'shortdot', gridLineWidth: 1, gridLineColor: '#000000', tickAmount: tick_amount }, 15 | xAxis: { type: 'daytime', labels: { style: { fontSize: '16px' } }, lineWidth: 0, gridLineDashStyle: 'dot', tickWidth: 1, tickLength: 0, gridLineWidth: 1, gridLineColor: '#000000', tickPixelInterval: 120 } 16 | } 17 | %> 18 | -------------------------------------------------------------------------------- /lib/stock_price/views/shared/_ticker_medium.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | <%= ticker[:symbol].gsub(' ', ' ').html_safe %> 7 | <%= ticker[:name] %> 8 |
9 |
10 |
11 |
12 |
13 | <%= ticker[:change] %> 14 | Daily Change 15 |
16 |
17 |
18 |
19 |
20 |
21 | <%= number_to_currency(ticker[:price], unit: currency_symbol, separator: currency_separator) %> 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /lib/days_left_until/views/_common.html.erb: -------------------------------------------------------------------------------- 1 | 47 | -------------------------------------------------------------------------------- /lib/stock_price/views/shared/_ticker_small.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | <%= ticker[:symbol].gsub(' ', ' ').html_safe %> 7 | <%= ticker[:name] %> 8 |
9 |
10 |
11 |
12 |
13 | <%= ticker[:change] %> 14 | Daily Change 15 |
16 |
17 |
18 |
19 |
20 |
21 | <%= number_to_currency(ticker[:price], unit: currency_symbol, separator: currency_separator) %> 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /lib/github_commit_graph/helpers.rb: -------------------------------------------------------------------------------- 1 | def git_commit_grayscale(contribution_count, opts = {}) 2 | if opts[:framework_v2] == false # @lucian opt-in to v1 colors 3 | case contribution_count 4 | when 1..3 5 | 'bg--gray-5' 6 | when 4..7 7 | 'bg--gray-4' 8 | when 8..10 9 | 'bg--gray-3' 10 | when 11..20 11 | 'bg--gray-2' 12 | when 20..1000 13 | 'bg-black' 14 | end 15 | else 16 | case contribution_count 17 | when 1 18 | 'bg--gray-60' 19 | when 2 20 | 'bg--gray-55' 21 | when 3 22 | 'bg--gray-50' 23 | when 4 24 | 'bg--gray-45' 25 | when 5 26 | 'bg--gray-40' 27 | when 6 28 | 'bg--gray-35' 29 | when 7..8 30 | 'bg--gray-30' 31 | when 9..10 32 | 'bg--gray-25' 33 | when 11..12 34 | 'bg--gray-20' 35 | when 13..15 36 | 'bg--gray-15' 37 | when 16..20 38 | 'bg--gray-10' 39 | when 21..1000 40 | 'bg-black' 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bounty---insert.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: bounty - INSERT 3 | about: insert summary from usetrmnl.com/admin/bounties 4 | title: '' 5 | labels: help wanted 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### desired outcome 11 | insert hints or existing plugins with a similar aesthetic to mimic. 12 | 13 | ### prerequisites 14 | 15 | - TRMNL account 16 | - Developer edition (for access to Private Plugin > Markup Editor) 17 | - Must use native [Framework](https://usetrmnl.com/framework) 18 | 19 | ### scope 20 | - [ ] Frontend markup (4x layouts) 21 | - [ ] Example API requests with valid endpoints to reproduce data in frontend 22 | - INSERT more if applicable 23 | 24 | ### compensation 25 | 26 | $X 27 | 28 | ### next steps 29 | 30 | 1. reply to claim, then a **X hour clock** begins before it's released to other devs 31 | 2. [export](https://help.usetrmnl.com/en/articles/10542599-importing-and-exporting-private-plugins) your Private Plugin and email team@usetrmnl.com, subject "Bounty - INSERT" 32 | -------------------------------------------------------------------------------- /lib/stock_price/views/shared/_ticker_xlarge.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= ticker[:symbol].gsub(' ', ' ').html_safe %> 8 | <%= ticker[:name] %> 9 |
10 |
11 |
12 |
13 |
14 | <%= ticker[:change] %> 15 | Daily Change 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | <%= number_to_currency(ticker[:price], unit: currency_symbol, separator: currency_separator) %> 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /lib/stock_price/views/shared/_ticker_xxlarge.erb: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |
7 | <%= ticker[:symbol].gsub(' ', ' ').html_safe %> 8 | <%= ticker[:name] %> 9 |
10 |
11 |
12 |
13 |
14 | <%= ticker[:change] %> 15 | Daily Change 16 |
17 |
18 |
19 |
20 |
21 |
22 | <%= number_to_currency(ticker[:price], unit: currency_symbol, separator: currency_separator) %> 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /lib/route_planner/route_planner.rb: -------------------------------------------------------------------------------- 1 | module Plugins 2 | class Routes < Base 3 | def locals 4 | { routes:, origin:, destination:, last_updated: } 5 | end 6 | 7 | private 8 | 9 | def routes 10 | [ 11 | { 12 | name: "Via N234", 13 | distance: "13.2 km", 14 | duration: "17 min", 15 | is_fastest: true, 16 | travel_mode: '🚘' 17 | }, 18 | { 19 | name: "via R. São Romão/N234 and N234", 20 | distance: "15.8 km", 21 | duration: "18 min", 22 | is_fastest: false, 23 | travel_mode: '🚘' 24 | }, 25 | { 26 | name: "via CM1037", 27 | distance: "12.9 km", 28 | duration: "18 min", 29 | is_fastest: false, 30 | travel_mode: '🚘' 31 | } 32 | ] 33 | end 34 | 35 | def origin = "Febres, Portugal" 36 | end 37 | 38 | def destination = "Ourentã, Portugal" 39 | end 40 | 41 | def last_updated = "7:30 AM" 42 | end 43 | end -------------------------------------------------------------------------------- /lib/stock_price/views/shared/_ticker_large.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= ticker[:symbol].gsub(' ', ' ').html_safe %> 8 | <%= ticker[:name] %> 9 |
10 |
11 |
12 |
13 |
14 | <%= ticker[:change] %> 15 | Daily Change 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | <%= number_to_currency(ticker[:price], unit: currency_symbol, separator: currency_separator) %> 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /lib/todoist/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <% todos.reverse.each_with_index do |doing_and_done, list_index| %> 4 |
5 | <%= doing_and_done[:label].titleize %> 6 |
7 | <% doing_and_done[:items].each_with_index do |item, idx| %> 8 |
9 |
10 | <%= idx + 1 %> 11 |
12 |
13 | <%= item %> 14 |
15 |
16 | <% end %> 17 |
18 |
19 | <% end %> 20 |
21 | 22 | <%= render 'plugins/todo_list/shared/title_bar', instance_name: instance_name %> 23 |
24 | -------------------------------------------------------------------------------- /lib/todoist/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <% todos.each_with_index do |doing_and_done, list_index| %> 4 |
5 | <%= doing_and_done[:label].titleize %> 6 | <% doing_and_done[:items].each_with_index do |item, idx| %> 7 |
8 |
9 | <%= idx + 1 %> 10 |
11 |
12 | <%= item %> 13 |
14 |
15 | <% end %> 16 | <% hidden_items_count = doing_and_done[:items].size - 3 %> 17 |
18 | <% if list_index == 0 %> 19 |
20 | <% end %> 21 | <% end %> 22 |
23 | 24 | <%= render 'plugins/todo_list/shared/title_bar', instance_name: instance_name %> 25 |
26 | -------------------------------------------------------------------------------- /lib/days_left_until/days_left_until.rb: -------------------------------------------------------------------------------- 1 | module Plugins 2 | class DaysLeftUntil < Base 3 | 4 | def locals 5 | { days_passed:, days_left:, percent_passed:, show_days_passed:, show_days_left:, message: } 6 | end 7 | 8 | private 9 | 10 | def start_date 11 | return plugin_settings.created_at.to_date.to_s unless settings['start_date'].present? 12 | 13 | settings['start_date'] 14 | end 15 | 16 | def end_date = settings['end_date'] 17 | 18 | def days_passed 19 | (today - start_date.to_date).to_i 20 | end 21 | 22 | def days_left 23 | (end_date.to_date - today).to_i 24 | end 25 | 26 | def percent_passed 27 | ((days_passed.to_f / (days_passed + days_left)) * 100).round 28 | rescue FloatDomainError 29 | 100 30 | end 31 | 32 | def show_days_passed 33 | return true unless settings['show_days_passed'].present? 34 | 35 | settings['show_days_passed'] == 'yes' 36 | end 37 | 38 | def show_days_left 39 | return true unless settings['show_days_left'].present? 40 | 41 | settings['show_days_left'] == 'yes' 42 | end 43 | 44 | def today = user.datetime_now.to_date 45 | 46 | # TODO: NOT in use 47 | def message 48 | settings['message'] 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/todoist/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <% todos.reverse.each_with_index do |doing_and_done, list_index| %> 5 |
6 | <%= doing_and_done[:label].titleize %> 7 | <% visible_items = doing_and_done[:items] %> 8 | <% hidden_items_count = doing_and_done[:items].size - visible_items.size %> 9 |
10 | <% visible_items.each_with_index do |item, idx| %> 11 |
12 |
13 | <%= idx + 1 %> 14 |
15 |
16 | <%= item %> 17 |
18 |
19 | <% end %> 20 |
21 |
22 | <% end %> 23 |
24 |
25 | 26 | <%= render 'plugins/todo_list/shared/title_bar', instance_name: instance_name %> 27 |
28 | -------------------------------------------------------------------------------- /lib/hacker_news/hacker_news.rb: -------------------------------------------------------------------------------- 1 | module Plugins 2 | class HackerNews < Base 3 | 4 | STORY_COUNT = 14.freeze 5 | 6 | STORY_TYPES = { 7 | 'top_stories' => 'topstories', 8 | 'show_hn' => 'showstories' 9 | }.freeze 10 | 11 | STORY_LABEL = { 12 | 'top_stories' => 'Top Stories', 13 | 'show_hn' => 'Show HN' 14 | }.freeze 15 | 16 | def locals 17 | { stories:, category: } 18 | end 19 | 20 | private 21 | 22 | def stories 23 | fetch_stories[..STORY_COUNT].map do |story_id| 24 | story = fetch_item(story_id) 25 | { 26 | title: story['title'].gsub(/show hn: /i, ''), 27 | score: story['score'], 28 | by: story['by'] 29 | } 30 | end 31 | end 32 | 33 | def fetch_stories = HTTParty.get("https://hacker-news.firebaseio.com/v0/#{story_type}.json", headers:) 34 | 35 | def fetch_item(story_id) = HTTParty.get("https://hacker-news.firebaseio.com/v0/item/#{story_id}.json", headers:) 36 | 37 | # https://github.com/HackerNews/API?tab=readme-ov-file#ask-show-and-job-stories 38 | def story_type 39 | STORY_TYPES[settings['story_type']] 40 | end 41 | 42 | def category 43 | STORY_LABEL[settings['story_type']] 44 | end 45 | 46 | def headers 47 | { 'content-type' => 'application/json' } 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/route_planner/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | <%= origin %> → <%= destination %> 7 |
8 |
9 | 10 |
11 | <% routes.each_with_index do |route, index| %> 12 |
13 |
14 | <% if route[:is_fastest] %> 15 | 16 | <% end %> 17 |
18 |
19 | <%= route[:name] %> (<%= route[:travel_mode] %>) 20 |
21 | <%= route[:duration] %> 22 | (<%= route[:distance] %>) 23 |
24 |
25 |
26 | <% end %> 27 | 28 |
29 | 30 |
31 | 32 | Daily Commute 33 | Route Planner 34 |
35 |
-------------------------------------------------------------------------------- /lib/calendar/views/_quadrant.html.erb: -------------------------------------------------------------------------------- 1 | <% days_to_show = event_layout == 'today_only' ? 1 : 2 %> 2 | 3 |
4 |
5 |
6 |
7 | <% formulate_and_group_events_by_day(events, today_in_tz, days_to_show).to_h.each_pair do |day, events| %> 8 | <% idx = 0 %> 9 | <%= day %> 10 | <% events.each do |event| %> 11 | <% if event[:all_day] %> 12 | <%= render partial: 'plugins/calendars/all_day_event', locals: { event: event, include_description: } %> 13 | <% else %> 14 |
15 |
16 | <%= idx += 1 %> 17 |
18 |
19 | <%= event[:summary] %> 20 |
21 | <%= "#{event[:start]}" %> - <%= event[:end] %> 22 |
23 |
24 |
25 | <% end %> 26 | <% end %> 27 | <% end %> 28 |
29 |
30 |
31 | 32 | <%= render partial: "plugins/calendars/title_bar", locals: local_assigns %> 33 |
34 | -------------------------------------------------------------------------------- /lib/days_left_until/views/_progress_bar.html.erb: -------------------------------------------------------------------------------- 1 | 47 | 48 | 49 | <% max_days ||= 74 %> 50 | 51 |
52 | <% max_days.times do |idx| %> 53 | <% if ((idx.to_f / max_days) * 100) <= percent_passed %> 54 |
55 | <% elsif ((idx.to_f / max_days) * 100) >= percent_passed && (((idx-1).to_f / max_days) * 100) <= percent_passed %> 56 |
57 | <% else %> 58 |
59 | <% end %> 60 | <% end %> 61 |
62 | -------------------------------------------------------------------------------- /lib/github_commit_graph/views/v1/_common.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/github_commit_graph/views/v1/_quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= contributions[:total] %> 8 | Contributions 9 |
10 |
11 |
12 |
13 |
14 | <%= stats[:current_streak] %> 15 | Current streak 16 |
17 |
18 |
19 | 20 |
21 |
22 | <% contributions[:commits].each do |week_of_commits| %> 23 | <% week_of_commits[:contributionDays].each do |day| %> 24 | 25 | <% end %> 26 | <% end %> 27 |
28 |
29 | 30 |
31 | 32 | GitHub 33 | <%= instance_name %> 34 |
35 |
36 | 37 | <%= render 'plugins/github_commit_graph/v1/common' %> -------------------------------------------------------------------------------- /lib/parcel/views/shared/_header.html.erb: -------------------------------------------------------------------------------- 1 | <% compact ||= false %> 2 | <% value_size ||= '' %> 3 | 4 |
5 |
6 |
7 |
8 | <% if compact %> 9 | 10 | <%= deliveries.count %> 11 | <%= t("renders.parcel.filter_modes.#{filter_mode.downcase}", count: deliveries.count, locale:) %> 12 | 13 | <% else %> 14 | 15 | <%= deliveries.count %> 16 | 17 | 18 | <%= t("renders.parcel.filter_modes.#{filter_mode.downcase}", count: deliveries.count, locale:) %> 19 | 20 | <% end %> 21 |
22 |
23 | 24 |
25 |
26 |
27 | <% if compact %> 28 | 29 | <%= deliveries.count { it[:status_key] == 'out_for_delivery' } %> 30 | <%= t('renders.parcel.status.out_for_delivery', locale:) %> 31 | 32 | <% else %> 33 | 34 | <%= deliveries.count { it[:status_key] == 'out_for_delivery' } %> 35 | 36 | 37 | <%= t('renders.parcel.status.out_for_delivery', locale:) %> 38 | 39 | <% end %> 40 |
41 |
42 |
-------------------------------------------------------------------------------- /lib/parcel/views/parcel/shared/_header.html.erb: -------------------------------------------------------------------------------- 1 | <% compact ||= false %> 2 | <% value_size ||= '' %> 3 | 4 |
5 |
6 |
7 |
8 | <% if compact %> 9 | 10 | <%= deliveries.count %> 11 | <%= t("renders.parcel.filter_modes.#{filter_mode.downcase}", count: deliveries.count, locale:) %> 12 | 13 | <% else %> 14 | 15 | <%= deliveries.count %> 16 | 17 | 18 | <%= t("renders.parcel.filter_modes.#{filter_mode.downcase}", count: deliveries.count, locale:) %> 19 | 20 | <% end %> 21 |
22 |
23 | 24 |
25 |
26 |
27 | <% if compact %> 28 | 29 | <%= deliveries.count { it[:status_key] == 'out_for_delivery' } %> 30 | <%= t('renders.parcel.status.out_for_delivery', locale:) %> 31 | 32 | <% else %> 33 | 34 | <%= deliveries.count { it[:status_key] == 'out_for_delivery' } %> 35 | 36 | 37 | <%= t('renders.parcel.status.out_for_delivery', locale:) %> 38 | 39 | <% end %> 40 |
41 |
42 |
-------------------------------------------------------------------------------- /lib/weather/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 |
10 |
11 |
12 |
13 | <%= temperature&.round %>° 14 | <%= conditions %> 15 |
16 |
17 |
18 |
19 |
20 | <%= forecast[:today][:mintemp] %>° - <%= forecast[:today][:maxtemp] %>° 21 | UV <%= forecast[:today][:uv_index] %> 22 |
23 |
24 |
25 |
26 |
27 |
28 | 29 |
30 | 31 |

<%= t('renders.weather.title') %>

32 | <%= instance_name %> 33 |
34 |
35 | 36 | <%= render 'plugins/weather/common' %> -------------------------------------------------------------------------------- /lib/weather/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | <%= temperature&.round %>° 15 | <%= t('renders.weather.temperature') %> 16 |
17 |
18 |
19 |
20 |
21 | <%= conditions %> 22 | Conditions 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 |
31 | 32 |

<%= t('renders.weather.title') %>

33 | <%= instance_name %> 34 |
35 |
36 | 37 | <%= render 'plugins/weather/common' %> -------------------------------------------------------------------------------- /lib/days_left_until/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | <% if show_days_left || show_days_passed %> 5 |
6 | <% if show_days_left %> 7 |
8 |
9 |
10 | <%= days_left %> 11 | <%= t('renders.days_left_year.days_left') %> 12 |
13 |
14 | <% end %> 15 | 16 | <% if show_days_passed %> 17 |
18 |
19 |
20 | <%= days_passed %> 21 | <%= t('renders.days_left_year.days_passed') %> 22 |
23 |
24 | <% end %> 25 |
26 | <% end %> 27 | 28 | <%= render 'plugins/days_left_until/progress_bar', percent_passed: percent_passed %> 29 | 30 |
31 | 32 |
33 | 34 | Days Left Until... 35 | <%= instance_name %> 36 |
37 |
38 | -------------------------------------------------------------------------------- /lib/github_commit_graph/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 | <% unless framework_v2 %> 2 | <%= render 'plugins/github_commit_graph/v1/quadrant', local_assigns %> 3 | <% else %> 4 |
5 |
6 |
7 |
8 |
9 |
10 | <%= contributions[:total] %> 11 | Contributions 12 |
13 |
14 |
15 |
16 |
17 | <%= stats[:current_streak] %> 18 | Current streak 19 |
20 |
21 |
22 | 23 |
24 |
25 | <% contributions[:commits].each do |week_of_commits| %> 26 | <% week_of_commits[:contributionDays].each do |day| %> 27 | 28 | <% end %> 29 | <% end %> 30 |
31 |
32 | 33 |
34 | 35 | GitHub 36 | <%= instance_name %> 37 |
38 |
39 | 40 | <%= render 'plugins/github_commit_graph/common' %> 41 | <% end %> -------------------------------------------------------------------------------- /lib/days_left_until/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | <% if show_days_left || show_days_passed %> 7 |
8 | <% if show_days_passed %> 9 |
10 | <%= days_passed %> 11 | <%= t('renders.days_left_year.days_passed') %> 12 |
13 | <% end %> 14 | 15 | <% if show_days_left %> 16 |
17 | <%= days_left %> 18 | <%= t('renders.days_left_year.days_left') %> 19 |
20 | <% end %> 21 |
22 | <% end %> 23 | 24 | <%= render 'plugins/days_left_until/progress_bar', percent_passed: percent_passed %> 25 | 26 |
27 |
28 |
29 | 30 |
31 | 32 | Days Left Until... 33 | <%= instance_name %> 34 |
35 |
36 | -------------------------------------------------------------------------------- /lib/days_left_until/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | <% if show_days_left || show_days_passed %> 5 |
6 | <% if show_days_left %> 7 |
8 |
9 |
10 | <%= days_left %> 11 | <%= t('renders.days_left_year.days_left') %> 12 |
13 |
14 | <% end %> 15 | 16 | <% if show_days_passed %> 17 |
18 |
19 |
20 | <%= days_passed %> 21 | <%= t('renders.days_left_year.days_passed') %> 22 |
23 |
24 | <% end %> 25 |
26 | <% end %> 27 | 28 | <%= render 'plugins/days_left_until/progress_bar', max_days: 54, percent_passed: percent_passed %> 29 | 30 |
31 | 32 |
33 | 34 | Days Left Until... 35 | <%= instance_name %> 36 |
37 |
38 | -------------------------------------------------------------------------------- /lib/stock_price/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if tickers.size == 1 %> 3 |
4 | <% tickers.each_with_index do |ticker, index| %> 5 | <%= render 'lib/stock_price/views/shared/ticker_medium', ticker:, currency_symbol:, currency_separator: %> 6 | <% end %> 7 |
8 | <% elsif tickers.size == 2 %> 9 |
10 | <% tickers_to_display = tickers.first(2) %> 11 | <% tickers_to_display.each_with_index do |ticker, index| %> 12 | <%= render 'lib/stock_price/views/shared/ticker_xsmall', ticker:, currency_symbol:, currency_separator: %> 13 | <% if index < tickers_to_display.size - 1 %> 14 |
15 | <% end %> 16 | <% end %> 17 |
18 | <% elsif tickers.size >= 3 %> 19 |
20 | <% tickers_to_display = tickers.first(3) %> 21 | <% tickers_to_display.each_with_index do |ticker, index| %> 22 | <%= render 'lib/stock_price/views/shared/ticker_xxsmall', ticker:, currency_symbol:, currency_separator: %> 23 | <% if index < tickers_to_display.size - 1 %> 24 |
25 | <% end %> 26 | <% end %> 27 |
28 | <% end %> 29 | 30 |
31 | 32 | Stock Prices 33 |
34 |
35 | -------------------------------------------------------------------------------- /lib/days_left_until/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | <% if show_days_left || show_days_passed %> 5 |
6 | <% if show_days_left %> 7 |
8 |
9 |
10 | <%= days_left %> 11 | <%= t('renders.days_left_year.days_left') %> 12 |
13 |
14 | <% end %> 15 | 16 | <% if show_days_passed %> 17 |
18 |
19 |
20 | <%= days_passed %> 21 | <%= t('renders.days_left_year.days_passed') %> 22 |
23 |
24 | <% end %> 25 |
26 | <% end %> 27 | 28 | <%= render 'plugins/days_left_until/progress_bar', percent_passed: percent_passed %> 29 | 30 |
31 | 32 |
33 | 34 | Days Left Until... 35 | <%= instance_name %> 36 |
37 |
38 | -------------------------------------------------------------------------------- /lib/weather/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 |
8 |
9 | <%= temperature&.round %>° 10 | <%= conditions %> 11 | <%= forecast[:today][:mintemp] %>° - <%= forecast[:today][:maxtemp] %>° 12 | UV <%= forecast[:today][:uv_index] %> 13 |
14 |
15 |
16 |
17 |
18 | <%= forecast[:tomorrow][:day_override] || t('renders.weather.tomorrow') %> 19 | <%= forecast[:tomorrow][:conditions] %> 20 | [L/H] <%= forecast[:tomorrow][:mintemp] %>° - <%= forecast[:tomorrow][:maxtemp] %>° 21 | [UV] <%= forecast[:tomorrow][:uv_index] %> 22 |
23 |
24 |
25 |
26 | 27 |
28 | 29 |

<%= t('renders.weather.title') %>

30 | <%= instance_name %> 31 |
32 |
33 | 34 | <%= render 'plugins/weather/common' %> -------------------------------------------------------------------------------- /lib/route_planner/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | <%= origin %> → <%= destination %> 7 |
8 |
9 | 10 |
11 | 12 |
13 | <% routes.each do |route| %> 14 |
15 |
16 | <% if route[:is_fastest] %> 17 | 18 | <% end %> 19 |
20 |
21 | <%= route[:name] %> (<%= route[:travel_mode] %>) 22 |
23 | <%= route[:duration] %> 24 | (<%= route[:distance] %>) 25 |
26 | <% if route[:is_fastest] %> 27 | Fastest Route 28 | <% else %> 29 | Alternative Route 30 | <% end %> 31 | 32 |
33 |
34 | <% end %> 35 |
36 |
37 | 38 |
39 | 40 | Daily Commute 41 | Route Planner 42 |
43 |
-------------------------------------------------------------------------------- /lib/calendar/views/_half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 | <% days_to_show = event_layout == 'today_only' ? 1 : 3 %> 2 | 3 |
4 |
5 |
6 | <% formulate_and_group_events_by_day(events, today_in_tz, days_to_show).to_h.each_pair do |day, events| %> 7 | <% idx = 0 %> 8 | <%= day %> 9 | <% events.each do |event| %> 10 | <% if event[:all_day] %> 11 | <%= render partial: 'plugins/calendars/all_day_event', locals: { event: event, include_description: } %> 12 | <% else %> 13 |
14 |
15 | <%= idx += 1 %> 16 |
17 |
18 | <%= event[:summary] %> 19 | <% if include_description %> 20 | <%= event[:description] %> 21 | <% end %> 22 |
23 | <%= day %> 24 | <%= "#{event[:start]}" %> - <%= event[:end] %> 25 |
26 |
27 |
28 | <% end %> 29 | <% end %> 30 | <% end %> 31 | 32 |
33 |
34 | 35 | <%= render partial: "plugins/calendars/title_bar", locals: local_assigns %> 36 |
37 | -------------------------------------------------------------------------------- /lib/screenshot/screenshot.rb: -------------------------------------------------------------------------------- 1 | require 'open-uri' 2 | 3 | module Plugins 4 | class Screenshot < Base 5 | def locals 6 | { html_hex: Digest::SHA2.hexdigest(html_document) } # Adding to compare the hex of the HTML document 7 | end 8 | 9 | # rubocop:disable Security/Open, Lint/ShadowedException 10 | def html_document(*) 11 | @doc ||= begin 12 | raise InvalidURL if settings['url'].include?('localhost') 13 | 14 | document = Nokogiri::HTML(URI.open(url, request_specific_fields: headers, open_timeout: 5)) 15 | 16 | %w[script img].each do |element| 17 | absolutize_element(document, element) 18 | end 19 | document.to_s.encode("UTF-8") 20 | end 21 | rescue Addressable::URI::InvalidURIError, OpenURI::HTTPError, Errno::ENOENT, Net::OpenTimeout, Socket::ResolutionError, SocketError, InvalidURL => e 22 | handle_erroring_state(e.message) 23 | erroring_html(e.message) 24 | end 25 | # rubocop:enable Security/Open, Lint/ShadowedException 26 | 27 | private 28 | 29 | def url 30 | @url ||= begin 31 | uri = URI.parse(settings['url']) 32 | response = Net::HTTP.get_response(uri, headers) 33 | response.is_a?(Net::HTTPRedirection) ? response['location'] : settings['url'] 34 | end 35 | end 36 | 37 | def headers 38 | return {} unless settings['headers'] 39 | 40 | string_to_hash(settings['headers']) 41 | end 42 | 43 | def erroring_html(message) = "

Error #{message}

" 44 | 45 | def absolutize_element(doc, element) 46 | doc.at(element)&.attribute_nodes&.each do |tag| 47 | tag.value = "#{url.chomp('/')}/#{tag.value}" unless tag.value.include?('http') 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/route_planner/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | <%= origin %> → <%= destination %> 7 |
8 |
9 | 10 |
11 | 12 |
13 | <% routes.each do |route| %> 14 |
15 |
16 |
17 | <% if route[:is_fastest] %> 18 | 19 | <% end %> 20 |
21 |
22 | <%= route[:name] %> (<%= route[:travel_mode] %>) 23 |
24 | <%= route[:duration] %> 25 | <%= route[:distance] %> 26 |
27 | <% if route[:is_fastest] %> 28 | Fastest Route 29 | <% else %> 30 | Alternative Route 31 | <% end %> 32 | 33 |
34 |
35 |
36 | 37 | <% end %> 38 |
39 |
40 | 41 |
42 | 43 | Daily Commute 44 | Route Planner 45 |
46 |
-------------------------------------------------------------------------------- /lib/google_analytics/views/full.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 | <% metrics.each do |metric, value| %> 8 |
9 |
10 |
11 | <%= format_number(value) %> 12 | <%= metric.to_s.titleize %> 13 |
14 |
15 | <% end %> 16 |
17 | 18 | <% formatted_data = histogram.map { |period| [period[:date], period[:pageviews]] } %> 19 | <%= line_chart formatted_data, adapter: :highcharts, prefix: "", thousands: ",", points: false, colors: ["black"], curve: true, 20 | library: { 21 | chart: { height: 260 }, 22 | plotOptions: { series: { animation: false, lineWidth: 4 } }, 23 | yAxis: { labels: { style: { fontSize: '16px', color: '#000000' } }, gridLineDashStyle: 'shortdot', gridLineWidth: 1, gridLineColor: '#000000', tickAmount: 5 }, 24 | xAxis: { type: 'daytime', labels: { style: { fontSize: '16px', color: '#000000' } }, lineWidth: 0, gridLineDashStyle: 'dot', tickWidth: 1, tickLength: 0, gridLineWidth: 1, gridLineColor: '#000000', tickPixelInterval: 120 }} 25 | %> 26 |
27 | 28 |
29 | 30 | Google Analytics 31 | <%= instance_name %> 32 |
33 |
-------------------------------------------------------------------------------- /lib/google_analytics/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 | <% metrics.first(2).each do |metric, value| %> 8 |
9 |
10 |
11 | <%= format_number(value) %> 12 | <%= metric.to_s.titleize %> 13 |
14 |
15 | <% end %> 16 |
17 | 18 | <% formatted_data = histogram.map { |period| [period[:date], period[:pageviews]] } %> 19 | <%= line_chart formatted_data, adapter: :highcharts, prefix: "", thousands: ",", points: false, colors: ["black"], curve: true, 20 | library: { 21 | chart: { height: 112 }, 22 | plotOptions: { series: { animation: false, lineWidth: 4 } }, 23 | yAxis: { labels: { style: { fontSize: '16px', color: '#000000' } }, gridLineDashStyle: 'shortdot', gridLineWidth: 1, gridLineColor: '#000000', tickAmount: 5 }, 24 | xAxis: { type: 'daytime', labels: { enabled: false }, lineWidth: 0, gridLineDashStyle: 'dot', tickWidth: 1, tickLength: 0, gridLineWidth: 1, gridLineColor: '#000000', tickPixelInterval: 120 }} 25 | %> 26 |
27 | 28 |
29 | 30 | Google Analytics 31 | <%= instance_name %> 32 |
33 |
-------------------------------------------------------------------------------- /lib/parcel/views/shared/_delivery.html.erb: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 | <% if style == 'compact' %> 8 |
9 | 10 | <%= delivery[:title] %> 11 | 12 | 13 | <%= t("renders.parcel.status.#{delivery[:status_key]}", locale:) %> 14 | 15 |
16 |
17 | <% if delivery[:delivery_by] %> 18 |
19 | <%= t('renders.parcel.delivery_by', locale:) %>: 20 | <%= delivery[:delivery_by] %> 21 |
22 | <% end %> 23 |
24 | <% else %> 25 | 26 | <%= delivery[:title] %> 27 | 28 | 29 | <%= t("renders.parcel.status.#{delivery[:status_key]}", locale:) %> 30 | 31 |
32 |
33 | <%= delivery[:latest] %> 34 |
35 | <% if delivery[:delivery_by] %> 36 |
37 | <%= t('renders.parcel.delivery_by', locale:) %>: 38 | <%= delivery[:delivery_by] %> 39 |
40 | <% end %> 41 |
42 | <% end %> 43 |
44 |
-------------------------------------------------------------------------------- /lib/parcel/views/parcel/shared/_delivery.html.erb: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 | <% if style == 'compact' %> 8 |
9 | 10 | <%= delivery[:title] %> 11 | 12 | 13 | <%= t("renders.parcel.status.#{delivery[:status_key]}", locale:) %> 14 | 15 |
16 |
17 | <% if delivery[:delivery_by] %> 18 |
19 | <%= t('renders.parcel.delivery_by', locale:) %>: 20 | <%= delivery[:delivery_by] %> 21 |
22 | <% end %> 23 |
24 | <% else %> 25 | 26 | <%= delivery[:title] %> 27 | 28 | 29 | <%= t("renders.parcel.status.#{delivery[:status_key]}", locale:) %> 30 | 31 |
32 |
33 | <%= delivery[:latest] %> 34 |
35 | <% if delivery[:delivery_by] %> 36 |
37 | <%= t('renders.parcel.delivery_by', locale:) %>: 38 | <%= delivery[:delivery_by] %> 39 |
40 | <% end %> 41 |
42 | <% end %> 43 |
44 |
-------------------------------------------------------------------------------- /lib/google_analytics/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 | <% metrics.each do |metric, value| %> 8 |
9 |
10 |
11 | <%= format_number(value) %> 12 | <%= metric.to_s.titleize %> 13 |
14 |
15 | <% end %> 16 |
17 | 18 | <% formatted_data = histogram.map { |period| [period[:date], period[:pageviews]] } %> 19 | <%= line_chart formatted_data, adapter: :highcharts, prefix: "", thousands: ",", points: false, colors: ["black"], curve: true, 20 | library: { 21 | chart: { height: 280 }, 22 | plotOptions: { series: { animation: false, lineWidth: 4 } }, 23 | yAxis: { labels: { style: { fontSize: '16px', color: '#000000' } }, gridLineDashStyle: 'shortdot', gridLineWidth: 1, gridLineColor: '#000000', tickAmount: 5 }, 24 | xAxis: { type: 'daytime', labels: { style: { fontSize: '16px', color: '#000000' } }, lineWidth: 0, gridLineDashStyle: 'dot', tickWidth: 1, tickLength: 0, gridLineWidth: 1, gridLineColor: '#000000', tickPixelInterval: 120 }} 25 | %> 26 |
27 | 28 |
29 | 30 | Google Analytics 31 | <%= instance_name %> 32 |
33 |
-------------------------------------------------------------------------------- /lib/github_commit_graph/views/v1/_half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= contributions[:total] %> 8 | Contributions in last year 9 |
10 |
11 |
12 |
13 |
14 |
15 | <%= stats[:longest_streak] %> 16 | Longest streak 17 |
18 |
19 | 20 |
21 | 22 |
23 |
24 |
25 | <%= stats[:current_streak] %> 26 | Current streak 27 |
28 |
29 |
30 |
31 | 32 |
33 | 34 |
35 | <% contributions[:commits].each do |week_of_commits| %> 36 | <% week_of_commits[:contributionDays].each do |day| %> 37 | 38 | <% end %> 39 | <% end %> 40 |
41 |
42 | 43 |
44 | 45 | GitHub 46 | <%= instance_name %> 47 |
48 |
49 | 50 | <%= render 'plugins/github_commit_graph/v1/common' %> -------------------------------------------------------------------------------- /lib/github_commit_graph/views/v1/_half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= contributions[:total] %> 9 | Contributions 10 |
11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | <%= stats[:longest_streak] %> 19 | Longest streak 20 |
21 |
22 | 23 |
24 | 25 |
26 |
27 |
28 | <%= stats[:current_streak] %> 29 | Current streak 30 |
31 |
32 |
33 |
34 |
35 | <% contributions[:commits].each do |week_of_commits| %> 36 | <% week_of_commits[:contributionDays].each do |day| %> 37 | 38 | <% end %> 39 | <% end %> 40 |
41 |
42 |
43 |
44 | 45 |
46 | 47 | GitHub 48 | <%= instance_name %> 49 |
50 |
51 | 52 | <%= render 'plugins/github_commit_graph/v1/common' %> -------------------------------------------------------------------------------- /lib/hacker_news/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | <% stories[0..5].each_with_index do |post, idx| %> 7 |
8 |
9 | <%= idx + 1 %> 10 |
11 |
12 | <%= post[:title] %> 13 |
14 | <%= post[:score] %> votes 15 | by <%= post[:by] %> 16 |
17 |
18 |
19 | <% end %> 20 |
21 |
22 | 23 |
24 |
25 | <% stories[6..11].each_with_index do |post, idx| %> 26 |
27 |
28 | <%= idx + 7 %> 29 |
30 |
31 | <%= post[:title] %> 32 |
33 | <%= post[:score] %> votes 34 | by <%= post[:by] %> 35 |
36 |
37 |
38 | <% end %> 39 |
40 |
41 |
42 |
43 | 44 |
45 | 46 | Hacker News 47 | <%= category %> 48 |
49 |
50 | -------------------------------------------------------------------------------- /lib/github_commit_graph/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 | <% unless framework_v2 %> 2 | <%= render 'plugins/github_commit_graph/v1/half_vertical', local_assigns %> 3 | <% else %> 4 |
5 |
6 |
7 |
8 |
9 |
10 | <%= contributions[:total] %> 11 | Contributions in last year 12 |
13 |
14 |
15 |
16 |
17 |
18 | <%= stats[:longest_streak] %> 19 | Longest streak 20 |
21 |
22 | 23 |
24 | 25 |
26 |
27 |
28 | <%= stats[:current_streak] %> 29 | Current streak 30 |
31 |
32 |
33 |
34 | 35 |
36 | 37 |
38 | <% contributions[:commits].each do |week_of_commits| %> 39 | <% week_of_commits[:contributionDays].each do |day| %> 40 | 41 | <% end %> 42 | <% end %> 43 |
44 |
45 | 46 |
47 | 48 | GitHub 49 | <%= instance_name %> 50 |
51 |
52 | 53 | <%= render 'plugins/github_commit_graph/common' %> 54 | <% end %> -------------------------------------------------------------------------------- /lib/notion/views/shared/_database_item.html.erb: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | <%= index + 1 %> 5 |
6 |
7 |
8 | <%= item[:title] %> 9 | <% if status_field.present? %> 10 | <% status_properties = filter_properties_by_names(item[:properties], [status_field]) %> 11 | <% if status_properties.any? %> 12 | <%= status_properties.first[:value] %> 13 | <% end %> 14 | <% end %> 15 |
16 | 17 | <% labeled_property_names = labeled_properties || [] %> 18 | <% if labeled_property_names.any? %> 19 | <% matched_labeled_properties = filter_properties_by_names(item[:properties], labeled_property_names) %> 20 | 21 | <% patterns = ["label--inverted", "label--outline", "label--underline"] %> 22 | 23 |
24 | <% matched_labeled_properties.each_with_index do |label, index| %> 25 | <% pattern = patterns[index % patterns.length] %> 26 | <% label_classes = "label label--small #{pattern}" %> 27 | <%= label[:value] %> 28 | <% end %> 29 |
30 | <% end %> 31 | 32 | <% listed_property_names = listed_properties || [] %> 33 | <% if listed_property_names.any? %> 34 | <% matched_listed_properties = filter_properties_by_names(item[:properties], listed_property_names) %> 35 | 36 | <% if matched_listed_properties.any? %> 37 |
38 | <% matched_listed_properties.each_with_index do |prop, index| %> 39 | <%= "✶" if index > 0 %> <%= prop[:value] %> 40 | <% end %> 41 |
42 | <% end %> 43 | <% end %> 44 |
45 |
-------------------------------------------------------------------------------- /lib/github_commit_graph/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 | <% unless framework_v2 %> 2 | <%= render 'plugins/github_commit_graph/v1/half_horizontal', local_assigns %> 3 | <% else %> 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | <%= contributions[:total] %> 12 | Contributions 13 |
14 |
15 | 16 |
17 | 18 |
19 |
20 |
21 | <%= stats[:longest_streak] %> 22 | Longest streak 23 |
24 |
25 | 26 |
27 | 28 |
29 |
30 |
31 | <%= stats[:current_streak] %> 32 | Current streak 33 |
34 |
35 |
36 |
37 |
38 | <% contributions[:commits].each do |week_of_commits| %> 39 | <% week_of_commits[:contributionDays].each do |day| %> 40 | 41 | <% end %> 42 | <% end %> 43 |
44 |
45 |
46 |
47 | 48 |
49 | 50 | GitHub 51 | <%= instance_name %> 52 |
53 |
54 | 55 | <%= render 'plugins/github_commit_graph/common' %> 56 | <% end %> -------------------------------------------------------------------------------- /lib/email_meter/email_meter.rb: -------------------------------------------------------------------------------- 1 | module Plugins 2 | class EmailMeter < Base 3 | 4 | def locals 5 | { metrics: } 6 | end 7 | 8 | private 9 | 10 | def base_url = 'https://api02.emailmeter.com/bqi' 11 | 12 | def headers 13 | { authorization: "Token #{settings['api_token']}" } 14 | end 15 | 16 | def metrics 17 | { 18 | messages_sent:, 19 | recipients:, 20 | avg_response_time:, 21 | messages_received:, 22 | senders: 23 | } 24 | end 25 | 26 | def timestamp_from 27 | lookback_period.days.ago.to_date.to_s 28 | end 29 | 30 | def timestamp_to 31 | Time.now.in_time_zone(user.tz).to_date.to_s 32 | end 33 | 34 | def messages_sent 35 | endpoint = "/total_sent_emails?timestamp_from=#{timestamp_from}×tamp_to=#{timestamp_to}" 36 | response = fetch(base_url + endpoint, headers: headers) 37 | response.dig(0, 'count') || 0 38 | end 39 | 40 | def recipients 41 | endpoint = "/sent_messages_list_by_recipient?timestamp_from=#{timestamp_from}×tamp_to=#{timestamp_to}" 42 | response = fetch(base_url + endpoint, headers: headers) 43 | response.count 44 | end 45 | 46 | def avg_response_time 47 | endpoint = "/reply_times?timestamp_from=#{timestamp_from}×tamp_to=#{timestamp_to}" 48 | response = fetch(base_url + endpoint, headers: headers) 49 | avg_reply_time = response.dig(0, 'avg_reply_time') || 0 50 | ActiveSupport::Duration.build(avg_reply_time).parts # => {:hours=>22, :minutes=>21, :seconds=>22.639999999999418} 51 | end 52 | 53 | def messages_received 54 | endpoint = "/total_received_emails?timestamp_from=#{timestamp_from}×tamp_to=#{timestamp_to}" 55 | response = fetch(base_url + endpoint, headers: headers) 56 | response.dig(0, 'count') || 0 57 | end 58 | 59 | def senders 60 | endpoint = "/received_messages_list_by_sender?timestamp_from=#{timestamp_from}×tamp_to=#{timestamp_to}" 61 | response = fetch(base_url + endpoint, headers: headers) 62 | response.count 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/stock_price/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if tickers.size <= 2 %> 3 |
4 | <% tickers.each_with_index do |ticker, index| %> 5 | <%= render 'lib/stock_price/views/shared/ticker_medium', ticker:, currency_symbol:, currency_separator: %> 6 | <% if tickers.size == 2 && index == 0 %> 7 |
8 | <% end %> 9 | <% end %> 10 |
11 | <% elsif tickers.size == 3 %> 12 |
13 | <% tickers.each_with_index do |ticker, index| %> 14 | <%= render 'lib/stock_price/views/shared/ticker_small', ticker:, currency_symbol:, currency_separator: %> 15 | <% if index == 0 || index == 1 %> 16 |
17 | <% end %> 18 | <% end %> 19 |
20 | <% elsif tickers.size == 4 %> 21 |
22 | <% tickers_to_display = tickers.first(6) %> 23 | <% tickers_to_display.each_with_index do |ticker, index| %> 24 | <%= render 'lib/stock_price/views/shared/ticker_xsmall', ticker:, currency_symbol:, currency_separator: %> 25 | <% if index < tickers_to_display.size - 1 %> 26 |
27 | <% end %> 28 | <% end %> 29 |
30 | <% else %> 31 |
32 | <% tickers_to_display = tickers.first(6) %> 33 | <% tickers_to_display.each_with_index do |ticker, index| %> 34 | <%= render 'lib/stock_price/views/shared/ticker_xxsmall', ticker:, currency_symbol:, currency_separator: %> 35 | <% if index < tickers_to_display.size - 1 %> 36 |
37 | <% end %> 38 | <% end %> 39 |
40 | <% end %> 41 | 42 |
43 | 44 | Stock Prices 45 |
46 |
47 | -------------------------------------------------------------------------------- /lib/notion/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if defined?(error) && error %> 3 | <%= render 'plugins/notion/shared/error_section', 4 | title_class: "title title--small", 5 | title_text: "Error", 6 | description_text: error %> 7 | <% elsif display_type == "database" %> 8 |
9 | <% if items && items.any? %> 10 |
11 |
12 | <% items.each_with_index do |item, idx| %> 13 | <%= render partial: 'plugins/notion/shared/database_item', locals: { item: item, index: idx, current_layout: "quadrant", labeled_properties: labeled_properties, listed_properties: listed_properties, status_field: status_field } %> 14 | <% end %> 15 |
16 |
17 | <% else %> 18 | <%= render 'plugins/notion/shared/error_section', 19 | title_class: "title title--small", 20 | title_text: "No Content", 21 | description_text: "Check config" %> 22 | <% end %> 23 |
24 | <% elsif display_type == "page" %> 25 |
26 | <% if items && items[:blocks] && items[:blocks].any? %> 27 |
28 |
29 | <% items[:blocks].each_with_index do |block, idx| %> 30 | <%= render 'plugins/notion/shared/page_block', block: block, index: idx, current_layout: "quadrant", image_height: image_height %> 31 | <% end %> 32 |
33 |
34 | <% else %> 35 | <%= render 'plugins/notion/shared/error_section', 36 | title_class: "title title--small", 37 | title_text: "No Content", 38 | description_text: "Check config" %> 39 | <% end %> 40 |
41 | <% end %> 42 | 43 | <%= render partial: 'plugins/notion/shared/title_bar', locals: { instance_name: instance_name } %> 44 |
-------------------------------------------------------------------------------- /lib/notion/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if defined?(error) && error %> 3 | <%= render 'plugins/notion/shared/error_section', 4 | title_class: "title title--small", 5 | title_text: "Notion Error", 6 | description_text: error %> 7 | <% elsif display_type == "database" %> 8 |
9 | <% if items && items.any? %> 10 |
11 |
12 | <% items.each_with_index do |item, idx| %> 13 | <%= render partial: 'plugins/notion/shared/database_item', locals: { item: item, index: idx, current_layout: "half_vertical", labeled_properties: labeled_properties, listed_properties: listed_properties, status_field: status_field } %> 14 | <% end %> 15 |
16 |
17 | <% else %> 18 | <%= render 'plugins/notion/shared/error_section', 19 | title_class: "title title--small", 20 | title_text: "No Content", 21 | description_text: "Check your Notion configuration" %> 22 | <% end %> 23 |
24 | <% elsif display_type == "page" %> 25 |
26 | <% if items && items[:blocks] && items[:blocks].any? %> 27 |
28 |
29 | <% items[:blocks].each_with_index do |block, idx| %> 30 | <%= render 'plugins/notion/shared/page_block', block: block, index: idx, current_layout: "half_vertical", image_height: image_height %> 31 | <% end %> 32 |
33 |
34 | <% else %> 35 | <%= render 'plugins/notion/shared/error_section', 36 | title_class: "title title--small", 37 | title_text: "No Content", 38 | description_text: "Check your Notion configuration" %> 39 | <% end %> 40 |
41 | <% end %> 42 | 43 | <%= render partial: 'plugins/notion/shared/title_bar', locals: { instance_name: instance_name } %> 44 |
-------------------------------------------------------------------------------- /lib/notion/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if defined?(error) && error %> 3 | <%= render 'plugins/notion/shared/error_section', 4 | title_class: "title", 5 | title_text: "Notion Error", 6 | description_text: error %> 7 | <% elsif display_type == "database" %> 8 |
9 | <% if items && items.any? %> 10 |
11 |
12 | <% items.each_with_index do |item, idx| %> 13 | <%= render partial: 'plugins/notion/shared/database_item', locals: { item: item, index: idx, current_layout: "full", labeled_properties: labeled_properties, listed_properties: listed_properties, status_field: status_field } %> 14 | <% end %> 15 |
16 |
17 | <% else %> 18 | <%= render 'plugins/notion/shared/error_section', 19 | title_class: "title", 20 | title_text: "No Content Available", 21 | description_text: "Check your Notion configuration and try again" %> 22 | <% end %> 23 |
24 | <% elsif display_type == "page" %> 25 |
26 | <% if items && items[:blocks] && items[:blocks].any? %> 27 |
28 |
29 | <% items[:blocks].each_with_index do |block, idx| %> 30 | <%= render 'plugins/notion/shared/page_block', block: block, index: idx, current_layout: "full", image_height: image_height %> 31 | <% end %> 32 |
33 |
34 | <% else %> 35 | <%= render 'plugins/notion/shared/error_section', 36 | title_class: "title", 37 | title_text: "No Content Available", 38 | description_text: "Check your Notion configuration and try again" %> 39 | <% end %> 40 |
41 | <% end %> 42 | 43 | <%= render partial: 'plugins/notion/shared/title_bar', locals: { instance_name: instance_name } %> 44 |
-------------------------------------------------------------------------------- /lib/google_analytics/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 |
9 | <% metrics.first(3).each_with_index do |(metric, value), index| %> 10 |
11 |
12 |
13 | <%= format_number(value) %> 14 | <%= metric.to_s.titleize %> 15 |
16 |
17 | <% if index < metrics.first(3).size - 1 %> 18 |
19 | <% end %> 20 | <% end %> 21 |
22 |
23 | 24 |
25 | <% formatted_data = histogram.map { |period| [period[:date], period[:pageviews]] } %> 26 | <%= line_chart formatted_data, adapter: :highcharts, prefix: "", thousands: ",", points: false, colors: ["black"], curve: true, 27 | library: { 28 | chart: { height: 176, width: 612 }, 29 | plotOptions: { series: { animation: false, lineWidth: 4 } }, 30 | yAxis: { labels: { style: { fontSize: '16px', color: '#000000' } }, gridLineDashStyle: 'shortdot', gridLineWidth: 1, gridLineColor: '#000000', tickAmount: 5 }, 31 | xAxis: { type: 'daytime', labels: { enabled: false }, lineWidth: 0, gridLineDashStyle: 'dot', tickWidth: 1, tickLength: 0, gridLineWidth: 1, gridLineColor: '#000000', tickPixelInterval: 120 } 32 | } 33 | %> 34 |
35 |
36 |
37 | 38 |
39 | 40 | Google Analytics 41 | <%= instance_name %> 42 |
43 |
-------------------------------------------------------------------------------- /lib/notion/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if defined?(error) && error %> 3 | <%= render 'plugins/notion/shared/error_section', 4 | title_class: "title title--small", 5 | title_text: "Notion Error", 6 | description_text: error %> 7 | <% elsif display_type == "database" %> 8 |
9 | <% if items && items.any? %> 10 |
11 |
12 | <% items.each_with_index do |item, idx| %> 13 | <%= render partial: 'plugins/notion/shared/database_item', locals: { item: item, index: idx, current_layout: "half_horizontal", labeled_properties: labeled_properties, listed_properties: listed_properties, status_field: status_field } %> 14 | <% end %> 15 |
16 |
17 | <% else %> 18 | <%= render 'plugins/notion/shared/error_section', 19 | title_class: "title title--small", 20 | title_text: "No Content", 21 | description_text: "Check your Notion configuration" %> 22 | <% end %> 23 |
24 | <% elsif display_type == "page" %> 25 |
26 | <% if items && items[:blocks] && items[:blocks].any? %> 27 |
28 |
29 | <% items[:blocks].each_with_index do |block, idx| %> 30 | <%= render 'plugins/notion/shared/page_block', block: block, index: idx, current_layout: "half_horizontal", image_height: image_height %> 31 | <% end %> 32 |
33 |
34 | <% else %> 35 | <%= render 'plugins/notion/shared/error_section', 36 | title_class: "title title--small", 37 | title_text: "No Content", 38 | description_text: "Check your Notion configuration" %> 39 | <% end %> 40 |
41 | <% end %> 42 | 43 | <%= render partial: 'plugins/notion/shared/title_bar', locals: { instance_name: instance_name } %> 44 |
-------------------------------------------------------------------------------- /lib/route_planner/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= origin %> → <%= destination %> 8 |
9 |
10 |
11 |
12 |
13 | <%= last_updated %> 14 | Last Updated 15 |
16 |
17 |
18 | 19 |
20 | 21 |
22 | <% routes.each do |route| %> 23 |
24 |
25 |
26 |
27 | <% if route[:is_fastest] %> 28 | 29 | <% end %> 30 |
31 |
32 | <%= route[:name] %> (<%= route[:travel_mode] %>) 33 |
34 | <%= route[:duration] %> 35 | <%= route[:distance] %> 36 |
37 |
38 | <% if route[:is_fastest] %> 39 | Fastest Route 40 | <% else %> 41 | Alternative Route 42 | <% end %> 43 |
44 |
45 |
46 |
47 |
48 | <% end %> 49 |
50 |
51 | 52 |
53 | 54 | Daily Commute 55 | Route Planner 56 |
57 |
-------------------------------------------------------------------------------- /lib/email_meter/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | <%= number_with_delimiter(metrics[:messages_sent]) %> 7 | Messages Sent 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | <%= number_with_delimiter(metrics[:recipients]) %> 16 | Recipients 17 |
18 |
19 |
20 |
21 |
22 | 23 | <% if metrics.dig(:avg_response_time, :hours).to_i > 0 %> 24 | <%= metrics.dig(:avg_response_time, :hours) %>h 25 | <% end %> 26 | <%= metrics.dig(:avg_response_time, :minutes) %>m 27 | 28 | Avg Response Time 29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | <%= number_with_delimiter(metrics[:messages_received]) %> 38 | Messages Received 39 |
40 |
41 |
42 |
43 |
44 | <%= number_with_delimiter(metrics[:senders]) %> 45 | Senders 46 |
47 |
48 |
49 |
50 | 51 |
52 | 53 | Email Meter 54 | <%= instance_name %> 55 |
56 |
-------------------------------------------------------------------------------- /lib/email_meter/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= number_with_delimiter(metrics[:messages_sent]) %> 8 | Messages Sent 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | <%= number_with_delimiter(metrics[:recipients]) %> 18 | Recipients 19 |
20 |
21 |
22 |
23 |
24 | 25 | <% if metrics.dig(:avg_response_time, :hours).to_i > 0 %> 26 | <%= metrics.dig(:avg_response_time, :hours) %>h 27 | <% end %> 28 | <%= metrics.dig(:avg_response_time, :minutes) %>m 29 | 30 | Avg Response Time 31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | <%= number_with_delimiter(metrics[:messages_received]) %> 40 | Messages Received 41 |
42 |
43 |
44 |
45 |
46 | <%= number_with_delimiter(metrics[:senders]) %> 47 | Senders 48 |
49 |
50 |
51 |
52 | 53 |
54 | 55 | Email Meter 56 | <%= instance_name %> 57 |
58 |
-------------------------------------------------------------------------------- /lib/email_meter/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= number_with_delimiter(metrics[:messages_sent]) %> 8 | Messages Sent 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | <%= number_with_delimiter(metrics[:recipients]) %> 18 | Recipients 19 |
20 |
21 |
22 |
23 |
24 | 25 | <% if metrics.dig(:avg_response_time, :hours).to_i > 0 %> 26 | <%= metrics.dig(:avg_response_time, :hours) %>h 27 | <% end %> 28 | <%= metrics.dig(:avg_response_time, :minutes) %>m 29 | 30 | Avg Response Time 31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | <%= number_with_delimiter(metrics[:messages_received]) %> 40 | Messages Received 41 |
42 |
43 |
44 |
45 |
46 | <%= number_with_delimiter(metrics[:senders]) %> 47 | Senders 48 |
49 |
50 |
51 |
52 | 53 |
54 | 55 | Email Meter 56 | <%= instance_name %> 57 |
58 |
-------------------------------------------------------------------------------- /lib/todoist/form_fields.yaml: -------------------------------------------------------------------------------- 1 | - keyname: todoist_project_id 2 | field_type: xhrSelect 3 | name: Todoist Project 4 | description: Select Todoist Project 5 | encrypted: true 6 | - keyname: sort_grouping 7 | name: Sort/Grouping 8 | description: How to group tasks in the task list. 9 | field_type: select 10 | options: 11 | - None 12 | - Date 13 | - Date Added 14 | - Deadline 15 | - Priority 16 | - Label 17 | default: none 18 | - keyname: sort_sorting 19 | name: Sort/Sorting 20 | description: How to sort tasks in the task list. 21 | field_type: select 22 | options: 23 | - Manual 24 | - Name 25 | - Date 26 | - Date Added 27 | - Deadline 28 | - Priority 29 | default: date 30 | - keyname: sort_direction 31 | name: Sort/Direction 32 | description: The direction to sort tasks in the task list. 33 | field_type: select 34 | options: 35 | - Ascending 36 | - Descending 37 | default: ascending 38 | - keyname: filter_completed_tasks 39 | name: Completed Tasks 40 | description: To show completed tasks in the task list. 41 | field_type: select 42 | options: 43 | - Yes 44 | - No 45 | default: no 46 | - keyname: filter_date 47 | name: Filter/Date 48 | description: Filter tasks by date in the task list. 49 | field_type: select 50 | options: 51 | - All # nil 52 | - Today # `overdue | today` 53 | - This Week # `due before: next week` 54 | - Next 7 Days # `overdue | next 7 days` 55 | - This month # `due before: first day` 56 | - Next 30 Days # `overdue | next 30 days` 57 | - No date # `no date` 58 | default: all 59 | - keyname: filter_deadline 60 | name: Filter/Deadline 61 | description: Filter tasks by deadline in the task list. 62 | field_type: select 63 | options: 64 | - All 65 | - Today 66 | - This Week 67 | - Next 7 Days 68 | - This month 69 | - Next 30 Days 70 | - No deadline 71 | default: all 72 | - keyname: filter_priority 73 | name: Filter/Priority 74 | description: Filter tasks by priority in the task list. 75 | field_type: select 76 | options: 77 | - Priority 1: p1 78 | - Priority 2: p2 79 | - Priority 3: p3 80 | - Priority 4: p4 81 | multiple: true 82 | optional: true 83 | - keyname: todoist_filter_label_ids 84 | name: Filter/Label 85 | description: Filter tasks by labels. 86 | field_type: xhrSelect 87 | multiple: true 88 | optional: true 89 | -------------------------------------------------------------------------------- /lib/calendar/views/_full_month.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "plugins/calendars/common" %> 2 | 3 | 34 | 35 |
36 | 37 | 38 | 39 | 64 | -------------------------------------------------------------------------------- /lib/email_meter/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= number_with_delimiter(metrics[:messages_sent]) %> 9 | Messages Sent 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | <%= number_with_delimiter(metrics[:recipients]) %> 18 | Recipients 19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 | <% if metrics.dig(:avg_response_time, :hours).to_i > 0 %> 27 | <%= metrics.dig(:avg_response_time, :hours) %>h 28 | <% end %> 29 | <%= metrics.dig(:avg_response_time, :minutes) %>m 30 | 31 | Avg Response Time 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | <%= number_with_delimiter(metrics[:messages_received]) %> 40 | Messages Received 41 |
42 |
43 |
44 |
45 |
46 |
47 | <%= number_with_delimiter(metrics[:senders]) %> 48 | Senders 49 |
50 |
51 |
52 |
53 |
54 | 55 |
56 | 57 | Email Meter 58 | <%= instance_name %> 59 |
60 |
-------------------------------------------------------------------------------- /lib/calendar/views/_full_auto.html.erb: -------------------------------------------------------------------------------- 1 | <% zoom_mode ||= false %> 2 | 3 | <% days_to_show = event_layout == 'today_only' ? 1 : 3 %> 4 | <% max_event_reduction = zoom_mode ? 1 : 0 %> 5 | <% date_heading_size = zoom_mode ? 'large' : 'medium' %> 6 | <% heading_size = zoom_mode ? 'medium' : 'small' %> 7 | <% sub_heading_size = zoom_mode ? 'small' : 'xsmall' %> 8 | <% event_time_style = zoom_mode ? 'inverted' : 'underline' %> 9 | 10 |
11 |
12 |
13 | <% formulate_and_group_events_by_day(events, today_in_tz, days_to_show).to_h.each_pair do |day, events| %> 14 | <% idx = 0 %> 15 |
16 |
17 | <%= day %> 18 | <% max_event_count = include_description ? 6 : 8 %> 19 | <% hidden_events_count = events[max_event_count..].to_a.count %> 20 | <% events[0..max_event_count].each do |event| %> 21 | <% if event[:all_day] %> 22 | <%= render partial: 'plugins/calendars/all_day_event', locals: { event: event, include_description:, heading_size: } %> 23 | <% else %> 24 |
25 |
26 | <%= idx += 1 %> 27 |
28 |
29 | <%= event[:summary] %> 30 | <% if include_description %><%= event[:description].html_safe %><% end %> 31 |
32 | <%= "#{event[:start]}" %> - <%= event[:end] %> 33 |
34 |
35 |
36 | <% end %> 37 | <% end %> 38 | <% if hidden_events_count.positive? %> 39 |
40 |
41 |
42 | And <%= hidden_events_count %> more 43 |
44 |
45 | <% end %> 46 |
47 |
48 | <% end %> 49 |
50 |
51 | 52 | <%= render partial: "plugins/calendars/title_bar", locals: local_assigns %> 53 |
54 | -------------------------------------------------------------------------------- /lib/lunch_money/lunch_money.rb: -------------------------------------------------------------------------------- 1 | module Plugins 2 | class LunchMoney < Base 3 | 4 | BASE_URL = 'https://dev.lunchmoney.app/v1'.freeze 5 | 6 | def locals 7 | { items: } 8 | end 9 | 10 | private 11 | 12 | def items 13 | case item_type 14 | when 'budgets' then budgets 15 | when 'accounts' then accounts 16 | end 17 | end 18 | 19 | def budgets 20 | resp = fetch(budgets_url, headers:) 21 | budget_data = JSON.parse(resp.body) 22 | raise AccessTokenExpired if budget_data.is_a?(Hash) && budget_data['message'] == 'Access token does not exist.' 23 | 24 | budget_data.map do |budget| 25 | next if budget['is_income'] # show only expenses by default 26 | 27 | [CGI.unescapeHTML(budget['category_name']), budget.dig('data', beginning_of_month, 'spending_to_base') || 0] 28 | end.compact 29 | rescue AccessTokenExpired => e 30 | handle_erroring_state(e.message) 31 | [] 32 | end 33 | 34 | # user probably expects all items from "Accounts Overview" widget, which actually lives in 2 places (accounts, assets) 35 | def accounts 36 | resp = fetch(accounts_url, headers:) 37 | account_data = JSON.parse(resp.body) 38 | raise AccessTokenExpired if account_data.is_a?(Hash) && account_data['message'] == 'Access token does not exist.' 39 | 40 | resp = fetch(assets_url, headers:) 41 | asset_data = JSON.parse(resp.body) 42 | raise AccessTokenExpired if asset_data.is_a?(Hash) && asset_data['message'] == 'Access token does not exist.' 43 | 44 | accounts_map = account_data['plaid_accounts'].map do |account| 45 | [CGI.unescapeHTML(account['display_name'] || account['name']), account['balance']] 46 | end 47 | 48 | assets_map = asset_data['assets'].map do |asset| 49 | [CGI.unescapeHTML(asset['display_name'] || asset['name']), asset['balance']] 50 | end 51 | 52 | accounts_map + assets_map 53 | rescue AccessTokenExpired => e 54 | handle_erroring_state(e.message) 55 | [] 56 | end 57 | 58 | def headers 59 | { "Authorization" => "Bearer #{settings['access_token']}" } 60 | end 61 | 62 | def item_type 63 | return 'budgets' unless settings['item_type'].present? 64 | 65 | settings['item_type'] 66 | end 67 | 68 | def beginning_of_month = Date.today.beginning_of_month.to_s 69 | 70 | def end_of_month = Date.today.end_of_month.to_s 71 | 72 | def budgets_url = "#{BASE_URL}/budgets?start_date=#{beginning_of_month}&end_date=#{end_of_month}" 73 | 74 | def accounts_url = "#{BASE_URL}/plaid_accounts" 75 | 76 | def assets_url = "#{BASE_URL}/assets" 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/lunar_calendar/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= current_phase[:name] %> 8 | <%= t('renders.lunar_calendar.current_phase') %> 9 |
10 |
11 |
12 |
13 |
14 | <%= next_phase[:name] %> 15 | <%= t('renders.lunar_calendar.next_phase_short') %> (<%= next_phase[:date] %>) 16 |
17 |
18 |
19 | 20 |
21 | 22 | <% ordered_phase_sequence = phase_sequence.sort_by { |seq| seq[:date_full] } %> 23 | <% current = ordered_phase_sequence.find { |phase| phase[:current] } %> 24 |
25 |
26 | <% ordered_phase_sequence.each do |phase| %> 27 |
28 | 29 |
30 | <% end %> 31 |
32 |
33 | 34 |
35 | 36 |
37 |
38 |
39 |
40 | <%= next_full_moon %> 41 | <%= t('renders.lunar_calendar.next_full_moon') %> 42 |
43 |
44 |
45 |
46 |
47 | <%= next_new_moon %> 48 | <%= t('renders.lunar_calendar.next_new_moon') %> 49 |
50 |
51 |
52 |
53 | 54 |
55 | 56 | <%= t('renders.lunar_calendar.title') %> 57 | <%= instance_name %> 58 |
59 |
60 | 61 | 72 | -------------------------------------------------------------------------------- /lib/notion/views/shared/_page_block.html.erb: -------------------------------------------------------------------------------- 1 | <% if block[:type] == "divider" %> 2 |
3 | <% elsif ["table_of_contents", "table", "unsupported", "synced_block", "breadcrumb", "column_list"].include?(block[:type]) %> 4 | <% return %> 5 | <% else %> 6 |
7 |
8 | <% # Show meta for placeholders, database views, and non-heading/non-image blocks %> 9 | <% show_meta = (block[:type] == "image" && !block[:image_url]) || 10 | block[:type] == "child_database" || 11 | !%w[heading_1 heading_2 heading_3 image].include?(block[:type]) %> 12 | <% if show_meta %> 13 | <% meta_content = case block[:type] 14 | when "bulleted_list_item", "numbered_list_item" then "•" 15 | when "to_do" then block[:checked] ? "✓" : "☐" 16 | when "quote" then '❝' 17 | when "callout" then "[!]" 18 | when "child_page", "bookmark" then "↗" 19 | when "code" then "{}" 20 | end %> 21 | <% if meta_content %> 22 | <% meta_classes = "label label--gray" if block[:type] == "to_do" && block[:checked] %> 23 | <%= meta_content %> 24 | <% end %> 25 | <% end %> 26 |
27 |
28 | <% if block[:type] == "image" && block[:image_url] %> 29 | <%= block[:caption] || 'Image' %> 30 | <% if block[:caption] %> 31 | <%= block[:caption] %> 32 | <% end %> 33 | <% else %> 34 | <% text = case block[:type] 35 | when "image" then (block[:image_url] ? nil : "Image placeholder") 36 | when "child_database" then "Database view" 37 | else block[:text] 38 | end %> 39 | <% if text %> 40 | <% content_classes = ["clamp--none"] %> 41 | <% case block[:type] 42 | when "heading_1" 43 | content_classes << (current_layout == "quadrant" ? " title title--small" : " title title--large") 44 | when "heading_2" 45 | content_classes << (current_layout == "quadrant" ? " text text--small" : " title") 46 | when "heading_3" 47 | content_classes << (current_layout == "quadrant" ? " text text--small" : " title title--small") 48 | else 49 | content_classes << "description" 50 | end %> 51 | " data-pixel-perfect="true"><%= text %> 52 | <% end %> 53 | <% end %> 54 |
55 |
56 | <% end %> -------------------------------------------------------------------------------- /lib/github_commit_graph/views/v1/_full.html.erb: -------------------------------------------------------------------------------- 1 | <% unless (contributions[:errors].present?) %> 2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= contributions[:total] %> 9 | Contributions in last year 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | <%= stats[:longest_streak] %> 18 | Longest streak 19 |
20 |
21 |
22 |
23 |
24 | <%= stats[:current_streak] %> 25 | Current streak 26 |
27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 |
36 | <%= stats[:max_contributions] %> 37 | Most in a day 38 |
39 |
40 |
41 |
42 |
43 | <%= stats[:average_contributions] %> 44 | Average per day 45 |
46 |
47 |
48 |
49 |
50 | 51 |
52 | 53 |
54 | <% contributions[:commits].each do |week_of_commits| %> 55 | <% week_of_commits[:contributionDays].each do |day| %> 56 | 57 | <% end %> 58 | <% end %> 59 |
60 |
61 | 62 |
63 | 64 | GitHub 65 | <%= instance_name %> 66 |
67 |
68 | <%= render 'plugins/github_commit_graph/v1/common' %> 69 | <% else %> 70 | <%= render partial: 'plugins/errors/full', locals: { 71 | icon: 'github--render.svg', 72 | title: 'GitHub', 73 | instance: instance_name, 74 | error_message: contributions[:errors] 75 | } 76 | %> 77 | <% end %> 78 | -------------------------------------------------------------------------------- /lib/stock_price/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if tickers.size == 1 %> 3 |
4 | <% tickers.each_with_index do |ticker, index| %> 5 | <%= render 'lib/stock_price/views/shared/ticker_xlarge', ticker:, currency_symbol:, currency_separator: %> 6 | <% end %> 7 |
8 | <% elsif tickers.size == 2 %> 9 |
10 | <% tickers.each_with_index do |ticker, index| %> 11 | <%= render 'lib/stock_price/views/shared/ticker_medium', ticker:, currency_symbol:, currency_separator: %> 12 | <% end %> 13 |
14 | <% elsif tickers.size >= 3 && tickers.size <= 4 %> 15 |
16 |
17 |
18 | <% [0, 1,].each do |i| %> 19 | <% if tickers[i].present? %> 20 | <%= render 'lib/stock_price/views/shared/ticker_xsmall', ticker: tickers[i], currency_symbol:, currency_separator: %> 21 | <% if i < 1 %> 22 |
23 | <% end %> 24 | <% end %> 25 | <% end %> 26 |
27 |
28 | <% [2, 3].each do |i| %> 29 | <% if tickers[i].present? %> 30 | <%= render 'lib/stock_price/views/shared/ticker_xsmall', ticker: tickers[i], currency_symbol:, currency_separator: %> 31 | <% if i < 3 && tickers[i+1].present? %> 32 |
33 | <% end %> 34 | <% end %> 35 | <% end %> 36 |
37 |
38 |
39 | <% elsif tickers.size >= 5 %> 40 |
41 |
42 |
43 | <% [0, 1, 2].each do |i| %> 44 | <% if tickers[i].present? %> 45 | <%= render 'lib/stock_price/views/shared/ticker_xxsmall', ticker: tickers[i], currency_symbol:, currency_separator: %> 46 | <% if i < 2 %> 47 |
48 | <% end %> 49 | <% end %> 50 | <% end %> 51 |
52 |
53 | <% [3, 4, 5].each do |i| %> 54 | <% if tickers[i].present? %> 55 | <%= render 'lib/stock_price/views/shared/ticker_xxsmall', ticker: tickers[i], currency_symbol:, currency_separator: %> 56 | <% if i < 5 && tickers[i+1].present? %> 57 |
58 | <% end %> 59 | <% end %> 60 | <% end %> 61 |
62 |
63 |
64 | <% end %> 65 | 66 |
67 | 68 | Stock Prices 69 |
70 |
71 | -------------------------------------------------------------------------------- /lib/github_commit_graph/views/full.html.erb: -------------------------------------------------------------------------------- 1 | <% unless framework_v2 %> 2 | <%= render 'plugins/github_commit_graph/v1/full', local_assigns %> 3 | <% else %> 4 | <% unless (contributions[:errors].present?) %> 5 |
6 |
7 |
8 |
9 |
10 |
11 | <%= contributions[:total] %> 12 | Contributions in last year 13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | <%= stats[:longest_streak] %> 21 | Longest streak 22 |
23 |
24 |
25 |
26 |
27 | <%= stats[:current_streak] %> 28 | Current streak 29 |
30 |
31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 |
39 | <%= stats[:max_contributions] %> 40 | Most in a day 41 |
42 |
43 |
44 |
45 |
46 | <%= stats[:average_contributions] %> 47 | Average per day 48 |
49 |
50 |
51 |
52 |
53 | 54 |
55 | 56 |
57 | <% contributions[:commits].each do |week_of_commits| %> 58 | <% week_of_commits[:contributionDays].each do |day| %> 59 | 60 | <% end %> 61 | <% end %> 62 |
63 |
64 | 65 |
66 | 67 | GitHub 68 | <%= instance_name %> 69 |
70 |
71 | <%= render 'plugins/github_commit_graph/common' %> 72 | <% else %> 73 | <%= render partial: 'plugins/errors/full', locals: { 74 | icon: 'github--render.svg', 75 | title: 'GitHub', 76 | instance: instance_name, 77 | error_message: contributions[:errors] 78 | } 79 | %> 80 | <% end %> 81 | <% end %> -------------------------------------------------------------------------------- /lib/github_commit_graph/github_commit_graph.rb: -------------------------------------------------------------------------------- 1 | module Plugins 2 | class GithubCommitGraph < Base 3 | def locals 4 | { username:, contributions:, stats: } 5 | end 6 | 7 | private 8 | 9 | def username = settings['username'] 10 | 11 | def contributions 12 | @contributions ||= begin 13 | query = "query($userName:String!) { 14 | user(login: $userName){ 15 | contributionsCollection { 16 | contributionCalendar { 17 | totalContributions 18 | weeks { 19 | contributionDays { 20 | contributionCount 21 | date 22 | } 23 | } 24 | } 25 | } 26 | } 27 | }" 28 | body = { query: query, variables: { userName: username } } 29 | resp = post('https://api.github.com/graphql', body: body.to_json, headers: headers) 30 | data = resp.dig('data', 'user', 'contributionsCollection', 'contributionCalendar') 31 | 32 | if data.nil? 33 | { errors: "Cannot fetch data for #{username}" } 34 | else 35 | { 36 | total: data['totalContributions'], 37 | commits: data['weeks'].map(&:deep_symbolize_keys) 38 | } 39 | end 40 | end 41 | end 42 | 43 | def headers 44 | { 'authorization' => "Bearer #{Rails.application.credentials.plugins.github_commit_graph_token}" } 45 | end 46 | 47 | # re "Streak" calculations, these may be wrong for some users 48 | # either A), the 'time-zone' header does not work (https://github.com/orgs/community/discussions/20898) 49 | # or B), the time zone is only inferred by the token's owner, us! (https://docs.github.com/en/rest/metrics/statistics?apiVersion=2022-11-28#get-the-hourly-commit-count-for-each-day) 50 | def stats 51 | return {} if contributions[:commits].nil? 52 | 53 | days = contributions[:commits].flat_map { |week| week[:contributionDays] } 54 | sorted_days = days.sort_by { |day| Date.parse(day[:date]) } 55 | 56 | { 57 | longest_streak: longest_streak(sorted_days), 58 | current_streak: current_streak(sorted_days), 59 | max_contributions: days.map { |day| day[:contributionCount] }.max, 60 | average_contributions: average_contributions(days) 61 | } 62 | end 63 | 64 | def average_contributions(days) 65 | total_contributions = days.sum { |day| day[:contributionCount] } 66 | (total_contributions.to_f / days.size).round(2) 67 | end 68 | 69 | def longest_streak(days) 70 | longest = current = 0 71 | days.each do |day| 72 | if day[:contributionCount].positive? 73 | current += 1 74 | longest = [longest, current].max 75 | else 76 | current = 0 77 | end 78 | end 79 | longest 80 | end 81 | 82 | def current_streak(days) 83 | streak = 0 84 | streak += 1 if days.last[:contributionCount].positive? 85 | 86 | days[0..-2].reverse_each do |day| 87 | break if day[:contributionCount].zero? 88 | 89 | streak += 1 90 | end 91 | streak 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/eight_sleep/eight_sleep.rb: -------------------------------------------------------------------------------- 1 | module Plugins 2 | class EightSleep < Base 3 | 4 | AUTH_URL = 'https://auth-api.8slp.net/v1/tokens'.freeze 5 | BASE_URL = 'https://app-api.8slp.net/v1'.freeze 6 | 7 | def locals 8 | { metrics: } 9 | end 10 | 11 | private 12 | 13 | def metrics 14 | create_access_token 15 | 16 | resp = fetch(metrics_url, headers:) 17 | JSON.parse(resp.body)['periods'][0] 18 | end 19 | 20 | def metrics_url 21 | fields = 'sfs,sqs,srs,sleep,light,rem,rem_percent,deep,deep_percent,hr,hrv,br,bedtime,wakeup,ttfa,ttgu,snore,snore_heavy,snore_percent,snore_heavy_percent' 22 | "#{BASE_URL}/users/#{user_id}/metrics/aggregate?to=#{today}&tz=#{tz}&metrics=#{fields}&periods=week" 23 | end 24 | 25 | # spoofs mobile app login 26 | def create_access_token 27 | resp = post(AUTH_URL, body: auth_body, headers: auth_headers) 28 | update_credentials(resp) 29 | end 30 | 31 | def update_credentials(resp, refreshing: false) 32 | data = JSON.parse(resp.body) # => { 'access_token', 'refresh_token', 'expires_in', 'userId', etc } 33 | 34 | # refreshing tokens does not respond with userId 35 | data = data.merge({ 'userId' => user_id }) if refreshing 36 | 37 | credentials = plugin_setting.encrypted_settings 38 | credentials['eight_sleep'] = data 39 | plugin_setting.update(encrypted_settings: credentials) 40 | end 41 | 42 | # anything cool we can do with this endpoint data? 43 | def me 44 | fetch('https://client-api.8slp.net/v1/users/me', headers:) 45 | end 46 | 47 | # https://github.com/lukas-clarke/eight_sleep/blob/101bbf88cdce8aeb7b449a29a1761cfd6004b65a/custom_components/eight_sleep/pyEight/constants.py#L30 48 | def auth_body 49 | { 50 | client_id: '0894c7f33bb94800a03f1f4df13a4f38', 51 | client_secret: 'f0954a3ed5763ba3d06834c73731a32f15f168f47d4f164751275def86db0c76', 52 | grant_type: 'password', 53 | username:, 54 | password: 55 | }.to_json 56 | end 57 | 58 | def auth_headers 59 | { 60 | 'content-type': "application/json", 61 | 'user-agent': "Android App", 62 | 'accept-encoding': "gzip", 63 | accept: "application/json" 64 | } 65 | end 66 | 67 | def headers 68 | { 69 | 'content-type': 'application/json', 70 | connection: 'keep-alive', 71 | 'user-agent': 'Home Assistant/2024.10.3-14058 (Android 14; SM-S911U1)', # based on: https://github.com/home-assistant/frontend/issues/19374#issuecomment-2506470285 72 | 'accept-encoding': 'gzip', 73 | accept: 'application/json', 74 | host: 'app-api.8slp.net', 75 | authorization: "Bearer #{access_token}" 76 | } 77 | end 78 | 79 | def access_token = plugin_setting.encrypted_settings.dig('eight_sleep', 'access_token') 80 | 81 | def user_id = plugin_setting.encrypted_settings.dig('eight_sleep', 'userId') 82 | 83 | def password = settings['password'] 84 | 85 | def username = settings['email'] 86 | 87 | def tz = ActiveSupport::TimeZone::MAPPING[user.tz] || 'America/New_York' 88 | 89 | def today = user.datetime_now.to_date 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/mondrian/views/_common.html.erb: -------------------------------------------------------------------------------- 1 | 48 | 49 | 107 | -------------------------------------------------------------------------------- /lib/mondrian/views/mondrian/_common.html.erb: -------------------------------------------------------------------------------- 1 | 48 | 49 | 107 | -------------------------------------------------------------------------------- /lib/youtube_analytics/views/quadrant.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= number_with_delimiter(metrics[:views]) %> 8 | Views 9 |
10 |
11 |
12 |
13 |
14 | 15 | <% total_seconds = metrics[:minutes_watched].to_i * 60 %> 16 | <% days = total_seconds / 86400 %> 17 | <% hours = (total_seconds % 86400) / 3600 %> 18 | <% minutes = (total_seconds % 3600) / 60 %> 19 | <% seconds = total_seconds % 60 %> 20 | 21 | <% if days > 0 %> 22 | <%= days %>+ 23 | <% time_unit = 'Days' %> 24 | <% elsif hours > 0 %> 25 | <%= hours %>+ 26 | <% time_unit = 'Hours' %> 27 | <% elsif minutes > 0 %> 28 | <%= minutes %>+ 29 | <% time_unit = 'Minutes' %> 30 | <% else %> 31 | <%= pluralize(seconds, 'second') %> 32 | <% time_unit = 'Seconds' %> 33 | <% end %> 34 | 35 | <%= time_unit %> Watched 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | <%= (metrics[:average_view_percentage])&.round %>% 45 | View Through Rate 46 |
47 |
48 |
49 |
50 |
51 | 52 | <% avg_view_duration = metrics[:average_view_duration].to_i %> 53 | <% minutes = avg_view_duration / 60 %> 54 | <% seconds = avg_view_duration % 60 %> 55 | <%= "#{minutes}m #{seconds}s" %> 56 | 57 | Average View Duration 58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | <%= number_with_delimiter(metrics[:subscribers_gained]) %> 67 | Subscribers Gained 68 |
69 |
70 |
71 |
72 |
73 | <%= number_with_delimiter(metrics[:likes]) %> 74 | Likes 75 |
76 |
77 |
78 |
79 | 80 |
81 | 82 | YouTube 83 | <%= instance_name %> 84 |
85 |
-------------------------------------------------------------------------------- /lib/calendar/views/_full_week.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "plugins/calendars/common" %> 2 | 3 | <%# week-only styling, will break month view so not inside common %> 4 | 45 | 46 |
47 | 48 | 49 | 50 | 85 | -------------------------------------------------------------------------------- /lib/usa_college_football_rankings.md: -------------------------------------------------------------------------------- 1 | # College Football Top 10 Rankings Plugin on TRMNL 2 | 3 | ![usa-college-football-rankings-plugin](https://github.com/user-attachments/assets/5e2fc458-6fea-4c78-b8d2-3731db22b64b) 4 | 5 | ## Step 1: Create a New Private Plugin 6 | Log in to your TRMNL dashboard. 7 | On the left-hand menu, click on the 'Go to Plugins' button. 8 | Find the 'Private Plugin' Plugin to create a Private Plugin. 9 | Click 'Add new' to create a new Private Plugin. 10 | 11 | ## Step 2: Set up the Polling Strategy 12 | Name your plugin (e.g., "College Football Top 10") then scroll down to the Strategy section. 13 | Choose the Polling strategy from the Strategy dropdown menu. 14 | In the Polling URL field, enter this URL: 15 | 16 | ``` 17 | https://site.api.espn.com/apis/site/v2/sports/football/college-football/rankings 18 | ``` 19 | Click Save. Once it is saved, the 'Edit Markup' button is not available. 20 | 21 | ## Step 3: Add the HTML Markup 22 | Click the 'Edit Markup' button. 23 | 24 | Copy and paste the following code into the Markup box. This code will display the Top 10 college football teams along with their rank, last week’s rank, and current record. 25 | 26 | ``` 27 | 32 |
33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {% assign counter = 1 %} 47 | {% for rank in rankings[0].ranks %} 48 | {% if counter <= 10 %} 49 | 50 | 51 | 57 | 58 | 59 | 60 | {% assign counter = counter | plus: 1 %} 61 | {% endif %} 62 | {% endfor %} 63 | 64 |
RankTeamLastRecord
{{ counter }} 52 |
53 | 54 | {{ rank.team.location }} {{ rank.team.name }} 55 |
56 |
{{ rank.previous }}{{ rank.recordSummary }}
65 |
66 |
67 |
68 |
69 | 70 | College Football 71 | Top 10 Rankings 72 |
73 |
74 | ``` 75 | 76 | ## Step 4: Save and Activate the Plugin 77 | Once you have entered the markup, click Save to store the plugin. 78 | Navigate to the Playlists tab in your TRMNL dashboard. 79 | Drag and drop your new College Football Top 10 plugin to the top of your playlist if not automatically added. 80 | 81 | ## Step 5: View the College Football Rankings on Your Device 82 | Once refreshed, your TRMNL device will display the Top 10 College Football Rankings, showing the team’s current rank, last week’s rank, and win-loss record. 83 | 84 | ### Customizations (Optional) 85 | Font Size: You can adjust the font size by changing the font-size values in the markup. 86 | More Teams: If you want to display more than 10 teams, you can increase the limit in the code by replacing 10 with the number of teams you want in this line: 87 | 88 | ``` 89 | {% if counter <= 10 %} 90 | ``` 91 | -------------------------------------------------------------------------------- /lib/lunar_calendar/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= current_phase[:name] %> 8 | <%= t('renders.lunar_calendar.current_phase') %> 9 |
10 |
11 | 12 |
13 |
14 |
15 | <%= illumination %>% 16 | <%= t('renders.lunar_calendar.illumination') %> 17 |
18 |
19 | 20 |
21 |
22 |
23 | <%= age %> 24 | <%= t('renders.lunar_calendar.age') %> 25 |
26 |
27 |
28 | 29 |
30 | 31 | <% ordered_phase_sequence = phase_sequence.sort_by { |seq| seq[:date_full] } %> 32 | 33 |
34 |
35 | <% ordered_phase_sequence.each do |phase| %> 36 |
37 | <%= phase[:date] %> 38 |
39 | <% end %> 40 |
41 |
42 | <% ordered_phase_sequence.each do |phase| %> 43 |
44 | 45 |
46 | <% end %> 47 |
48 |
49 | <% ordered_phase_sequence.each do |phase| %> 50 |
51 | 52 | <%= phase[:name] %> 53 | 54 |
55 | <% end %> 56 |
57 |
58 | 59 |
60 | 61 |
62 |
63 |
64 |
65 | <%= next_phase[:name] %> 66 | <%= t('renders.lunar_calendar.next_phase') %> (<%= next_phase[:date] %>) 67 |
68 |
69 | 70 |
71 |
72 |
73 | <%= next_full_moon %> 74 | <%= t('renders.lunar_calendar.next_full_moon') %> 75 |
76 |
77 | 78 |
79 |
80 |
81 | <%= next_new_moon %> 82 | <%= t('renders.lunar_calendar.next_new_moon') %> 83 |
84 |
85 |
86 |
87 | 88 |
89 | 90 | <%= t('renders.lunar_calendar.title') %> 91 | <%= instance_name %> 92 |
93 |
94 | 95 | 106 | -------------------------------------------------------------------------------- /lib/parcel/parcel.rb: -------------------------------------------------------------------------------- 1 | module Plugins 2 | class Parcel < Base 3 | 4 | # maps Parcel status codes to I18n keys "renders.parcel.status.{status_key}" 5 | # (fall back to 'renders.parcel.status.unknown') 6 | STATUS_KEYS = { 7 | 0 => 'delivered', 8 | 1 => 'frozen', 9 | 2 => 'in_transit', 10 | 3 => 'expecting_pickup', 11 | 4 => 'out_for_delivery', 12 | 5 => 'not_found', 13 | 6 => 'failed_delivery_attempt', 14 | 7 => 'delivery_exception', 15 | 8 => 'information_received' 16 | }.freeze 17 | 18 | def locals 19 | result = fetch_deliveries 20 | skip_display = skip_if_empty? && result[:error].nil? && result[:deliveries].empty? 21 | 22 | { 23 | deliveries: result[:deliveries].map { delivery_item(it) }, 24 | error: result[:error], 25 | filter_mode: filter_mode.humanize, 26 | skip_display:, 27 | style: 28 | } 29 | end 30 | 31 | private 32 | 33 | def delivery_item(delivery) 34 | { 35 | title: delivery['description'], 36 | status_key: status_key(delivery['status_code']), 37 | latest: latest_event(delivery['events']), 38 | delivery_by: delivery_by(delivery), 39 | days: delivery_days(delivery) 40 | } 41 | end 42 | 43 | def style = settings['style'] || 'detailed' 44 | 45 | def api_key = settings['api_key'] 46 | 47 | def filter_mode = settings['filter_mode'] 48 | 49 | def skip_if_empty? = settings['empty_state'] == 'skip' 50 | 51 | def headers = { 'api-key' => api_key, 'user-agent' => 'TRMNL Server' } 52 | 53 | def query = { 'filter_mode' => filter_mode } 54 | 55 | def url = 'https://api.parcel.app/external/deliveries/' 56 | 57 | def cache_key = "parcel_#{api_key}" 58 | 59 | def fetch_deliveries 60 | # API docs: https://parcelapp.net/help/api.html 61 | # "The rate limit is 20 requests per hour." 62 | Rails.cache.fetch(cache_key, expires_in: 5.minutes) do 63 | response = HTTParty.get(url, headers:, query:) 64 | 65 | if response.success? 66 | data = JSON.parse(response.body) # content-type is wrong (text/html), so we need to explicitly parse the body 67 | if data['success'] 68 | { deliveries: data['deliveries'], error: nil } 69 | elsif data['error_message'].present? 70 | { deliveries: [], error: data['error_message'] } 71 | else 72 | { deliveries: [], error: t('renders.parcel.errors.api') } 73 | end 74 | else 75 | { deliveries: [], error: t('renders.parcel.errors.http', code: response.code) } 76 | end 77 | end 78 | rescue StandardError 79 | { deliveries: [], error: t('renders.parcel.errors.internal') } 80 | end 81 | 82 | def status_key(status_code) 83 | STATUS_KEYS.fetch(status_code, 'unknown') 84 | end 85 | 86 | def user_today 87 | user.datetime_now.to_date 88 | end 89 | 90 | def delivery_days(delivery) 91 | return unless delivery['date_expected'].present? 92 | 93 | date_expected = DateTime.parse(delivery['date_expected']).to_date 94 | 95 | days = (date_expected - user_today).to_i 96 | days >= 0 ? days : nil 97 | rescue Date::Error 98 | nil 99 | end 100 | 101 | def latest_event(events) 102 | return nil if events.blank? 103 | 104 | events.first['event'] 105 | end 106 | 107 | def delivery_by(delivery) 108 | delivery_days = delivery_days(delivery) 109 | return unless delivery_days 110 | 111 | date_expected = DateTime.parse(delivery['date_expected']).to_date 112 | if delivery_days >= 7 113 | l(date_expected, format: :short) # far in the future 114 | elsif delivery_days == 1 115 | t('renders.parcel.tomorrow') # tomorrow 116 | elsif delivery_days.zero? 117 | t('renders.parcel.today') # today 118 | else 119 | l(date_expected, format: '%A') # day of week 120 | end 121 | rescue Date::Error 122 | nil 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /lib/youtube_analytics/views/full.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= number_with_delimiter(metrics[:views]) %> 9 | Views 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 18 | <% total_seconds = metrics[:minutes_watched].to_i * 60 %> 19 | <% days = total_seconds / 86400 %> 20 | <% hours = (total_seconds % 86400) / 3600 %> 21 | <% minutes = (total_seconds % 3600) / 60 %> 22 | <% seconds = total_seconds % 60 %> 23 | 24 | <% if days > 0 %> 25 | <%= days %>+ 26 | <% time_unit = 'Days' %> 27 | <% elsif hours > 0 %> 28 | <%= hours %>+ 29 | <% time_unit = 'Hours' %> 30 | <% elsif minutes > 0 %> 31 | <%= minutes %>+ 32 | <% time_unit = 'Minutes' %> 33 | <% else %> 34 | <%= seconds %> 35 | <% time_unit = 'Seconds' %> 36 | <% end %> 37 | 38 | <%= time_unit %> Watched 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 | <% avg_view_duration = metrics[:average_view_duration].to_i %> 50 | <% minutes = avg_view_duration / 60 %> 51 | <% seconds = avg_view_duration % 60 %> 52 | <%= "#{minutes}m #{seconds}s" %> 53 | 54 | Average View Duration 55 |
56 |
57 |
58 |
59 |
60 | <%= number_with_delimiter(metrics[:subscribers_gained]) %> 61 | Subscribers Gained 62 |
63 |
64 |
65 |
66 |
67 | <%= (metrics[:average_view_percentage])&.round %>% 68 | View Through Rate 69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | <%= number_with_delimiter(metrics[:likes]) %> 78 | Likes 79 |
80 |
81 |
82 |
83 |
84 | <%= number_with_delimiter(metrics[:comments]) %> 85 | Comments 86 |
87 |
88 |
89 |
90 |
91 | <%= number_with_delimiter(metrics[:shares]) %> 92 | Shares 93 |
94 |
95 |
96 |
97 | 98 |
99 | 100 | YouTube 101 | <%= instance_name %> 102 |
103 |
-------------------------------------------------------------------------------- /lib/calendar/views/_common.html.erb: -------------------------------------------------------------------------------- 1 | 157 | -------------------------------------------------------------------------------- /lib/youtube_analytics/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | <%= number_with_delimiter(metrics[:views]) %> 7 | Views 8 |
9 |
10 |
11 |
12 |
13 |
14 | 15 | <% total_seconds = metrics[:minutes_watched].to_i * 60 %> 16 | <% days = total_seconds / 86400 %> 17 | <% hours = (total_seconds % 86400) / 3600 %> 18 | <% minutes = (total_seconds % 3600) / 60 %> 19 | <% seconds = total_seconds % 60 %> 20 | 21 | <% if days > 0 %> 22 | <%= days %>+ 23 | <% time_unit = 'Days' %> 24 | <% elsif hours > 0 %> 25 | <%= hours %>+ 26 | <% time_unit = 'Hours' %> 27 | <% elsif minutes > 0 %> 28 | <%= minutes %>+ 29 | <% time_unit = 'Minutes' %> 30 | <% else %> 31 | <%= pluralize(seconds, 'second') %> 32 | <% time_unit = 'Seconds' %> 33 | <% end %> 34 | 35 | <%= time_unit %> Watched 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | <%= metrics[:average_view_percentage]&.round %>% 44 | View Through Rate 45 |
46 |
47 |
48 |
49 |
50 | 51 | <% avg_view_duration = metrics[:average_view_duration].to_i %> 52 | <% minutes = avg_view_duration / 60 %> 53 | <% seconds = avg_view_duration % 60 %> 54 | <%= "#{minutes}m #{seconds}s" %> 55 | 56 | Average View Duration 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | <%= number_with_delimiter(metrics[:subscribers_gained]) %> 66 | Subscribers Gained 67 |
68 |
69 |
70 |
71 |
72 | <%= number_with_delimiter(metrics[:likes]) %> 73 | Likes 74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | <%= number_with_delimiter(metrics[:comments]) %> 83 | Comments 84 |
85 |
86 |
87 |
88 |
89 | <%= number_with_delimiter(metrics[:shares]) %> 90 | Shares 91 |
92 |
93 |
94 |
95 | 96 |
97 | 98 | YouTube 99 | <%= instance_name %> 100 |
101 |
-------------------------------------------------------------------------------- /lib/youtube_analytics/youtube_analytics.rb: -------------------------------------------------------------------------------- 1 | module Plugins 2 | class YoutubeAnalytics < Base 3 | def locals 4 | { metrics: } 5 | end 6 | 7 | class << self 8 | def redirect_url 9 | client = Signet::OAuth2::Client.new(client_options) 10 | client.authorization_uri.to_s 11 | end 12 | 13 | def fetch_access_token(code) 14 | client = Signet::OAuth2::Client.new(client_options) 15 | client.code = code 16 | client.fetch_access_token! 17 | end 18 | 19 | def client_options 20 | { 21 | client_id: Rails.application.credentials.plugins[:google][:client_id], 22 | client_secret: Rails.application.credentials.plugins[:google][:client_secret], 23 | authorization_uri: 'https://accounts.google.com/o/oauth2/auth', # may require change to: '/oauth2/v2/auth' 24 | token_credential_uri: 'https://accounts.google.com/o/oauth2/token', # may require change to: '/oauth2/v2/token' 25 | access_type: 'offline', 26 | scope: [Google::Apis::YoutubeAnalyticsV2::AUTH_YT_ANALYTICS_READONLY], 27 | redirect_uri: "#{Rails.application.credentials.base_url}/plugin_settings/youtube_analytics/redirect", 28 | additional_parameters: { 29 | prompt: 'consent select_account' 30 | } 31 | } 32 | end 33 | end 34 | 35 | private 36 | 37 | def metrics = fetch_analytics 38 | 39 | def fetch_analytics 40 | retry_count = 0 41 | begin 42 | service = Google::Apis::YoutubeAnalyticsV2::YouTubeAnalyticsService.new 43 | service.authorization = client 44 | 45 | report = service.query_report( 46 | ids: "channel==MINE", 47 | start_date: start_date, 48 | end_date: end_date, 49 | metrics: "views,estimatedMinutesWatched,averageViewDuration,averageViewPercentage,subscribersGained,likes,dislikes,comments,shares", 50 | dimensions: "day", 51 | sort: "day" 52 | ) 53 | 54 | process_report(report) 55 | rescue Google::Apis::AuthorizationError 56 | refresh! 57 | retry_count += 1 58 | if retry_count <= 1 # Retry just once, otherwise retry next day. 59 | sleep 2 60 | retry 61 | else 62 | plugin_settings.refresh_in_24hr 63 | {} 64 | end 65 | rescue Google::Apis::ClientError => e 66 | handle_erroring_state(e.message) 67 | end 68 | end 69 | 70 | def process_report(report) 71 | kpis = { 72 | views: 0, 73 | minutes_watched: 0, 74 | average_view_duration: 0, 75 | average_view_percentage: 0, 76 | subscribers_gained: 0, 77 | likes: 0, 78 | dislikes: 0, 79 | comments: 0, 80 | shares: 0 81 | } 82 | 83 | report.rows.each do |metrics| 84 | kpis[:views] += metrics[1].round(2) 85 | kpis[:minutes_watched] += metrics[2].round(2) 86 | kpis[:average_view_duration] += metrics[3].round(2) 87 | kpis[:average_view_percentage] += metrics[4].round(2) 88 | kpis[:subscribers_gained] += metrics[5].round(2) 89 | kpis[:likes] += metrics[6].round(2) 90 | kpis[:dislikes] += metrics[7].round(2) 91 | kpis[:comments] += metrics[8].round(2) 92 | kpis[:shares] += metrics[9].round(2) 93 | end 94 | 95 | kpis[:average_view_duration] = (kpis[:average_view_duration] / settings['duration'].to_i).round(2) 96 | kpis[:average_view_percentage] = (kpis[:average_view_percentage] / settings['duration'].to_i).to_i 97 | kpis 98 | end 99 | 100 | def start_date = (Date.today - settings['duration'].to_i.days).to_s 101 | 102 | def end_date = Date.today.to_s 103 | 104 | def client 105 | credentials = settings['youtube_analytics'] 106 | oauth_client = Signet::OAuth2::Client.new(Plugins::YoutubeAnalytics.client_options) 107 | oauth_client.update!(credentials) 108 | oauth_client 109 | end 110 | 111 | def refresh! 112 | response = client.refresh! 113 | credentials = plugin_settings.encrypted_settings 114 | credentials['youtube_analytics']['access_token'] = response['access_token'] 115 | plugin_settings.update(encrypted_settings: credentials) 116 | self.settings = plugin_settings.settings.merge(plugin_settings.encrypted_settings) 117 | rescue Signet::AuthorizationError 118 | handle_erroring_state('Signet::AuthorizationError') 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/stock_price/stock_price.rb: -------------------------------------------------------------------------------- 1 | module Plugins 2 | class StockPrice < Base 3 | TICKER_NAME_DATA = JSON.parse(File.read("db/data/ticker-name.json")) 4 | STOCK_TICKER_LIMIT = 12.freeze 5 | 6 | SUPPORTED_INDEX = [ 7 | 'VIX', 'MXACW', 'XWLD', 'MXUSA', 'MXEF', 'MXEA', 'DJI', 'RUI', 'RUT', 'OEX', 'OEX', 'SPX', 'XSP', 'XSP', 'SPESG', 'XND', 'IXIC' 8 | ].freeze 9 | 10 | SUPPORTED_METAL = ['XAG', 'XAU', 'XPD', 'HG'].freeze 11 | 12 | def locals = { tickers:, currency_symbol:, currency_separator: } 13 | 14 | private 15 | 16 | def tickers 17 | ticker_data = string_to_array(settings["symbol"], limit: STOCK_TICKER_LIMIT).map do |symbol| 18 | ticker_price(symbol.upcase) 19 | end.compact 20 | 21 | ticker_data.each do |stock| 22 | stock[:price] = localized_price(stock[:price]) 23 | end 24 | end 25 | 26 | def invalid_symbol(symbol) 27 | { 28 | symbol: symbol, 29 | name: 'SYMBOL_NOT_SUPPORTED', 30 | price: 0, 31 | change: '' 32 | } 33 | end 34 | 35 | def ticker_name(symbol) = TICKER_NAME_DATA[symbol] 36 | 37 | def ticker_price(symbol) 38 | Rails.cache.fetch "STOCK_PRICE_#{symbol}_extended_#{extended_hours?}_v2", expire_in: 15.minutes, skip_nil: true do 39 | if SUPPORTED_INDEX.include?(symbol) 40 | response = fetch(index_url(symbol), headers:) 41 | format_data(response, symbol) 42 | elsif SUPPORTED_METAL.include?(symbol.upcase.gsub!('USD', '')) 43 | response = fetch(metal_url(symbol), headers:) 44 | format_metal_data(response, symbol) 45 | else 46 | response = fetch(stock_url(symbol), headers:) 47 | format_data(response, symbol) 48 | end 49 | end 50 | end 51 | 52 | def format_metal_data(response, symbol) 53 | { 54 | symbol: symbol, 55 | name: response['name'], 56 | price: response['price'], 57 | change: "N/A" 58 | } 59 | end 60 | 61 | def format_data(response, symbol) 62 | if %w[error no_data].include?(response['s']) 63 | invalid_symbol(symbol) 64 | else 65 | { 66 | symbol: symbol.upcase, 67 | name: ticker_name(symbol.upcase), 68 | price: response.dig("last", 0), 69 | change: "#{(response.dig('changepct', 0).to_f * 100)&.round(2)}%" 70 | } 71 | end 72 | end 73 | 74 | def localized_price(base_price) 75 | (base_price * currency_conversions[currency])&.round(2) 76 | end 77 | 78 | def currency = settings['currency'].upcase 79 | 80 | def currency_symbol 81 | { 82 | 'USD' => '$', 83 | 'EUR' => '€', 84 | 'CAD' => '$', 85 | 'CHF' => '₣', 86 | 'GBP' => '£', 87 | 'KRW' => '₩', 88 | 'CNY' => '¥', 89 | 'JPY' => '¥', 90 | 'INR' => '₹', 91 | 'ZAR' => 'R', 92 | 'CLP' => '$' 93 | }[currency] 94 | end 95 | 96 | def currency_separator 97 | case currency 98 | when 'EUR' 99 | ',' 100 | else 101 | '.' 102 | end 103 | end 104 | 105 | def currency_conversions 106 | Rails.cache.fetch "STOCK_PRICE_CURRENCIES_#{supported_currencies.count}_VALUES", expire_in: 1.day, skip_nil: true do 107 | resp = HTTParty.get(currency_conversion_url) 108 | JSON.parse(resp.body)['data'].map { |k, v| { k => v['value'] } }.reduce({}, :merge) 109 | rescue StandardError 110 | nil 111 | end 112 | end 113 | 114 | def supported_currencies 115 | plugin.account_fields.find { |af| af['keyname'] == 'currency' }&.dig('options') 116 | end 117 | 118 | def extended_hours? 119 | settings['extended_hours'] == 'yes' 120 | end 121 | 122 | def stock_url(symbol) = "https://api.marketdata.app/v1/stocks/quotes/#{CGI.escape(symbol)}/?extended=#{extended_hours?}" 123 | 124 | def index_url(symbol) = "https://api.marketdata.app/v1/indices/quotes/#{CGI.escape(symbol)}/?extended=#{extended_hours?}" 125 | 126 | def metal_url(symbol) = "https://api.gold-api.com/price/#{symbol.gsub!('USD', '')}" 127 | 128 | def currency_conversion_url = "https://api.currencyapi.com/v3/latest?apikey=#{Rails.application.credentials.plugins.currency_api}¤cies=#{supported_currencies.join('%2C')}" 129 | 130 | def headers 131 | { 132 | Accept: "application/json", 133 | Authorization: "Token #{Rails.application.credentials.plugins[:marketdata_app]}" 134 | } 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/youtube_analytics/views/half_horizontal.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= number_with_delimiter(metrics[:views]) %> 9 | Views 10 |
11 |
12 |
13 |
14 |
15 |
16 | 17 | <% total_seconds = metrics[:minutes_watched].to_i * 60 %> 18 | <% days = total_seconds / 86400 %> 19 | <% hours = (total_seconds % 86400) / 3600 %> 20 | <% minutes = (total_seconds % 3600) / 60 %> 21 | <% seconds = total_seconds % 60 %> 22 | 23 | <% if days > 0 %> 24 | <%= days %>+ 25 | <% time_unit = 'Days Watched' %> 26 | <% elsif hours > 0 %> 27 | <%= hours %>+ 28 | <% time_unit = 'Hours Watched' %> 29 | <% elsif minutes > 0 %> 30 | <%= minutes %>+ 31 | <% time_unit = 'Minutes Watched' %> 32 | <% else %> 33 | <%= pluralize(seconds, 'second') %> 34 | <% time_unit = 'Seconds Watched' %> 35 | <% end %> 36 | 37 | <%= time_unit %> 38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | <%= (metrics[:average_view_percentage])&.round %>% 46 | View Through Rate 47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 | <% avg_view_duration = metrics[:average_view_duration].to_i %> 55 | <% minutes = avg_view_duration / 60 %> 56 | <% seconds = avg_view_duration % 60 %> 57 | <%= "#{minutes}m #{seconds}s" %> 58 | 59 | Average View Duration 60 |
61 |
62 |
63 |
64 |
65 |
66 | <%= number_with_delimiter(metrics[:subscribers_gained]) %> 67 | Subscribers Gained 68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | <%= number_with_delimiter(metrics[:likes]) %> 76 | Likes 77 |
78 |
79 |
80 |
81 |
82 |
83 | <%= number_with_delimiter(metrics[:comments]) %> 84 | Comments 85 |
86 |
87 |
88 |
89 |
90 |
91 | <%= number_with_delimiter(metrics[:shares]) %> 92 | Shares 93 |
94 |
95 |
96 |
97 |
98 | 99 |
100 | 101 | YouTube 102 | <%= instance_name %> 103 |
104 |
-------------------------------------------------------------------------------- /lib/lunar_calendar/views/half_vertical.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= current_phase[:name] %> 8 | <%= t('renders.lunar_calendar.current_phase') %> 9 |
10 |
11 |
12 |
13 |
14 | <%= next_phase[:name] %> 15 | <%= t('renders.lunar_calendar.next_phase_short') %> (<%= next_phase[:date] %>) 16 |
17 |
18 |
19 | 20 |
21 | 22 | <% ordered_phase_sequence = phase_sequence.sort_by { |seq| seq[:date_full] } %> 23 | <% first_phase = ordered_phase_sequence.first %> 24 | <% current = ordered_phase_sequence.find { |phase| phase[:current] } %> 25 | <% last_phase = ordered_phase_sequence.last %> 26 | 27 |
28 |
29 |
30 | <%= first_phase[:date] %> 31 |
32 |
33 | <%= current[:date] %> 34 |
35 |
36 | <%= last_phase[:date] %> 37 |
38 |
39 | 40 |
41 | <% ordered_phase_sequence.each do |phase| %> 42 |
43 | 44 |
45 | <% end %> 46 |
47 | 48 |
49 |
50 | <%= first_phase[:name] %> 51 |
52 |
53 | <%= current[:name] %> 54 |
55 |
56 | <%= last_phase[:name] %> 57 |
58 |
59 |
60 | 61 |
62 | 63 |
64 |
65 |
66 |
67 | <%= age %> 68 | <%= t('renders.lunar_calendar.age') %> 69 |
70 |
71 |
72 |
73 |
74 | <%= illumination %>% 75 | <%= t('renders.lunar_calendar.illumination') %> 76 |
77 |
78 |
79 | 80 |
81 | 82 |
83 |
84 |
85 |
86 | <%= next_full_moon %> 87 | <%= t('renders.lunar_calendar.next_full_moon') %> 88 |
89 |
90 |
91 |
92 |
93 | <%= next_new_moon %> 94 | <%= t('renders.lunar_calendar.next_new_moon') %> 95 |
96 |
97 |
98 |
99 | 100 |
101 | 102 | <%= t('renders.lunar_calendar.title') %> 103 | <%= instance_name %> 104 |
105 |
106 | 107 | 118 | -------------------------------------------------------------------------------- /lib/calendar/helpers/base.rb: -------------------------------------------------------------------------------- 1 | module Calendar 2 | module Helper 3 | # rubocop:disable Lint/DuplicateBranch 4 | def cutoff_time 5 | case [event_layout, include_past_event?] 6 | when ['default', true], ['today_only', true], ['week', false], ['month', false], ['rolling_month', false] 7 | beginning_of_day 8 | when ['default', false], ['today_only', false] # this is useful for busy calendar to remove event that have elapsed. 9 | now_in_tz 10 | when ['week', true], ['month', true], ['rolling_month', true] 11 | time_min 12 | else beginning_of_day 13 | end 14 | end 15 | # rubocop:enable Lint/DuplicateBranch 16 | 17 | def event_layout = settings['event_layout'] 18 | 19 | def include_past_event? = settings['include_past_events'] == 'yes' 20 | 21 | def now_in_tz = user.datetime_now 22 | 23 | # required to ensure locals data has a 'diff' at least 1x per day 24 | # given week/month view 'highlight' current day 25 | # without this local, previous day will be highlighted if events dont change 26 | def today_in_tz = now_in_tz.to_date.to_s 27 | 28 | def beginning_of_day = now_in_tz.beginning_of_day 29 | 30 | def time_min 31 | days_behind = case event_layout 32 | when 'month', 'rolling_month' 33 | 30 # TODO: improve this to not store all 30 days, as right now we endup storing 30+3=60 days events. 34 | else 35 | 7 36 | end 37 | 38 | (beginning_of_day - days_behind.days) 39 | end 40 | 41 | def first_day = Date.strptime(settings['first_day'], '%a').wday 42 | 43 | def fixed_week = settings['fixed_week'] == 'yes' 44 | 45 | def ignore_based_on_status?(event) 46 | if event.instance_of?(Icalendar::Event) 47 | return true if event.status == 'CANCELLED' && event.dtstart.nil? 48 | return true if event.exdate.flatten.filter { |exception_date| event.dtstart&.to_datetime == exception_date.to_datetime }.present? 49 | end 50 | return false if event.status&.downcase == 'confirmed' # always include confirmed events 51 | 52 | # include non-confirmed events if user prefers to see them (received requests for both options) 53 | # if this branch is reached, event.status == [nil, 'rejected'] etc 54 | settings['event_status_filter'] == 'confirmed_only' 55 | end 56 | 57 | def include_description 58 | return true unless settings['include_description'] # backward compatible default value 59 | 60 | settings['include_description'] == 'yes' 61 | end 62 | 63 | def include_event_time 64 | return false unless settings['include_event_time'] # backward compatible default value 65 | 66 | settings['include_event_time'] == 'yes' 67 | end 68 | 69 | def ignored_phrases 70 | return [] unless settings['ignore_phrases'] 71 | 72 | settings['ignore_phrases'].gsub("\n", "").gsub("\r", "").split(',').map(&:squish) 73 | end 74 | 75 | def time_format = settings['time_format'] || 'am/pm' 76 | 77 | def formatted_time 78 | return "%-I:%M %p" if time_format == 'am/pm' 79 | 80 | "%R" 81 | end 82 | 83 | def date_format 84 | return '%A, %B %-d' unless settings['date_format'].present? # => Monday, June 16 85 | 86 | # special i18n key that must be symbolized to be accepted as an arg to l(.. format:) 87 | return :short if settings['date_format'] == 'short' 88 | 89 | settings['date_format'] 90 | end 91 | 92 | # ability to hard-code each day's first time slot in event_layout=week mode 93 | # by default we lookup the earliest event within the period, but some users 94 | # prefer to sacrifice morning visibility to see more throughout the day 95 | def scroll_time 96 | return settings['scroll_time'] if settings['scroll_time'].present? 97 | 98 | events.reject { |e| e[:all_day] }.map { |e| e[:start_full].to_time.strftime("%H:00:00") }.min || '08:00:00' 99 | end 100 | 101 | def scroll_time_end 102 | return settings['scroll_time_end'] if settings['scroll_time_end'].present? 103 | 104 | events.reject { |e| e[:all_day] }.map { |e| e[:end_full].to_time.strftime("%H:00:00") }.max || '24:00:00' # same default: https://fullcalendar.io/docs/slotMaxTime 105 | end 106 | 107 | def zoom_mode 108 | settings['zoom_mode'] == 'yes' 109 | end 110 | 111 | # Override the no_screen_padding? method from Formatter to handle calendar-specific logic 112 | def no_screen_padding? 113 | # Check if event_layout requires no padding 114 | if %w[week month rolling_month].include?(event_layout) 115 | return true 116 | end 117 | 118 | # Fall back to default behavior from Formatter 119 | super 120 | end 121 | end 122 | end 123 | --------------------------------------------------------------------------------