trmnl_preview 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd5804f039fe8932f996c097798d5a4c9211725fad19f71c230ac3d99c9c8875
4
- data.tar.gz: d13908a4314d5a397667e59ed4576ab1cb9a396b47bf2962df12bc80f53e9d14
3
+ metadata.gz: abf6757d447adc5c12e7eed3baa049bc80757bc536f0da60af8ccb624d4216d4
4
+ data.tar.gz: a533ae492f2ea9b71a55130977a88988ec3767c1393b2ebbbf618364a5a0f53b
5
5
  SHA512:
6
- metadata.gz: e51b14f3cc1320a96a948c5c1b44622dc13a53eeb348f7b9f6ca4e928628dbccec6fd4de54598408c7d778f1891d82577ebc3853b884a122432b89d74dd11d56
7
- data.tar.gz: 7962920070178a0701251b5f847b1b35630d6643323b1d87cb8c11c0f91f8478e842d88722f1d04743a28c69f25c78c394538daa9aa3e4777beb8647b3b53f9b
6
+ metadata.gz: a88985299e6c183a0ee61fa609f117dffad48b42e1abdd4c2ea8303051725f44fc411870d9b5999d0ce7b1f78e6923d10974cf79ea79787ed61212eed36c3aa4
7
+ data.tar.gz: bbdd8ac78d326b086eb56c8a66cf2aaf96096e1beb879ee951cda37b6f699cf14fc5d796fac34d4711b20c8d750075f38c1110d2829d0ebe1ed0d5fafa44c9c3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.6
4
+
5
+ - Fixed bug that left blank plugins on server after upload failed
6
+ - Fixed bug creating upload.zip after previous upload had failed
7
+ - Added support to read API key fromk `TRMNL_API_KEY` environment variable (@andi4000)
8
+ - Fixed `init` command in Docker container (@jbarreiros)
9
+ - Automatically remove ephemeral Docker container after exit (@andi4000)
10
+
11
+ ## 0.5.5
12
+
13
+ - Added dark mode (@stephenyeargin)
14
+ - Added override for `polling_url` in project config (@heroheman)
15
+ - Reworked `bin/dev` into more generic `bin/trmnlp`
16
+ - Fixed pull, push, and clone commands on Windows (@eugenio)
17
+
3
18
  ## 0.5.4
4
19
 
5
20
  - Added `shared.liquid` file to template (@mariovisic)
data/README.md CHANGED
@@ -31,11 +31,11 @@ This is the structure of a plugin project:
31
31
  You can start building a plugin locally, then `push` it to the TRMNL server for display on your device.
32
32
 
33
33
  ```sh
34
- trmnlp init my_plugin # generate
35
- cd my_plugin
36
- trmnlp serve # develop locally
37
- trmnlp login # authenticate
38
- trmnlp push # upload
34
+ trmnlp init [my_plugin] # generate
35
+ cd [my_plugin]
36
+ trmnlp serve # develop locally
37
+ trmnlp login # authenticate
38
+ trmnlp push # upload
39
39
  ```
40
40
 
41
41
  ## Modifying an Existing Plugin
@@ -43,16 +43,26 @@ trmnlp push # upload
43
43
  If you have built a plugin with the web-based editor, you can `clone` it, work on it locally, and `push` changes back to the server.
44
44
 
45
45
  ```sh
46
- trmnlp login # authenticate
47
- trmnlp clone my_plugin [id] # first download
48
- cd my_plugin
49
- trmnlp serve # develop locally
50
- trmnlp push # upload
46
+ trmnlp login # authenticate
47
+ trmnlp clone [my_plugin] [id] # first download
48
+ cd [my_plugin]
49
+ trmnlp serve # develop locally
50
+ trmnlp push # upload
51
51
  ```
52
52
 
53
+ ## Authentication
54
+
55
+ The `trmnlp login` command saves your API key to `~/.config/trmnlp/config.yml`.
56
+
57
+ If an environment variable is more convenient (for example in a CI/CD pipeline), you can set `$TRMNL_API_KEY` instead.
58
+
53
59
  ## Running trmnlp
54
60
 
55
- ### Via RubyGems
61
+ The `bin/trmnlp` script is provided as a convenience. It will use the local Ruby gem if available, falling back to the `trmnl/trmnlp` Docker image.
62
+
63
+ You can modify the `bin/trmnlp` script to set up environment variables (plugin secrets, etc.) before running the server.
64
+
65
+ ### Installing via RubyGems
56
66
 
57
67
  Prerequisites:
58
68
 
@@ -66,13 +76,13 @@ gem install trmnl_preview
66
76
  trmnlp serve
67
77
  ```
68
78
 
69
- ### Via Docker (`trmnlp serve` only)
79
+ ### Installing via Docker
70
80
 
71
81
  ```sh
72
82
  docker run \
73
- -p 4567:4567 \
74
- -v /path/to/plugin/on/host:/plugin \
75
- trmnl/trmnlp
83
+ --publish 4567:4567 \
84
+ --volume ".:/plugin" \
85
+ trmnl/trmnlp serve
76
86
  ```
77
87
 
78
88
  ## `.trmnlp.yml` Reference - Project Config
@@ -18,20 +18,24 @@ module TRMNLP
18
18
  temp_file.write(response.body)
19
19
  temp_file.rewind
20
20
 
21
- # return the path to the temp file
22
- Pathname.new(temp_file.path)
21
+ # return the temp file IO
22
+ temp_file
23
23
  else
24
24
  raise Error, "failed to download plugin settings archive: #{response.status} #{response.body}"
25
25
  end
26
26
  end
27
27
 
28
28
  def post_plugin_setting_archive(id, path)
29
+ filepart = Faraday::Multipart::FilePart.new(path, 'application/zip')
30
+
29
31
  payload = {
30
- file: Faraday::Multipart::FilePart.new(path, 'application/zip')
32
+ file: filepart
31
33
  }
32
34
 
33
35
  response = conn.post("plugin_settings/#{id}/archive", payload)
34
36
 
37
+ filepart.close
38
+
35
39
  if response.status == 200
36
40
  JSON.parse(response.body)
37
41
  else
@@ -49,6 +53,16 @@ module TRMNLP
49
53
  end
50
54
  end
51
55
 
56
+ def delete_plugin_setting(id)
57
+ response = conn.delete("plugin_settings/#{id}")
58
+
59
+ if response.status == 204
60
+ true
61
+ else
62
+ raise Error, "failed to delete plugin setting: #{response.status} #{response.body}"
63
+ end
64
+ end
65
+
52
66
  private
53
67
 
54
68
  attr_reader :config
data/lib/trmnlp/cli.rb CHANGED
@@ -14,6 +14,8 @@ module TRMNLP
14
14
 
15
15
  def self.exit_on_failure? = true
16
16
 
17
+ def self.default_bind = File.exist?('/.dockerenv') ? '0.0.0.0' : '127.0.0.1'
18
+
17
19
  desc 'build', 'Generate static HTML files'
18
20
  def build
19
21
  Commands::Build.new(options).call
@@ -52,7 +54,7 @@ module TRMNLP
52
54
  end
53
55
 
54
56
  desc 'serve', 'Start a local dev server'
55
- method_option :bind, type: :string, default: '127.0.0.1', aliases: '-b', desc: 'Bind address'
57
+ method_option :bind, type: :string, default: default_bind, aliases: '-b', desc: 'Bind address'
56
58
  method_option :port, type: :numeric, default: 4567, aliases: '-p', desc: 'Port number'
57
59
  def serve
58
60
  Commands::Serve.new(options).call
@@ -6,7 +6,9 @@ module TRMNLP
6
6
  def call
7
7
  if config.app.logged_in?
8
8
  anonymous_key = config.app.api_key[0..10] + '*' * (config.app.api_key.length - 11)
9
- output "Currently authenticated as: #{anonymous_key}"
9
+ output "Currently authenticated as: #{anonymous_key}"
10
+ confirm = prompt("You are already authenticated. Do you want to re-authenticate? (y/N): ")
11
+ return unless confirm.strip.downcase == 'y'
10
12
  end
11
13
 
12
14
  output "Please visit #{config.app.account_uri} to grab your API key, then paste it here."
@@ -19,11 +19,11 @@ module TRMNLP
19
19
  end
20
20
 
21
21
  api = APIClient.new(config)
22
- temp_path = api.get_plugin_setting_archive(plugin_settings_id)
22
+ tempfile = api.get_plugin_setting_archive(plugin_settings_id)
23
23
  size = 0
24
24
 
25
25
  begin
26
- Zip::File.open(temp_path) do |zip_file|
26
+ Zip::File.open(tempfile.path) do |zip_file|
27
27
  zip_file.each do |entry|
28
28
  dest_path = paths.src_dir.join(entry.name)
29
29
  dest_path.dirname.mkpath
@@ -31,9 +31,9 @@ module TRMNLP
31
31
  end
32
32
  end
33
33
 
34
- size = File.size(temp_path)
34
+ size = File.size(tempfile.path)
35
35
  ensure
36
- temp_path.delete
36
+ tempfile.close
37
37
  end
38
38
 
39
39
  puts "Downloaded plugin (#{size} bytes)"
@@ -11,6 +11,7 @@ module TRMNLP
11
11
  authenticate!
12
12
 
13
13
  is_new = false
14
+ zip_path = 'upload.zip'
14
15
 
15
16
  api = APIClient.new(config)
16
17
 
@@ -27,20 +28,16 @@ module TRMNLP
27
28
  raise Error, 'aborting' unless answer == 'y' || answer == 'yes'
28
29
  end
29
30
 
30
- size = 0
31
-
32
- Tempfile.create(binmode: true) do |temp_file|
33
- Zip::File.open(temp_file.path, Zip::File::CREATE) do |zip_file|
34
- paths.src_files.each do |file|
35
- zip_file.add(File.basename(file), file)
36
- end
31
+ Zip::File.open(zip_path, Zip::File::CREATE) do |zip_file|
32
+ paths.src_files.each do |file|
33
+ zip_file.add(File.basename(file), file)
37
34
  end
38
-
39
- response = api.post_plugin_setting_archive(plugin_settings_id, temp_file.path)
40
- paths.plugin_config.write(response.dig('data', 'settings_yaml'))
41
-
42
- size = File.size(temp_file.path)
43
35
  end
36
+
37
+ response = api.post_plugin_setting_archive(plugin_settings_id, zip_path)
38
+ paths.plugin_config.write(response.dig('data', 'settings_yaml'))
39
+
40
+ size = File.size(zip_path)
44
41
 
45
42
  output <<~HEREDOC
46
43
  Uploaded plugin (#{size} bytes)
@@ -55,6 +52,15 @@ module TRMNLP
55
52
  #{config.app.playlists_uri}
56
53
  HEREDOC
57
54
  end
55
+ rescue
56
+ if is_new && plugin_settings_id
57
+ output 'Error during creation, cleaning up...'
58
+ api.delete_plugin_setting(plugin_settings_id)
59
+ end
60
+
61
+ raise
62
+ ensure
63
+ File.delete(zip_path) if File.exist?(zip_path)
58
64
  end
59
65
  end
60
66
  end
@@ -17,7 +17,11 @@ module TRMNLP
17
17
  def logged_in? = api_key && !api_key.empty?
18
18
  def logged_out? = !logged_in?
19
19
 
20
- def api_key = @config['api_key']
20
+ def api_key
21
+ env_api_key = ENV['TRMNL_API_KEY']
22
+ return env_api_key if env_api_key && !env_api_key.empty?
23
+ @config['api_key']
24
+ end
21
25
 
22
26
  def api_key=(key)
23
27
  @config['api_key'] = key
@@ -3,9 +3,9 @@ require 'yaml'
3
3
  module TRMNLP
4
4
  class Config
5
5
  class Plugin
6
- def initialize(paths, trmnlp_config)
6
+ def initialize(paths, project_config)
7
7
  @paths = paths
8
- @trmnlp_config = trmnlp_config
8
+ @project_config = project_config
9
9
  reload!
10
10
  end
11
11
 
@@ -23,11 +23,12 @@ module TRMNLP
23
23
  def static? = strategy == 'static'
24
24
 
25
25
  def polling_urls
26
- return [] if @config['polling_url'].nil? || @config['polling_url'].empty?
26
+ # allow project-level config to override
27
+ urls = project_config.user_data_overrides.dig('trmnl', 'plugin_settings', 'polling_url') || @config['polling_url']
27
28
 
28
- urls = @config['polling_url'].split("\n").map(&:strip)
29
+ return [] if urls.nil?
29
30
 
30
- urls.map { |url| with_custom_fields(url) }
31
+ urls.strip.split("\n").map { |url| with_custom_fields(url.strip) }
31
32
  end
32
33
 
33
34
  def polling_url_text = polling_urls.join("\r\n") # for {{ trmnl }}
@@ -56,9 +57,9 @@ module TRMNLP
56
57
 
57
58
  private
58
59
 
59
- attr_reader :paths, :trmnlp_config
60
+ attr_reader :paths, :project_config
60
61
 
61
- def with_custom_fields(value) = trmnlp_config.with_custom_fields(value)
62
+ def with_custom_fields(value) = project_config.with_custom_fields(value)
62
63
 
63
64
  # copied from TRMNL core
64
65
  def string_to_hash(str, delimiter: '=')
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TRMNLP
4
- VERSION = "0.5.4".freeze
4
+ VERSION = "0.5.6".freeze
5
5
  end
@@ -0,0 +1,31 @@
1
+ #! /bin/bash
2
+ #
3
+ # This script was automatically generated by `trmnlp init` but it's yours to modify!
4
+ # Use this opportunity to set up environment variables, install dependencies, etc.
5
+ #
6
+
7
+ if command -v trmnlp &> /dev/null
8
+ then
9
+ trmnlp "$@"
10
+ exit
11
+ fi
12
+
13
+ if command -v docker &> /dev/null
14
+ then
15
+ docker run \
16
+ --rm \
17
+ --publish 4567:4567 \
18
+ --volume "$(pwd):/plugin" \
19
+ trmnl/trmnlp "$@"
20
+ exit
21
+ fi
22
+
23
+ echo "Install the trmnl_preview gem:
24
+
25
+ gem install trmnl_preview
26
+
27
+ Or install Docker:
28
+
29
+ https://docs.docker.com/get-docker/"
30
+
31
+ exit 1
@@ -0,0 +1,315 @@
1
+ /*!
2
+ Highlight.js v11.11.1 (git: 08cb242e7d)
3
+ (c) 2006-2025 Josh Goebel <hello@joshgoebel.com> and other contributors
4
+ License: BSD-3-Clause
5
+ */
6
+ var hljs=function(){"use strict";function e(t){
7
+ return t instanceof Map?t.clear=t.delete=t.set=()=>{
8
+ throw Error("map is read-only")}:t instanceof Set&&(t.add=t.clear=t.delete=()=>{
9
+ throw Error("set is read-only")
10
+ }),Object.freeze(t),Object.getOwnPropertyNames(t).forEach((n=>{
11
+ const i=t[n],s=typeof i;"object"!==s&&"function"!==s||Object.isFrozen(i)||e(i)
12
+ })),t}class t{constructor(e){
13
+ void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}
14
+ ignoreMatch(){this.isMatchIgnored=!0}}function n(e){
15
+ return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;")
16
+ }function i(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t]
17
+ ;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const s=e=>!!e.scope
18
+ ;class r{constructor(e,t){
19
+ this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){
20
+ this.buffer+=n(e)}openNode(e){if(!s(e))return;const t=((e,{prefix:t})=>{
21
+ if(e.startsWith("language:"))return e.replace("language:","language-")
22
+ ;if(e.includes(".")){const n=e.split(".")
23
+ ;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ")
24
+ }return`${t}${e}`})(e.scope,{prefix:this.classPrefix});this.span(t)}
25
+ closeNode(e){s(e)&&(this.buffer+="</span>")}value(){return this.buffer}span(e){
26
+ this.buffer+=`<span class="${e}">`}}const o=(e={})=>{const t={children:[]}
27
+ ;return Object.assign(t,e),t};class a{constructor(){
28
+ this.rootNode=o(),this.stack=[this.rootNode]}get top(){
29
+ return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){
30
+ this.top.children.push(e)}openNode(e){const t=o({scope:e})
31
+ ;this.add(t),this.stack.push(t)}closeNode(){
32
+ if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){
33
+ for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}
34
+ walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){
35
+ return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t),
36
+ t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){
37
+ "string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{
38
+ a._collapse(e)})))}}class c extends a{constructor(e){super(),this.options=e}
39
+ addText(e){""!==e&&this.add(e)}startScope(e){this.openNode(e)}endScope(){
40
+ this.closeNode()}__addSublanguage(e,t){const n=e.root
41
+ ;t&&(n.scope="language:"+t),this.add(n)}toHTML(){
42
+ return new r(this,this.options).value()}finalize(){
43
+ return this.closeAllNodes(),!0}}function l(e){
44
+ return e?"string"==typeof e?e:e.source:null}function g(e){return h("(?=",e,")")}
45
+ function u(e){return h("(?:",e,")*")}function d(e){return h("(?:",e,")?")}
46
+ function h(...e){return e.map((e=>l(e))).join("")}function f(...e){const t=(e=>{
47
+ const t=e[e.length-1]
48
+ ;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{}
49
+ })(e);return"("+(t.capture?"":"?:")+e.map((e=>l(e))).join("|")+")"}
50
+ function p(e){return RegExp(e.toString()+"|").exec("").length-1}
51
+ const b=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./
52
+ ;function m(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n
53
+ ;let i=l(e),s="";for(;i.length>0;){const e=b.exec(i);if(!e){s+=i;break}
54
+ s+=i.substring(0,e.index),
55
+ i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?s+="\\"+(Number(e[1])+t):(s+=e[0],
56
+ "("===e[0]&&n++)}return s})).map((e=>`(${e})`)).join(t)}
57
+ const E="[a-zA-Z]\\w*",x="[a-zA-Z_]\\w*",y="\\b\\d+(\\.\\d+)?",_="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",w="\\b(0b[01]+)",O={
58
+ begin:"\\\\[\\s\\S]",relevance:0},v={scope:"string",begin:"'",end:"'",
59
+ illegal:"\\n",contains:[O]},k={scope:"string",begin:'"',end:'"',illegal:"\\n",
60
+ contains:[O]},N=(e,t,n={})=>{const s=i({scope:"comment",begin:e,end:t,
61
+ contains:[]},n);s.contains.push({scope:"doctag",
62
+ begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)",
63
+ end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0})
64
+ ;const r=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/)
65
+ ;return s.contains.push({begin:h(/[ ]+/,"(",r,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),s
66
+ },S=N("//","$"),M=N("/\\*","\\*/"),R=N("#","$");var j=Object.freeze({
67
+ __proto__:null,APOS_STRING_MODE:v,BACKSLASH_ESCAPE:O,BINARY_NUMBER_MODE:{
68
+ scope:"number",begin:w,relevance:0},BINARY_NUMBER_RE:w,COMMENT:N,
69
+ C_BLOCK_COMMENT_MODE:M,C_LINE_COMMENT_MODE:S,C_NUMBER_MODE:{scope:"number",
70
+ begin:_,relevance:0},C_NUMBER_RE:_,END_SAME_AS_BEGIN:e=>Object.assign(e,{
71
+ "on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{
72
+ t.data._beginMatch!==e[1]&&t.ignoreMatch()}}),HASH_COMMENT_MODE:R,IDENT_RE:E,
73
+ MATCH_NOTHING_RE:/\b\B/,METHOD_GUARD:{begin:"\\.\\s*"+x,relevance:0},
74
+ NUMBER_MODE:{scope:"number",begin:y,relevance:0},NUMBER_RE:y,
75
+ PHRASAL_WORDS_MODE:{
76
+ begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/
77
+ },QUOTE_STRING_MODE:k,REGEXP_MODE:{scope:"regexp",begin:/\/(?=[^/\n]*\/)/,
78
+ end:/\/[gimuy]*/,contains:[O,{begin:/\[/,end:/\]/,relevance:0,contains:[O]}]},
79
+ RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",
80
+ SHEBANG:(e={})=>{const t=/^#![ ]*\//
81
+ ;return e.binary&&(e.begin=h(t,/.*\b/,e.binary,/\b.*/)),i({scope:"meta",begin:t,
82
+ end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)},
83
+ TITLE_MODE:{scope:"title",begin:E,relevance:0},UNDERSCORE_IDENT_RE:x,
84
+ UNDERSCORE_TITLE_MODE:{scope:"title",begin:x,relevance:0}});function A(e,t){
85
+ "."===e.input[e.index-1]&&t.ignoreMatch()}function I(e,t){
86
+ void 0!==e.className&&(e.scope=e.className,delete e.className)}function T(e,t){
87
+ t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",
88
+ e.__beforeBegin=A,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,
89
+ void 0===e.relevance&&(e.relevance=0))}function L(e,t){
90
+ Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function B(e,t){
91
+ if(e.match){
92
+ if(e.begin||e.end)throw Error("begin & end are not supported with match")
93
+ ;e.begin=e.match,delete e.match}}function P(e,t){
94
+ void 0===e.relevance&&(e.relevance=1)}const D=(e,t)=>{if(!e.beforeMatch)return
95
+ ;if(e.starts)throw Error("beforeMatch cannot be used with starts")
96
+ ;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t]
97
+ })),e.keywords=n.keywords,e.begin=h(n.beforeMatch,g(n.begin)),e.starts={
98
+ relevance:0,contains:[Object.assign(n,{endsParent:!0})]
99
+ },e.relevance=0,delete n.beforeMatch
100
+ },H=["of","and","for","in","not","or","if","then","parent","list","value"]
101
+ ;function C(e,t,n="keyword"){const i=Object.create(null)
102
+ ;return"string"==typeof e?s(n,e.split(" ")):Array.isArray(e)?s(n,e):Object.keys(e).forEach((n=>{
103
+ Object.assign(i,C(e[n],t,n))})),i;function s(e,n){
104
+ t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|")
105
+ ;i[n[0]]=[e,$(n[0],n[1])]}))}}function $(e,t){
106
+ return t?Number(t):(e=>H.includes(e.toLowerCase()))(e)?0:1}const U={},z=e=>{
107
+ console.error(e)},W=(e,...t)=>{console.log("WARN: "+e,...t)},X=(e,t)=>{
108
+ U[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),U[`${e}/${t}`]=!0)
109
+ },G=Error();function K(e,t,{key:n}){let i=0;const s=e[n],r={},o={}
110
+ ;for(let e=1;e<=t.length;e++)o[e+i]=s[e],r[e+i]=!0,i+=p(t[e-1])
111
+ ;e[n]=o,e[n]._emit=r,e[n]._multi=!0}function F(e){(e=>{
112
+ e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope,
113
+ delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={
114
+ _wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope
115
+ }),(e=>{if(Array.isArray(e.begin)){
116
+ if(e.skip||e.excludeBegin||e.returnBegin)throw z("skip, excludeBegin, returnBegin not compatible with beginScope: {}"),
117
+ G
118
+ ;if("object"!=typeof e.beginScope||null===e.beginScope)throw z("beginScope must be object"),
119
+ G;K(e,e.begin,{key:"beginScope"}),e.begin=m(e.begin,{joinWith:""})}})(e),(e=>{
120
+ if(Array.isArray(e.end)){
121
+ if(e.skip||e.excludeEnd||e.returnEnd)throw z("skip, excludeEnd, returnEnd not compatible with endScope: {}"),
122
+ G
123
+ ;if("object"!=typeof e.endScope||null===e.endScope)throw z("endScope must be object"),
124
+ G;K(e,e.end,{key:"endScope"}),e.end=m(e.end,{joinWith:""})}})(e)}function Z(e){
125
+ function t(t,n){
126
+ return RegExp(l(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":""))
127
+ }class n{constructor(){
128
+ this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}
129
+ addRule(e,t){
130
+ t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),
131
+ this.matchAt+=p(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null)
132
+ ;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(m(e,{joinWith:"|"
133
+ }),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex
134
+ ;const t=this.matcherRe.exec(e);if(!t)return null
135
+ ;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n]
136
+ ;return t.splice(0,n),Object.assign(t,i)}}class s{constructor(){
137
+ this.rules=[],this.multiRegexes=[],
138
+ this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){
139
+ if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n
140
+ ;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))),
141
+ t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){
142
+ return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){
143
+ this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){
144
+ const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex
145
+ ;let n=t.exec(e)
146
+ ;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{
147
+ const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}
148
+ return n&&(this.regexIndex+=n.position+1,
149
+ this.regexIndex===this.count&&this.considerAll()),n}}
150
+ if(e.compilerExtensions||(e.compilerExtensions=[]),
151
+ e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.")
152
+ ;return e.classNameAliases=i(e.classNameAliases||{}),function n(r,o){const a=r
153
+ ;if(r.isCompiled)return a
154
+ ;[I,B,F,D].forEach((e=>e(r,o))),e.compilerExtensions.forEach((e=>e(r,o))),
155
+ r.__beforeBegin=null,[T,L,P].forEach((e=>e(r,o))),r.isCompiled=!0;let c=null
156
+ ;return"object"==typeof r.keywords&&r.keywords.$pattern&&(r.keywords=Object.assign({},r.keywords),
157
+ c=r.keywords.$pattern,
158
+ delete r.keywords.$pattern),c=c||/\w+/,r.keywords&&(r.keywords=C(r.keywords,e.case_insensitive)),
159
+ a.keywordPatternRe=t(c,!0),
160
+ o&&(r.begin||(r.begin=/\B|\b/),a.beginRe=t(a.begin),r.end||r.endsWithParent||(r.end=/\B|\b/),
161
+ r.end&&(a.endRe=t(a.end)),
162
+ a.terminatorEnd=l(a.end)||"",r.endsWithParent&&o.terminatorEnd&&(a.terminatorEnd+=(r.end?"|":"")+o.terminatorEnd)),
163
+ r.illegal&&(a.illegalRe=t(r.illegal)),
164
+ r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>i(e,{
165
+ variants:null},t)))),e.cachedVariants?e.cachedVariants:V(e)?i(e,{
166
+ starts:e.starts?i(e.starts):null
167
+ }):Object.isFrozen(e)?i(e):e))("self"===e?r:e)))),r.contains.forEach((e=>{n(e,a)
168
+ })),r.starts&&n(r.starts,o),a.matcher=(e=>{const t=new s
169
+ ;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin"
170
+ }))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end"
171
+ }),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function V(e){
172
+ return!!e&&(e.endsWithParent||V(e.starts))}class q extends Error{
173
+ constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}}
174
+ const J=n,Y=i,Q=Symbol("nomatch"),ee=n=>{
175
+ const i=Object.create(null),s=Object.create(null),r=[];let o=!0
176
+ ;const a="Could not find the language '{}', did you forget to load/include a language module?",l={
177
+ disableAutodetect:!0,name:"Plain text",contains:[]};let p={
178
+ ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,
179
+ languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",
180
+ cssSelector:"pre code",languages:null,__emitter:c};function b(e){
181
+ return p.noHighlightRe.test(e)}function m(e,t,n){let i="",s=""
182
+ ;"object"==typeof t?(i=e,
183
+ n=t.ignoreIllegals,s=t.language):(X("10.7.0","highlight(lang, code, ...args) has been deprecated."),
184
+ X("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),
185
+ s=e,i=t),void 0===n&&(n=!0);const r={code:i,language:s};N("before:highlight",r)
186
+ ;const o=r.result?r.result:E(r.language,r.code,n)
187
+ ;return o.code=r.code,N("after:highlight",o),o}function E(e,n,s,r){
188
+ const c=Object.create(null);function l(){if(!N.keywords)return void M.addText(R)
189
+ ;let e=0;N.keywordPatternRe.lastIndex=0;let t=N.keywordPatternRe.exec(R),n=""
190
+ ;for(;t;){n+=R.substring(e,t.index)
191
+ ;const s=w.case_insensitive?t[0].toLowerCase():t[0],r=(i=s,N.keywords[i]);if(r){
192
+ const[e,i]=r
193
+ ;if(M.addText(n),n="",c[s]=(c[s]||0)+1,c[s]<=7&&(j+=i),e.startsWith("_"))n+=t[0];else{
194
+ const n=w.classNameAliases[e]||e;u(t[0],n)}}else n+=t[0]
195
+ ;e=N.keywordPatternRe.lastIndex,t=N.keywordPatternRe.exec(R)}var i
196
+ ;n+=R.substring(e),M.addText(n)}function g(){null!=N.subLanguage?(()=>{
197
+ if(""===R)return;let e=null;if("string"==typeof N.subLanguage){
198
+ if(!i[N.subLanguage])return void M.addText(R)
199
+ ;e=E(N.subLanguage,R,!0,S[N.subLanguage]),S[N.subLanguage]=e._top
200
+ }else e=x(R,N.subLanguage.length?N.subLanguage:null)
201
+ ;N.relevance>0&&(j+=e.relevance),M.__addSublanguage(e._emitter,e.language)
202
+ })():l(),R=""}function u(e,t){
203
+ ""!==e&&(M.startScope(t),M.addText(e),M.endScope())}function d(e,t){let n=1
204
+ ;const i=t.length-1;for(;n<=i;){if(!e._emit[n]){n++;continue}
205
+ const i=w.classNameAliases[e[n]]||e[n],s=t[n];i?u(s,i):(R=s,l(),R=""),n++}}
206
+ function h(e,t){
207
+ return e.scope&&"string"==typeof e.scope&&M.openNode(w.classNameAliases[e.scope]||e.scope),
208
+ e.beginScope&&(e.beginScope._wrap?(u(R,w.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap),
209
+ R=""):e.beginScope._multi&&(d(e.beginScope,t),R="")),N=Object.create(e,{parent:{
210
+ value:N}}),N}function f(e,n,i){let s=((e,t)=>{const n=e&&e.exec(t)
211
+ ;return n&&0===n.index})(e.endRe,i);if(s){if(e["on:end"]){const i=new t(e)
212
+ ;e["on:end"](n,i),i.isMatchIgnored&&(s=!1)}if(s){
213
+ for(;e.endsParent&&e.parent;)e=e.parent;return e}}
214
+ if(e.endsWithParent)return f(e.parent,n,i)}function b(e){
215
+ return 0===N.matcher.regexIndex?(R+=e[0],1):(T=!0,0)}function m(e){
216
+ const t=e[0],i=n.substring(e.index),s=f(N,e,i);if(!s)return Q;const r=N
217
+ ;N.endScope&&N.endScope._wrap?(g(),
218
+ u(t,N.endScope._wrap)):N.endScope&&N.endScope._multi?(g(),
219
+ d(N.endScope,e)):r.skip?R+=t:(r.returnEnd||r.excludeEnd||(R+=t),
220
+ g(),r.excludeEnd&&(R=t));do{
221
+ N.scope&&M.closeNode(),N.skip||N.subLanguage||(j+=N.relevance),N=N.parent
222
+ }while(N!==s.parent);return s.starts&&h(s.starts,e),r.returnEnd?0:t.length}
223
+ let y={};function _(i,r){const a=r&&r[0];if(R+=i,null==a)return g(),0
224
+ ;if("begin"===y.type&&"end"===r.type&&y.index===r.index&&""===a){
225
+ if(R+=n.slice(r.index,r.index+1),!o){const t=Error(`0 width match regex (${e})`)
226
+ ;throw t.languageName=e,t.badRule=y.rule,t}return 1}
227
+ if(y=r,"begin"===r.type)return(e=>{
228
+ const n=e[0],i=e.rule,s=new t(i),r=[i.__beforeBegin,i["on:begin"]]
229
+ ;for(const t of r)if(t&&(t(e,s),s.isMatchIgnored))return b(n)
230
+ ;return i.skip?R+=n:(i.excludeBegin&&(R+=n),
231
+ g(),i.returnBegin||i.excludeBegin||(R=n)),h(i,e),i.returnBegin?0:n.length})(r)
232
+ ;if("illegal"===r.type&&!s){
233
+ const e=Error('Illegal lexeme "'+a+'" for mode "'+(N.scope||"<unnamed>")+'"')
234
+ ;throw e.mode=N,e}if("end"===r.type){const e=m(r);if(e!==Q)return e}
235
+ if("illegal"===r.type&&""===a)return R+="\n",1
236
+ ;if(I>1e5&&I>3*r.index)throw Error("potential infinite loop, way more iterations than matches")
237
+ ;return R+=a,a.length}const w=O(e)
238
+ ;if(!w)throw z(a.replace("{}",e)),Error('Unknown language: "'+e+'"')
239
+ ;const v=Z(w);let k="",N=r||v;const S={},M=new p.__emitter(p);(()=>{const e=[]
240
+ ;for(let t=N;t!==w;t=t.parent)t.scope&&e.unshift(t.scope)
241
+ ;e.forEach((e=>M.openNode(e)))})();let R="",j=0,A=0,I=0,T=!1;try{
242
+ if(w.__emitTokens)w.__emitTokens(n,M);else{for(N.matcher.considerAll();;){
243
+ I++,T?T=!1:N.matcher.considerAll(),N.matcher.lastIndex=A
244
+ ;const e=N.matcher.exec(n);if(!e)break;const t=_(n.substring(A,e.index),e)
245
+ ;A=e.index+t}_(n.substring(A))}return M.finalize(),k=M.toHTML(),{language:e,
246
+ value:k,relevance:j,illegal:!1,_emitter:M,_top:N}}catch(t){
247
+ if(t.message&&t.message.includes("Illegal"))return{language:e,value:J(n),
248
+ illegal:!0,relevance:0,_illegalBy:{message:t.message,index:A,
249
+ context:n.slice(A-100,A+100),mode:t.mode,resultSoFar:k},_emitter:M};if(o)return{
250
+ language:e,value:J(n),illegal:!1,relevance:0,errorRaised:t,_emitter:M,_top:N}
251
+ ;throw t}}function x(e,t){t=t||p.languages||Object.keys(i);const n=(e=>{
252
+ const t={value:J(e),illegal:!1,relevance:0,_top:l,_emitter:new p.__emitter(p)}
253
+ ;return t._emitter.addText(e),t})(e),s=t.filter(O).filter(k).map((t=>E(t,e,!1)))
254
+ ;s.unshift(n);const r=s.sort(((e,t)=>{
255
+ if(e.relevance!==t.relevance)return t.relevance-e.relevance
256
+ ;if(e.language&&t.language){if(O(e.language).supersetOf===t.language)return 1
257
+ ;if(O(t.language).supersetOf===e.language)return-1}return 0})),[o,a]=r,c=o
258
+ ;return c.secondBest=a,c}function y(e){let t=null;const n=(e=>{
259
+ let t=e.className+" ";t+=e.parentNode?e.parentNode.className:""
260
+ ;const n=p.languageDetectRe.exec(t);if(n){const t=O(n[1])
261
+ ;return t||(W(a.replace("{}",n[1])),
262
+ W("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"}
263
+ return t.split(/\s+/).find((e=>b(e)||O(e)))})(e);if(b(n))return
264
+ ;if(N("before:highlightElement",{el:e,language:n
265
+ }),e.dataset.highlighted)return void console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e)
266
+ ;if(e.children.length>0&&(p.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),
267
+ console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),
268
+ console.warn("The element with unescaped HTML:"),
269
+ console.warn(e)),p.throwUnescapedHTML))throw new q("One of your code blocks includes unescaped HTML.",e.innerHTML)
270
+ ;t=e;const i=t.textContent,r=n?m(i,{language:n,ignoreIllegals:!0}):x(i)
271
+ ;e.innerHTML=r.value,e.dataset.highlighted="yes",((e,t,n)=>{const i=t&&s[t]||n
272
+ ;e.classList.add("hljs"),e.classList.add("language-"+i)
273
+ })(e,n,r.language),e.result={language:r.language,re:r.relevance,
274
+ relevance:r.relevance},r.secondBest&&(e.secondBest={
275
+ language:r.secondBest.language,relevance:r.secondBest.relevance
276
+ }),N("after:highlightElement",{el:e,result:r,text:i})}let _=!1;function w(){
277
+ if("loading"===document.readyState)return _||window.addEventListener("DOMContentLoaded",(()=>{
278
+ w()}),!1),void(_=!0);document.querySelectorAll(p.cssSelector).forEach(y)}
279
+ function O(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}
280
+ function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{
281
+ s[e.toLowerCase()]=t}))}function k(e){const t=O(e)
282
+ ;return t&&!t.disableAutodetect}function N(e,t){const n=e;r.forEach((e=>{
283
+ e[n]&&e[n](t)}))}Object.assign(n,{highlight:m,highlightAuto:x,highlightAll:w,
284
+ highlightElement:y,
285
+ highlightBlock:e=>(X("10.7.0","highlightBlock will be removed entirely in v12.0"),
286
+ X("10.7.0","Please use highlightElement now."),y(e)),configure:e=>{p=Y(p,e)},
287
+ initHighlighting:()=>{
288
+ w(),X("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")},
289
+ initHighlightingOnLoad:()=>{
290
+ w(),X("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.")
291
+ },registerLanguage:(e,t)=>{let s=null;try{s=t(n)}catch(t){
292
+ if(z("Language definition for '{}' could not be registered.".replace("{}",e)),
293
+ !o)throw t;z(t),s=l}
294
+ s.name||(s.name=e),i[e]=s,s.rawDefinition=t.bind(null,n),s.aliases&&v(s.aliases,{
295
+ languageName:e})},unregisterLanguage:e=>{delete i[e]
296
+ ;for(const t of Object.keys(s))s[t]===e&&delete s[t]},
297
+ listLanguages:()=>Object.keys(i),getLanguage:O,registerAliases:v,
298
+ autoDetection:k,inherit:Y,addPlugin:e=>{(e=>{
299
+ e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{
300
+ e["before:highlightBlock"](Object.assign({block:t.el},t))
301
+ }),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{
302
+ e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),r.push(e)},
303
+ removePlugin:e=>{const t=r.indexOf(e);-1!==t&&r.splice(t,1)}}),n.debugMode=()=>{
304
+ o=!1},n.safeMode=()=>{o=!0},n.versionString="11.11.1",n.regex={concat:h,
305
+ lookahead:g,either:f,optional:d,anyNumberOfTimes:u}
306
+ ;for(const t in j)"object"==typeof j[t]&&e(j[t]);return Object.assign(n,j),n
307
+ },te=ee({});return te.newInstance=()=>ee({}),te}()
308
+ ;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `json` grammar compiled for Highlight.js 11.11.1 */
309
+ (()=>{var e=(()=>{"use strict";return e=>{const a=["true","false","null"],s={
310
+ scope:"literal",beginKeywords:a.join(" ")};return{name:"JSON",aliases:["jsonc"],
311
+ keywords:{literal:a},contains:[{className:"attr",
312
+ begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01},{match:/[{}[\],:]/,
313
+ className:"punctuation",relevance:0
314
+ },e.QUOTE_STRING_MODE,s,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],
315
+ illegal:"\\S"}}})();hljs.registerLanguage("json",e)})();
@@ -0,0 +1 @@
1
+ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
data/web/public/index.css CHANGED
@@ -1,6 +1,7 @@
1
1
  body {
2
2
  font-family: sans-serif;
3
3
  margin: 10px;
4
+ background-color: #eaebef;
4
5
  }
5
6
 
6
7
  main {
@@ -19,7 +20,7 @@ menu {
19
20
 
20
21
  menu a {
21
22
  padding: 0.5em 1em;
22
- background: #ddd;
23
+ background: #c7cbd5;
23
24
  border-radius: 0.5em;
24
25
  display: inline-block;
25
26
  text-decoration: none;
@@ -35,9 +36,10 @@ menu a.active {
35
36
  color: white;
36
37
  }
37
38
 
38
- pre {
39
+ pre > code {
39
40
  max-width: 950px;
40
41
  overflow-x: scroll;
42
+ border-radius: 0.5em;
41
43
  }
42
44
 
43
45
  .spinner {
@@ -57,3 +59,19 @@ pre {
57
59
  transform: rotate(360deg);
58
60
  }
59
61
  }
62
+
63
+ @media (prefers-color-scheme: dark) {
64
+ body {
65
+ background-color: #191b21;
66
+ color: #ffffff;
67
+ }
68
+
69
+ menu a {
70
+ background-color: #262932;
71
+ color: #b1b6c4;
72
+ }
73
+
74
+ menu a:hover {
75
+ background-color: #2f333e;
76
+ }
77
+ }
@@ -204,7 +204,7 @@
204
204
 
205
205
 
206
206
 
207
- <foreignobject class="node" x="36" y="34" width="840" height="520"
207
+ <foreignobject class="node" x="36" y="34" width="830" height="520"
208
208
  style="transform:scale(0.98); position: relative; border-radius: 12px; opacity: 0.9; mix-blend-mode: darken;">
209
209
  <div id="${contentWrapperId}"
210
210
  style="position: static; width: 100%; height: 100%; max-width: 100%; max-height: 100%;">
data/web/views/index.erb CHANGED
@@ -11,6 +11,11 @@
11
11
  <link rel="stylesheet" href="/index.css">
12
12
  <script src="/trmnl-component.js" defer></script>
13
13
  <script src="/index.js"></script>
14
+
15
+ <!-- Highlight.js for syntax highlighting -->
16
+ <link rel="stylesheet" href="/highlight/styles/atom-one-dark.min.css">
17
+ <script src="/highlight/highlight.min.js"></script>
18
+ <script>hljs.highlightAll();</script>
14
19
  </head>
15
20
  <body>
16
21
  <main>
@@ -41,7 +46,7 @@
41
46
 
42
47
  <trmnl-frame>Rendering…</trmnl-frame>
43
48
 
44
- <pre id="user-data"><%= @user_data %></pre>
49
+ <pre id="user-data"><code><%= @user_data %></code></pre>
45
50
  </main>
46
51
  </body>
47
52
  </html>
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trmnl_preview
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rockwell Schrock
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-09 00:00:00.000000000 Z
10
+ date: 2025-07-10 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: sinatra
@@ -286,7 +286,7 @@ files:
286
286
  - lib/trmnlp/screen_generator.rb
287
287
  - lib/trmnlp/version.rb
288
288
  - templates/init/.trmnlp.yml
289
- - templates/init/bin/dev
289
+ - templates/init/bin/trmnlp
290
290
  - templates/init/src/full.liquid
291
291
  - templates/init/src/half_horizontal.liquid
292
292
  - templates/init/src/half_vertical.liquid
@@ -294,6 +294,8 @@ files:
294
294
  - templates/init/src/settings.yml
295
295
  - templates/init/src/shared.liquid
296
296
  - trmnl_preview.gemspec
297
+ - web/public/highlight/highlight.min.js
298
+ - web/public/highlight/styles/atom-one-dark.min.css
297
299
  - web/public/index.css
298
300
  - web/public/index.js
299
301
  - web/public/trmnl-component.js
@@ -1,25 +0,0 @@
1
- #! /bin/bash
2
-
3
- if command -v trmnlp &> /dev/null
4
- then
5
- echo "Starting trmnlp..."
6
- trmnlp serve
7
- exit
8
- fi
9
-
10
- if command -v docker &> /dev/null
11
- then
12
- echo "Running trmnl/trmnlp container..."
13
- docker run -p 4567:4567 -v .:/plugin trmnl/trmnlp
14
- exit
15
- fi
16
-
17
- echo "Install the trmnl_preview gem:
18
-
19
- gem install trmnl_preview
20
-
21
- Or install Docker:
22
-
23
- https://docs.docker.com/get-docker/"
24
-
25
- exit 1