Home

tailpolicy @main - refs - log -
-
https://git.jolheiser.com/tailpolicy.git
Tailscale policy editor on your tailnet
tree log patch
add prism and submit Signed-off-by: jolheiser <git@jolheiser.com>
Signature
-----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgBTEvCQk6VqUAdN2RuH6bj1dNkY oOpbPWj+jw4ua1B1cAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQCRayGJ9cunQwqmX4f/A636/149XWG6VBYSZ6OZdiUwfL5FrHK53uOPCbmeHluJQvH 11/8lfrvITz7RWsyeGxwQ= -----END SSH SIGNATURE-----
jolheiser <git@jolheiser.com>
1 month ago
9 changed files, 204 additions(+), 21 deletions(-)
M .gitignore.gitignore
diff --git a/.gitignore b/.gitignore
index dc5678396ee36caf8e2508a04ea7172f711d8b84..20867276934e392b5449491d64335bd2c03458a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 .tsnet/
+.policy/
M flags.goflags.go
diff --git a/flags.go b/flags.go
index ad39886431f753fcd48f4feaccb11fde317dc034..96eac9b866d551c424debb7c1ac0514e114c67c5 100644
--- a/flags.go
+++ b/flags.go
@@ -5,6 +5,7 @@
 // Args are commandline arguments
 type Args struct {
 	RepoUrl      string
+	RepoOrigin   string
 	RepoLocation string
 	Hostname     string
 	DataDir      string
@@ -16,12 +17,14 @@
 // Flags is the [flag.FlagSet] for tailpolicy
 func Flags() (*Args, *flag.FlagSet) {
 	args := &Args{
-		DataDir:  ".tsnet",
-		Hostname: "policy",
+		DataDir:      ".tsnet",
+		Hostname:     "policy",
+		RepoLocation: ".policy",
 	}
 	fs := flag.NewFlagSet("tailpolicy", flag.ExitOnError)
 	fs.StringVar(&args.RepoUrl, "repo-url", args.RepoUrl, "Repo URL for the Tailscale policy")
-	fs.StringVar(&args.RepoLocation, "repo-location", args.RepoLocation, "Repo location to perform git operations against")
+	fs.StringVar(&args.RepoOrigin, "repo-origin", args.RepoOrigin, "Repo URL for cloning/pushing")
+	fs.StringVar(&args.RepoLocation, "repo-location", args.RepoLocation, "Repo storage location")
 	fs.StringVar(&args.Hostname, "hostname", args.Hostname, "Tailnet hostname")
 	fs.StringVar(&args.DataDir, "data-dir", args.DataDir, "tsnet data directory")
 	fs.StringVar(&args.AuthKey, "auth-key", args.AuthKey, "tsnet auth key")
M index.goindex.go
diff --git a/index.go b/index.go
index f04d3049ba13d44c1d77457f66b2ea8392deef41..acf87e8b25914215a9879f29787616ca7264de65 100644
--- a/index.go
+++ b/index.go
@@ -78,7 +78,7 @@ 				}
 				if err := repo.Push(&git.PushOptions{}); err != nil {
 					return err
 				}
-				http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
+				http.Redirect(w, r, "/", http.StatusSeeOther)
 				return nil
 			default:
 				return errors.New("method not allowed")
M main.gomain.go
diff --git a/main.go b/main.go
index 9592ed3ca44ddb057811b669703a6d32aa0b1bde..11666fb37ae0aa5c3089576496e7cab0ad508001 100644
--- a/main.go
+++ b/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	_ "embed"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -16,6 +17,15 @@ 	"go.jolheiser.com/tailpolicy/static"
 	"go.jolheiser.com/tailroute"
 )
 
+var (
+	//go:embed static/prism.js
+	prismJS []byte
+	//go:embed static/prism.css
+	prismCSS []byte
+	//go:embed static/ctp.css
+	ctpCSS []byte
+)
+
 func maine() error {
 	args, fs := Flags()
 	if err := fs.Parse(os.Args[1:]); err != nil {
@@ -29,6 +39,18 @@ 	mux := http.NewServeMux()
 	mux.HandleFunc("/", Index(args.RepoUrl, args.RepoLocation))
 	mux.HandleFunc("/preview", preview(args.RepoUrl))
 	mux.HandleFunc("/tailwind.css", static.TailwindHandler)
+	mux.HandleFunc("/prism.js", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "text/javascript")
+		w.Write(prismJS)
+	})
+	mux.HandleFunc("/prism.css", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "text/css")
+		w.Write(prismCSS)
+	})
+	mux.HandleFunc("/ctp.css", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "text/css")
+		w.Write(ctpCSS)
+	})
 	r := tailroute.Router{
 		Tailnet: mux,
 	}
@@ -38,7 +60,7 @@ 		if err := os.MkdirAll(filepath.Dir(args.RepoLocation), os.ModePerm); err != nil {
 			return err
 		}
 		if _, err := git.PlainClone(args.RepoLocation, false, &git.CloneOptions{
-			URL: args.RepoUrl,
+			URL: args.RepoOrigin,
 		}); err != nil {
 			return err
 		}
M nix/module.nixnix/module.nix
diff --git a/nix/module.nix b/nix/module.nix
index 6fc0342232a168228551040f8e45bbbc831fe7cf..c8413873315281056d427ff7151b78e5de2b81b8 100644
--- a/nix/module.nix
+++ b/nix/module.nix
@@ -27,9 +27,14 @@         repo-url = mkOption {
           type = types.str;
           description = "Repo URL for the Tailscale policy";
         };
+        repo-origin = mkOption {
+          type = types.str;
+          description = "Repo URL for cloning/pushing";
+        };
         repo-location = mkOption {
           type = types.str;
-          description = "Repo location to perform git operations against";
+          description = "Repo storage location";
+          default = "/var/lib/tailpolicy/policy";
         };
         hostname = mkOption {
           type = types.str;
@@ -39,7 +44,7 @@         };
         data-dir = mkOption {
           type = types.str;
           description = "tsnet data directory";
-          default = ".tsnet";
+          default = "/var/lib/tailpolicy/tsnet";
 
         };
         auth-key = mkOption {
Istatic/ctp.css
diff --git a/static/ctp.css b/static/ctp.css
new file mode 100644
index 0000000000000000000000000000000000000000..ba2456d283f355e6b9e73d3e1db1ca3e77fc7bfc
--- /dev/null
+++ b/static/ctp.css
@@ -0,0 +1,135 @@
+code[class*="language-"],
+pre[class*="language-"] {
+  color: #cdd6f4;
+}
+
+:not(pre)>code[class*="language-"],
+pre[class*="language-"] {
+  background: #181825;
+}
+
+/* https://prismjs.com/tokens.html */
+
+.token.keyword {
+  color: #cba6f7;
+}
+
+.token.builtin {
+  color: #f38ba8;
+}
+
+.token.class-name {
+  color: #f9e2af;
+}
+
+.token.function {
+  color: #89b4fa;
+}
+
+.token.boolean,
+.token.number {
+  color: #fab387;
+}
+
+.token.string,
+.token.char {
+  color: #a6e3a1;
+}
+
+.token.symbol {
+  color: #f9e2af;
+}
+
+.token.regex {
+  color: #f5c2e7;
+}
+
+.token.url {
+  color: #a6e3a1;
+}
+
+.token.operator {
+  color: #89dceb;
+}
+
+.token.variable {
+  color: #cdd6f4;
+}
+
+.token.constant {
+  color: #fab387;
+}
+
+.token.property {
+  color: #89b4fa;
+}
+
+.token.punctuation {
+  color: #9399b2;
+}
+
+.token.important {
+  color: #cba6f7;
+}
+
+.token.comment {
+  color: #9399b2;
+}
+
+.token.tag {
+  color: #89b4fa;
+}
+
+.token.attr-name {
+  color: #f9e2af;
+}
+
+.token.attr-value {
+  color: #a6e3a1;
+}
+
+.token.namespace {
+  color: #f9e2af;
+}
+
+.token.prolog,
+.token.doctype {
+  color: #cba6f7;
+}
+
+.token.cdata {
+  color: #94e2d5;
+}
+
+.token.entity {
+  color: #f38ba8;
+}
+
+.token.atrule {
+  color: #cba6f7;
+}
+
+.token.selector {
+  color: #89b4fa;
+}
+
+/* Diff */
+
+.token.deleted {
+  color: #f38ba8;
+}
+
+.token.inserted {
+  color: #a6e3a1
+}
+
+/* Other */
+
+.token.important,
+.token.bold {
+  font-weight: bold;
+}
+
+.token.italic {
+  font-style: italic;
+}
\ No newline at end of file
M static/index.tmplstatic/index.tmpl
diff --git a/static/index.tmpl b/static/index.tmpl
index 63f20bbf87c08234887e8681b8430477412f1720..5252efeada0b5ae9de13ce8ff8f588f68bae7bbd 100644
--- a/static/index.tmpl
+++ b/static/index.tmpl
@@ -4,24 +4,30 @@
 <head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>cfg</title>
+  <title>tailpolicy</title>
   <link rel="stylesheet" href="/tailwind.css" />
+  <link rel="stylesheet" href="/prism.css" />
+  <link rel="stylesheet" href="/ctp.css" />
 </head>
 
 <body class="latte dark:mocha bg-base/50 dark:bg-base/95 text-text">
-  <div class="flex h-screen">
-    <div class="flex-1 flex flex-col p-4">
-      <h2 class="text-2xl font-bold mb-4">Jsonnet</h2>
-      <textarea id="input" name="content"
-        class="bg-base dark:bg-base/50 flex-1 p-2 border rounded">{{ .content }}</textarea>
-    </div>
-    <div class="flex-1 flex flex-col p-4">
-      <h2 class="text-2xl font-bold mb-4">Policy</h2>
-      <textarea id="output" class="bg-base dark:bg-base/50 flex-1 p-2 border rounded"
-        placeholder="Converted policy will appear here..." readonly></textarea>
+  <form method="POST">
+    <div class="flex h-screen">
+      <div class="flex-1 flex flex-col p-4">
+        <h2 class="text-2xl font-bold mb-4">Jsonnet</h2>
+        <textarea id="input" name="content" class="bg-base dark:bg-base/50 flex-1 p-2 border rounded">{{ .content
+          }}</textarea>
+      </div>
+      <div class="flex-1 flex flex-col p-4">
+        <h2 class="text-2xl font-bold mb-4">Policy</h2>
+        <pre class="bg-base dark:bg-base/50 flex-1 p-2 border rounded"><code id="output" class="language-json5"></code>
+        </pre>
+      </div>
     </div>
-  </div>
+    <button type="submit">Save ACL</button>
+  </form>
 
+  <script src="/prism.js" data-manual></script>
   <script>
     const $inputText = document.getElementById('input');
     const $outputText = document.getElementById('output');
@@ -37,6 +43,7 @@         this.selectionStart = this.selectionEnd = start + 2;
       }
     });
 
+    if ($inputText.value !== "") convert();
 
     function convert() {
       fetch('/preview', {
@@ -50,11 +57,12 @@         }),
       })
         .then(response => response.text())
         .then(data => {
-          $outputText.value = data;
+          $outputText.innerHTML = data;
+          Prism.highlightElement($outputText);
         })
         .catch(error => {
           console.error('Error:', error);
-          $outputText.value = 'An error occurred during conversion.';
+          $outputText.innerHTML = 'An error occurred during conversion.';
         });
     }
 
Istatic/prism.css
diff --git a/static/prism.css b/static/prism.css
new file mode 100644
index 0000000000000000000000000000000000000000..e72328992ae4b0d3233a7a5418f6d1c1685eceee
--- /dev/null
+++ b/static/prism.css
@@ -0,0 +1,3 @@
+/* PrismJS 1.29.0
+https://prismjs.com/download.html#themes=prism&languages=json+json5 */
+code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
Istatic/prism.js
diff --git a/static/prism.js b/static/prism.js
new file mode 100644
index 0000000000000000000000000000000000000000..bf1ab8d82cbf2fa756bf45aadfa9fb619a618c0a
--- /dev/null
+++ b/static/prism.js
@@ -0,0 +1,6 @@
+/* PrismJS 1.29.0
+https://prismjs.com/download.html#themes=prism&languages=json+json5 */
+var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function e(n,t){var r,i;switch(t=t||{},a.util.type(n)){case"Object":if(i=a.util.objId(n),t[i])return t[i];for(var l in r={},t[i]=r,n)n.hasOwnProperty(l)&&(r[l]=e(n[l],t));return r;case"Array":return i=a.util.objId(n),t[i]?t[i]:(r=[],t[i]=r,n.forEach((function(n,a){r[a]=e(n,t)})),r);default:return n}},getLanguage:function(e){for(;e;){var t=n.exec(e.className);if(t)return t[1].toLowerCase();e=e.parentElement}return"none"},setLanguage:function(e,t){e.className=e.className.replace(RegExp(n,"gi"),""),e.classList.add("language-"+t)},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(r){var e=(/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(r.stack)||[])[1];if(e){var n=document.getElementsByTagName("script");for(var t in n)if(n[t].src==e)return n[t]}return null}},isActive:function(e,n,t){for(var r="no-"+n;e;){var a=e.classList;if(a.contains(n))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!t}},languages:{plain:r,plaintext:r,text:r,txt:r,extend:function(e,n){var t=a.util.clone(a.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(e,n,t,r){var i=(r=r||a.languages)[e],l={};for(var o in i)if(i.hasOwnProperty(o)){if(o==n)for(var s in t)t.hasOwnProperty(s)&&(l[s]=t[s]);t.hasOwnProperty(o)||(l[o]=i[o])}var u=r[e];return r[e]=l,a.languages.DFS(a.languages,(function(n,t){t===u&&n!=e&&(this[n]=l)})),l},DFS:function e(n,t,r,i){i=i||{};var l=a.util.objId;for(var o in n)if(n.hasOwnProperty(o)){t.call(n,o,n[o],r||o);var s=n[o],u=a.util.type(s);"Object"!==u||i[l(s)]?"Array"!==u||i[l(s)]||(i[l(s)]=!0,e(s,t,o,i)):(i[l(s)]=!0,e(s,t,null,i))}}},plugins:{},highlightAll:function(e,n){a.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};a.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),a.hooks.run("before-all-elements-highlight",r);for(var i,l=0;i=r.elements[l++];)a.highlightElement(i,!0===n,r.callback)},highlightElement:function(n,t,r){var i=a.util.getLanguage(n),l=a.languages[i];a.util.setLanguage(n,i);var o=n.parentElement;o&&"pre"===o.nodeName.toLowerCase()&&a.util.setLanguage(o,i);var s={element:n,language:i,grammar:l,code:n.textContent};function u(e){s.highlightedCode=e,a.hooks.run("before-insert",s),s.element.innerHTML=s.highlightedCode,a.hooks.run("after-highlight",s),a.hooks.run("complete",s),r&&r.call(s.element)}if(a.hooks.run("before-sanity-check",s),(o=s.element.parentElement)&&"pre"===o.nodeName.toLowerCase()&&!o.hasAttribute("tabindex")&&o.setAttribute("tabindex","0"),!s.code)return a.hooks.run("complete",s),void(r&&r.call(s.element));if(a.hooks.run("before-highlight",s),s.grammar)if(t&&e.Worker){var c=new Worker(a.filename);c.onmessage=function(e){u(e.data)},c.postMessage(JSON.stringify({language:s.language,code:s.code,immediateClose:!0}))}else u(a.highlight(s.code,s.grammar,s.language));else u(a.util.encode(s.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};if(a.hooks.run("before-tokenize",r),!r.grammar)throw new Error('The language "'+r.language+'" has no grammar.');return r.tokens=a.tokenize(r.code,r.grammar),a.hooks.run("after-tokenize",r),i.stringify(a.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new s;return u(a,a.head,e),o(e,a,n,a.head,0),function(e){for(var n=[],t=e.head.next;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=a.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=a.hooks.all[e];if(t&&t.length)for(var r,i=0;r=t[i++];)r(n)}},Token:i};function i(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||"").length}function l(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function o(e,n,t,r,s,g){for(var f in t)if(t.hasOwnProperty(f)&&t[f]){var h=t[f];h=Array.isArray(h)?h:[h];for(var d=0;d<h.length;++d){if(g&&g.cause==f+","+d)return;var v=h[d],p=v.inside,m=!!v.lookbehind,y=!!v.greedy,k=v.alias;if(y&&!v.pattern.global){var x=v.pattern.toString().match(/[imsuy]*$/)[0];v.pattern=RegExp(v.pattern.source,x+"g")}for(var b=v.pattern||v,w=r.next,A=s;w!==n.tail&&!(g&&A>=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(j<O||"string"==typeof C.value);C=C.next)L++,j+=C.value.length;L--,E=e.slice(A,j),P.index-=A}else if(!(P=l(b,0,E,m)))continue;S=P.index;var N=P[0],_=E.slice(0,S),M=E.slice(S+N.length),W=A+E.length;g&&W>g.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a<t&&r!==e.tail;a++)r=r.next;n.next=r,r.prev=n,e.length-=a}if(e.Prism=a,i.stringify=function e(n,t){if("string"==typeof n)return n;if(Array.isArray(n)){var r="";return n.forEach((function(n){r+=e(n,t)})),r}var i={type:n.type,content:e(n.content,t),tag:"span",classes:["token",n.type],attributes:{},language:t},l=n.alias;l&&(Array.isArray(l)?Array.prototype.push.apply(i.classes,l):i.classes.push(l)),a.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=" "+s+'="'+(i.attributes[s]||"").replace(/"/g,"&quot;")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+o+">"+i.content+"</"+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
+Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json;
+!function(n){var e=/("|')(?:\\(?:\r\n?|\n|.)|(?!\1)[^\\\r\n])*\1/;n.languages.json5=n.languages.extend("json",{property:[{pattern:RegExp(e.source+"(?=\\s*:)"),greedy:!0},{pattern:/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/,alias:"unquoted"}],string:{pattern:e,greedy:!0},number:/[+-]?\b(?:NaN|Infinity|0x[a-fA-F\d]+)\b|[+-]?(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[eE][+-]?\d+\b)?/})}(Prism);
+