forked from tangled.org/core
this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+256 -11
appview
notify
pages
templates
posthog
strings
contrib
knotserver
+18
appview/notify/merged_notifier.go
···
notifier.UpdateProfile(ctx, profile)
}
}
+
+
func (m *mergedNotifier) NewString(ctx context.Context, string *db.String) {
+
for _, notifier := range m.notifiers {
+
notifier.NewString(ctx, string)
+
}
+
}
+
+
func (m *mergedNotifier) EditString(ctx context.Context, string *db.String) {
+
for _, notifier := range m.notifiers {
+
notifier.EditString(ctx, string)
+
}
+
}
+
+
func (m *mergedNotifier) DeleteString(ctx context.Context, did, rkey string) {
+
for _, notifier := range m.notifiers {
+
notifier.DeleteString(ctx, did, rkey)
+
}
+
}
+8
appview/notify/notifier.go
···
NewPullComment(ctx context.Context, comment *db.PullComment)
UpdateProfile(ctx context.Context, profile *db.Profile)
+
+
NewString(ctx context.Context, s *db.String)
+
EditString(ctx context.Context, s *db.String)
+
DeleteString(ctx context.Context, did, rkey string)
}
// BaseNotifier is a listener that does nothing
···
func (m *BaseNotifier) NewPullComment(ctx context.Context, comment *db.PullComment) {}
func (m *BaseNotifier) UpdateProfile(ctx context.Context, profile *db.Profile) {}
+
+
func (m *BaseNotifier) NewString(ctx context.Context, s *db.String) {}
+
func (m *BaseNotifier) EditString(ctx context.Context, s *db.String) {}
+
func (m *BaseNotifier) DeleteString(ctx context.Context, did, rkey string) {}
+90
appview/pages/templates/fragments/multiline-select.html
···
+
{{ define "fragments/multiline-select" }}
+
<script>
+
function highlight(scroll = false) {
+
document.querySelectorAll(".hl").forEach(el => {
+
el.classList.remove("hl");
+
});
+
+
const hash = window.location.hash;
+
if (!hash || !hash.startsWith("#L")) {
+
return;
+
}
+
+
const rangeStr = hash.substring(2);
+
const parts = rangeStr.split("-");
+
let startLine, endLine;
+
+
if (parts.length === 2) {
+
startLine = parseInt(parts[0], 10);
+
endLine = parseInt(parts[1], 10);
+
} else {
+
startLine = parseInt(parts[0], 10);
+
endLine = startLine;
+
}
+
+
if (isNaN(startLine) || isNaN(endLine)) {
+
console.log("nan");
+
console.log(startLine);
+
console.log(endLine);
+
return;
+
}
+
+
let target = null;
+
+
for (let i = startLine; i<= endLine; i++) {
+
const idEl = document.getElementById(`L${i}`);
+
if (idEl) {
+
const el = idEl.closest(".line");
+
if (el) {
+
el.classList.add("hl");
+
target = el;
+
}
+
}
+
}
+
+
if (scroll && target) {
+
target.scrollIntoView({
+
behavior: "smooth",
+
block: "center",
+
});
+
}
+
}
+
+
document.addEventListener("DOMContentLoaded", () => {
+
console.log("DOMContentLoaded");
+
highlight(true);
+
});
+
window.addEventListener("hashchange", () => {
+
console.log("hashchange");
+
highlight();
+
});
+
window.addEventListener("popstate", () => {
+
console.log("popstate");
+
highlight();
+
});
+
+
const lineNumbers = document.querySelectorAll('a[href^="#L"');
+
let startLine = null;
+
+
lineNumbers.forEach(el => {
+
el.addEventListener("click", (event) => {
+
event.preventDefault();
+
const currentLine = parseInt(el.href.split("#L")[1]);
+
+
if (event.shiftKey && startLine !== null) {
+
const endLine = currentLine;
+
const min = Math.min(startLine, endLine);
+
const max = Math.max(startLine, endLine);
+
const newHash = `#L${min}-${max}`;
+
history.pushState(null, '', newHash);
+
} else {
+
const newHash = `#L${currentLine}`;
+
history.pushState(null, '', newHash);
+
startLine = currentLine;
+
}
+
+
highlight();
+
});
+
});
+
</script>
+
{{ end }}
+7 -2
appview/pages/templates/layouts/profilebase.html
···
{{ define "content" }}
{{ template "profileTabs" . }}
-
<section class="bg-white dark:bg-gray-800 p-6 rounded w-full dark:text-white drop-shadow-sm">
+
<section class="bg-white dark:bg-gray-800 px-2 py-6 md:p-6 rounded w-full dark:text-white drop-shadow-sm">
<div class="grid grid-cols-1 md:grid-cols-11 gap-4">
-
<div class="md:col-span-3 order-1 md:order-1">
+
{{ $style := "hidden md:block md:col-span-3" }}
+
{{ if eq $.Active "overview" }}
+
{{ $style = "md:col-span-3" }}
+
{{ end }}
+
<div class="{{ $style }} order-1 order-1">
<div class="flex flex-col gap-4">
{{ template "user/fragments/profileCard" .Card }}
{{ block "punchcard" .Card.Punchcard }} {{ end }}
</div>
</div>
+
{{ block "profileContent" . }} {{ end }}
</div>
</section>
+1
appview/pages/templates/repo/blob.html
···
{{ end }}
</div>
{{ end }}
+
{{ template "fragments/multiline-select" }}
{{ end }}
+3 -2
appview/pages/templates/strings/string.html
···
hx-boost="true"
href="/strings/{{ .String.Did }}/{{ .String.Rkey }}/edit">
{{ i "pencil" "size-4" }}
-
<span class="hidden md:inline">edit</span>
+
<span class="hidden md:inline">edit</span>
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
</a>
<button
···
hx-confirm="Are you sure you want to delete the string `{{ .String.Filename }}`?"
>
{{ i "trash-2" "size-4" }}
-
<span class="hidden md:inline">delete</span>
+
<span class="hidden md:inline">delete</span>
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
</button>
</div>
···
<div id="blob-contents" class="whitespace-pre peer-target:bg-yellow-200 dark:peer-target:bg-yellow-900">{{ .String.Contents | escapeHtml }}</div>
{{ end }}
</div>
+
{{ template "fragments/multiline-select" }}
</section>
{{ end }}
+33
appview/posthog/notifier.go
···
log.Println("failed to enqueue posthog event:", err)
}
}
+
+
func (n *posthogNotifier) DeleteString(ctx context.Context, did, rkey string) {
+
err := n.client.Enqueue(posthog.Capture{
+
DistinctId: did,
+
Event: "delete_string",
+
Properties: posthog.Properties{"rkey": rkey},
+
})
+
if err != nil {
+
log.Println("failed to enqueue posthog event:", err)
+
}
+
}
+
+
func (n *posthogNotifier) EditString(ctx context.Context, string *db.String) {
+
err := n.client.Enqueue(posthog.Capture{
+
DistinctId: string.Did.String(),
+
Event: "edit_string",
+
Properties: posthog.Properties{"rkey": string.Rkey},
+
})
+
if err != nil {
+
log.Println("failed to enqueue posthog event:", err)
+
}
+
}
+
+
func (n *posthogNotifier) CreateString(ctx context.Context, string *db.String) {
+
err := n.client.Enqueue(posthog.Capture{
+
DistinctId: string.Did.String(),
+
Event: "create_string",
+
Properties: posthog.Properties{"rkey": string.Rkey},
+
})
+
if err != nil {
+
log.Println("failed to enqueue posthog event:", err)
+
}
+
}
+8
appview/strings/strings.go
···
"tangled.sh/tangled.sh/core/appview/config"
"tangled.sh/tangled.sh/core/appview/db"
"tangled.sh/tangled.sh/core/appview/middleware"
+
"tangled.sh/tangled.sh/core/appview/notify"
"tangled.sh/tangled.sh/core/appview/oauth"
"tangled.sh/tangled.sh/core/appview/pages"
"tangled.sh/tangled.sh/core/appview/pages/markup"
···
IdResolver *idresolver.Resolver
Logger *slog.Logger
Knotstream *eventconsumer.Consumer
+
Notifier notify.Notifier
}
func (s *Strings) Router(mw *middleware.Middleware) http.Handler {
···
return
}
+
s.Notifier.EditString(r.Context(), &entry)
+
// if that went okay, redir to the string
s.Pages.HxRedirect(w, "/strings/"+user.Handle+"/"+entry.Rkey)
}
···
return
}
+
s.Notifier.NewString(r.Context(), &string)
+
// successful
s.Pages.HxRedirect(w, "/strings/"+user.Handle+"/"+string.Rkey)
}
···
fail("Failed to delete string.", err)
return
}
+
+
s.Notifier.DeleteString(r.Context(), user.Did, rkey)
s.Pages.HxRedirect(w, "/strings/"+user.Handle)
}
+44
contrib/Tiltfile
···
+
common_env = {
+
"TANGLED_VM_SPINDLE_OWNER": os.getenv("TANGLED_VM_SPINDLE_OWNER", default=""),
+
"TANGLED_VM_KNOT_OWNER": os.getenv("TANGLED_VM_KNOT_OWNER", default=""),
+
"TANGLED_DB_PATH": os.getenv("TANGLED_DB_PATH", default="dev.db"),
+
"TANGLED_DEV": os.getenv("TANGLED_DEV", default="true"),
+
}
+
+
nix_globs = ["nix/**", "flake.nix", "flake.lock"]
+
+
local_resource(
+
name="appview",
+
serve_cmd="nix run .#watch-appview",
+
serve_dir="..",
+
deps=nix_globs,
+
env=common_env,
+
allow_parallel=True,
+
)
+
+
local_resource(
+
name="tailwind",
+
serve_cmd="nix run .#watch-tailwind",
+
serve_dir="..",
+
deps=nix_globs,
+
env=common_env,
+
allow_parallel=True,
+
)
+
+
local_resource(
+
name="redis",
+
serve_cmd="redis-server",
+
serve_dir="..",
+
deps=nix_globs,
+
env=common_env,
+
allow_parallel=True,
+
)
+
+
local_resource(
+
name="vm",
+
serve_cmd="nix run --impure .#vm",
+
serve_dir="..",
+
deps=nix_globs,
+
env=common_env,
+
allow_parallel=True,
+
)
+16
default.nix
···
+
# Default setup from https://git.lix.systems/lix-project/flake-compat
+
let
+
lockFile = builtins.fromJSON (builtins.readFile ./flake.lock);
+
flake-compat-node = lockFile.nodes.${lockFile.nodes.root.inputs.flake-compat};
+
flake-compat = builtins.fetchTarball {
+
inherit (flake-compat-node.locked) url;
+
sha256 = flake-compat-node.locked.narHash;
+
};
+
+
flake = (
+
import flake-compat {
+
src = ./.;
+
}
+
);
+
in
+
flake.defaultNix
+15
flake.lock
···
{
"nodes": {
+
"flake-compat": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1751685974,
+
"narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=",
+
"rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1",
+
"type": "tarball",
+
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz?rev=549f2762aebeff29a2e5ece7a7dc0f955281a1d1"
+
},
+
"original": {
+
"type": "tarball",
+
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
+
}
+
},
"flake-utils": {
"inputs": {
"systems": "systems"
···
},
"root": {
"inputs": {
+
"flake-compat": "flake-compat",
"gomod2nix": "gomod2nix",
"htmx-src": "htmx-src",
"htmx-ws-src": "htmx-ws-src",
+7 -1
flake.nix
···
url = "github:nix-community/gomod2nix";
inputs.nixpkgs.follows = "nixpkgs";
};
+
flake-compat = {
+
url = "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz";
+
flake = false;
+
};
indigo = {
url = "github:oppiliappan/indigo";
flake = false;
···
inter-fonts-src,
sqlite-lib-src,
ibm-plex-mono-src,
+
...
}: let
supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
···
nativeBuildInputs = [
pkgs.go
pkgs.air
+
pkgs.tilt
pkgs.gopls
pkgs.httpie
pkgs.litecli
···
tailwind-watcher =
pkgs.writeShellScriptBin "run"
''
-
${pkgs.tailwindcss}/bin/tailwindcss -w -i input.css -o ./appview/pages/static/tw.css
+
${pkgs.tailwindcss}/bin/tailwindcss --watch=always -i input.css -o ./appview/pages/static/tw.css
'';
in {
fmt = {
+2 -5
input.css
···
}
/* LineHighlight */
.chroma .hl {
-
background-color: #bcc0cc;
+
@apply bg-amber-400/30 dark:bg-amber-500/20;
}
+
/* LineNumbersTable */
.chroma .lnt {
white-space: pre;
···
text-decoration: underline;
}
}
-
-
.chroma .line:has(.ln:target) {
-
@apply bg-amber-400/30 dark:bg-amber-500/20;
-
}
+4 -1
knotserver/git/language.go
···
import (
"context"
"path"
+
"strings"
"github.com/go-enry/go-enry/v2"
"github.com/go-git/go-git/v5/plumbing/object"
···
return nil
}
-
if enry.IsGenerated(filepath, content) {
+
if enry.IsGenerated(filepath, content) ||
+
enry.IsBinary(content) ||
+
strings.HasSuffix(filepath, "bun.lock") {
return nil
}