scripts: replace dark-variant.sh with go scripts/variants

uses regex mostly, resulting in a significantly faster execution (old:
~2s, new: ~31ms). Only matches classList.add/remove and class="..."
(won't touch string literal variables or colours), but these weren't
really used anyway. Supports multi-line classList.add/remove, which was
the reason I wrote this anyway (formatting the codebase introduced some
of these).
This commit is contained in:
Harvey Tindall
2025-12-09 14:28:05 +00:00
parent 817107622a
commit 4a9bac1027
7 changed files with 166 additions and 61 deletions

1
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1 @@
817107622a8fe6f2fdaf198da4b2632854aa9bac

View File

@@ -142,11 +142,14 @@ TYPESCRIPT_TEMPSRC = $(TYPESCRIPT_SRC:ts/%=tempts/%)
TYPESCRIPT_TARGET = $(DATA)/web/js/admin.js TYPESCRIPT_TARGET = $(DATA)/web/js/admin.js
$(TYPESCRIPT_TARGET): $(TYPESCRIPT_FULLSRC) ts/tsconfig.json $(TYPESCRIPT_TARGET): $(TYPESCRIPT_FULLSRC) ts/tsconfig.json
$(TYPECHECK) $(TYPECHECK)
# rm -rf tempts
# cp -r ts tempts
rm -rf tempts rm -rf tempts
cp -r ts tempts mkdir -p tempts
$(adding dark variants to typescript) $(adding dark variants to typescript)
scripts/dark-variant.sh tempts # scripts/dark-variant.sh tempts
scripts/dark-variant.sh tempts/modules # scripts/dark-variant.sh tempts/modules
go run scripts/variants/main.go -dir ts -out tempts
$(info compiling typescript) $(info compiling typescript)
$(foreach tempsrc,$(TYPESCRIPT_TEMPSRC),$(ESBUILD) --target=es6 --bundle $(tempsrc) $(SOURCEMAP) --outfile=$(patsubst %.ts,%.js,$(subst tempts/,./$(DATA)/web/js/,$(tempsrc))) $(MINIFY);) $(foreach tempsrc,$(TYPESCRIPT_TEMPSRC),$(ESBUILD) --target=es6 --bundle $(tempsrc) $(SOURCEMAP) --outfile=$(patsubst %.ts,%.js,$(subst tempts/,./$(DATA)/web/js/,$(tempsrc))) $(MINIFY);)
$(COPYTS) $(COPYTS)

View File

@@ -23,15 +23,16 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/goccy/go-yaml" "github.com/goccy/go-yaml"
"github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3"
"gopkg.in/ini.v1"
"github.com/hrfee/jfa-go/common" "github.com/hrfee/jfa-go/common"
_ "github.com/hrfee/jfa-go/docs" _ "github.com/hrfee/jfa-go/docs"
"github.com/hrfee/jfa-go/jellyseerr" "github.com/hrfee/jfa-go/jellyseerr"
"github.com/hrfee/jfa-go/logger" "github.com/hrfee/jfa-go/logger"
lm "github.com/hrfee/jfa-go/logmessages" lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/jfa-go/ombi" "github.com/hrfee/jfa-go/ombi"
"github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3"
"gopkg.in/ini.v1"
) )
var ( var (
@@ -77,7 +78,9 @@ var serverTypes = map[string]string{
"jellyfin": "Jellyfin", "jellyfin": "Jellyfin",
"emby": "Emby (experimental)", "emby": "Emby (experimental)",
} }
var serverType = mediabrowser.JellyfinServer var serverType = mediabrowser.JellyfinServer
var substituteStrings = "" var substituteStrings = ""
var externalURI, externalDomain string // The latter lower-case as should be accessed through app.ExternalDomain() var externalURI, externalDomain string // The latter lower-case as should be accessed through app.ExternalDomain()

View File

@@ -1,50 +0,0 @@
#!/usr/bin/env bash
# scan all typescript and automatically add dark variants to color tags if they're not already present.
for f in $1/*.ts; do
# FIXME: inline html
for l in $(grep -n "~neutral\|~positive\|~urge\|~warning\|~info\|~critical" $f | sed -e 's/:.*//g'); do
# for l in $(sed -n '/classList/=' $f); do
line=$(sed -n "${l}p" $f)
echo $line | grep "classList" &> /dev/null
if [ $? -eq 0 ]; then
echo $line | sed 's/.*classList//; s/).*//' | grep "~neutral\|~positive\|~urge\|~warning\|~info\|~critical" &> /dev/null
if [ $? -eq 0 ]; then
# echo "found classList @ " $l
echo $line | grep "dark:" &>/dev/null
if [ $? -ne 0 ]; then
for color in neutral positive urge warning info critical; do
sed -i "${l},${l}s/\"~${color}\"/\"~${color}\", \"dark:~d_${color}\"/g" $f
done
fi
else
echo "FIX: classList found, but color tag wasn't in it"
fi
else
echo $line | grep "querySelector" &> /dev/null
if [ $? -ne 0 ]; then
# echo "found inline in " $f " @ " $l ", " $(sed -n "${l}p" $f)
echo $line | grep "dark:" &>/dev/null
if [ $? -ne 0 ]; then
for color in neutral positive urge warning info critical; do
sed -i "${l},${l}s/~${color}/~${color} dark:~d_${color}/g" $f
done
fi
else
echo $line | sed 's/.*querySelector//; s/).*//' | grep "~neutral\|~positive\|~urge\|~warning\|~info\|~critical" &> /dev/null
if [ $? -ne 0 ]; then
echo $line | grep "dark:" &>/dev/null
if [ $? -ne 0 ]; then
# echo "found inline in " $f " @ " $l ", " $(sed -n "${l}p" $f)
for color in neutral positive urge warning info critical; do
sed -i "${l},${l}s/~${color}/~${color} dark:~d_${color}/g" $f
done
fi
#else
#echo "FIX: querySelector found, but color tag wasn't in it: " $line
fi
fi
fi
done
done

3
scripts/variants/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/hrfee/jfa-go/scripts/variants
go 1.25.4

146
scripts/variants/main.go Normal file
View File

@@ -0,0 +1,146 @@
// Package variants provides a script to comb through typescript/javascript files and
// find (most) instances of a a17t color (~neutral...) being used without
// an accompanying dark version (dark:~d_neutral...) and insert one.
// Not fully feature-matched with the old bash version, only matching classList.add/remove and class="...",
// but doesn't break on multi line function params, and is probably much faster.
package main
import (
"bytes"
"flag"
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"regexp"
)
var classList = func() *regexp.Regexp {
classList, err := regexp.Compile(`classList\.(add|remove)(\((?:[^()]*|\((?:[^()]*|\([^()]*\))\))*\))`)
if err != nil {
panic(err)
}
return classList
}()
var color = func() *regexp.Regexp {
color, err := regexp.Compile(`\~(neutral|positive|warning|critical|info|urge|gray)`)
if err != nil {
panic(err)
}
return color
}()
var quotedColor = func() *regexp.Regexp {
quotedColor, err := regexp.Compile(`("|'|\x60)\~(neutral|positive|warning|critical|info|urge|gray)("|'|\x60)`)
if err != nil {
panic(err)
}
return quotedColor
}()
var htmlClassList = func() *regexp.Regexp {
htmlClassList, err := regexp.Compile(`class="[^"]*\~(neutral|positive|warning|critical|info|urge|gray)[^"]*"`)
if err != nil {
panic(err)
}
return htmlClassList
}()
func ParseDir(in, out string) error {
err := filepath.WalkDir(in, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
info, err := d.Info()
if err != nil {
return err
}
perm := info.Mode()
rel, err := filepath.Rel(in, path)
outFile := filepath.Join(out, rel)
if d.IsDir() {
return os.MkdirAll(outFile, perm)
}
log.Printf("\"%s\" => \"%s\"\n", path, outFile)
if err != nil {
return err
}
return ParseFile(path, outFile, &perm)
})
return err
}
func ParseFile(in, out string, perm *fs.FileMode) error {
file, err := os.ReadFile(in)
if err != nil {
return err
}
if perm == nil {
f, err := os.Stat(in)
if err != nil {
return err
}
p := f.Mode()
perm = &p
}
outText := classList.ReplaceAllFunc(file, func(match []byte) []byte {
if bytes.Contains(match, []byte("dark:~d_")) {
log.Printf("Skipping pre-set dark colour: `%s`\n", string(match))
return match
}
submatches := quotedColor.FindSubmatch(match)
if len(submatches) == 0 {
return match
}
quote := string(submatches[1])
color := string(submatches[2])
fmt.Printf("Matched quote %s, color %s\n", quote, color)
newVal := bytes.Replace(match, []byte(quote+"~"+color+quote), []byte(quote+"~"+color+quote+", "+quote+"dark:~d_"+color+quote), 1)
fmt.Printf("`%s` => `%s`\n", string(match), string(newVal))
return newVal
})
outText = htmlClassList.ReplaceAllFunc(outText, func(match []byte) []byte {
if bytes.Contains(match, []byte("dark:~d_")) {
log.Printf("Skipping pre-set dark colour: `%s`\n", string(match))
return match
}
// Sucks we can't get a submatch from ReplaceAllFunc
submatches := color.FindSubmatch(match)
if len(submatches) == 0 {
return match
}
c := submatches[1]
newVal := bytes.Replace(match, []byte("~"+string(c)), []byte("~"+string(c)+" dark:~d_"+string(c)), 1)
fmt.Printf("`%s` => `%s`\n", string(match), string(newVal))
return newVal
})
err = os.WriteFile(out, outText, *perm)
return err
}
func main() {
var inFile, inDir, out string
flag.StringVar(&inFile, "file", "", "Input of an individual file.")
flag.StringVar(&inDir, "dir", "", "Input of a whole directory.")
flag.StringVar(&out, "out", "", "Output filepath/directory, depending on if -file or -dir passed.")
flag.Parse()
if out == "" {
flag.PrintDefaults()
os.Exit(1)
}
var err error
if inFile != "" {
err = ParseFile(inFile, out, nil)
} else if inDir != "" {
err = ParseDir(inDir, out)
} else {
flag.PrintDefaults()
os.Exit(1)
}
if err != nil {
log.Fatalf("failed: %v\n", err)
os.Exit(1)
}
}

View File

@@ -472,13 +472,12 @@ export function throttle(callback: () => void, limitMilliseconds: number): () =>
export function SetupCopyButton( export function SetupCopyButton(
button: HTMLButtonElement, button: HTMLButtonElement,
text: string | (() => string), text: string | (() => string),
baseClass?: string, baseClasses?: string[],
notif?: string, notif?: string,
) { ) {
if (!notif) notif = window.lang.strings("copied"); if (!notif) notif = window.lang.strings("copied");
if (!baseClass) baseClass = "~info"; if (!baseClasses || baseClasses.length == 0) baseClasses = ["~info", "dark:~d_info"];
// script will probably turn this into multiple // script will probably turn this into multiple
const baseClasses = baseClass.split(" ");
button.type = "button"; button.type = "button";
button.classList.add("button", ...baseClasses, "@low", "p-1"); button.classList.add("button", ...baseClasses, "@low", "p-1");
button.title = window.lang.strings("copy"); button.title = window.lang.strings("copy");
@@ -505,8 +504,8 @@ export function SetupCopyButton(
}; };
} }
export function CopyButton(text: string | (() => string), baseClass?: string, notif?: string): HTMLButtonElement { export function CopyButton(text: string | (() => string), baseClasses?: string[], notif?: string): HTMLButtonElement {
const button = document.createElement("button"); const button = document.createElement("button");
SetupCopyButton(button, text, baseClass, notif); SetupCopyButton(button, text, baseClasses, notif);
return button; return button;
} }