+1263
-170
api/tangled/cbor_gen.go
+1263
-170
api/tangled/cbor_gen.go
······························
·····················+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.label.definition"))); err != nil {+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.label.definition"))); err != nil {+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.label.op"))); err != nil {+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("performedAt"))); err != nil {+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.PerformedAt))); err != nil {·········
+43
api/tangled/labeldefinition.go
+43
api/tangled/labeldefinition.go
···
···+LexiconTypeID string `json:"$type,const=sh.tangled.label.definition" cborgen:"$type,const=sh.tangled.label.definition"`+// color: The hex value for the background color for the label. Appviews may choose to respect this.+// multiple: Whether this label can be repeated for a given entity, eg.: [reviewer:foo, reviewer:bar]+// scope: The area of the repo this label may apply to, eg.: sh.tangled.repo.issue. Appviews may choose to respect this.+LexiconTypeID string `json:"$type,const=sh.tangled.label.definition" cborgen:"$type,const=sh.tangled.label.definition"`
+34
api/tangled/labelop.go
+34
api/tangled/labelop.go
···
···+LexiconTypeID string `json:"$type,const=sh.tangled.label.op" cborgen:"$type,const=sh.tangled.label.op"`+// subject: The subject (task, pull or discussion) of this label. Appviews may apply a `scope` check and refuse this op.+// value: Stringified value of the label. This is first unstringed by appviews and then interpreted as a concrete value.
+3
-2
api/tangled/tangledrepo.go
+3
-2
api/tangled/tangledrepo.go
···
···
+65
-1
appview/db/db.go
+65
-1
appview/db/db.go
······
···+at_uri text generated always as ('at://' || did || '/' || 'sh.tangled.label.definition' || '/' || rkey) stored,+-- ops are flattened, a record may contain several additions and deletions, but the table will include one row per add/del+at_uri text generated always as ('at://' || did || '/' || 'sh.tangled.label.op' || '/' || rkey) stored,+-- we need two time values: performed is declared by the user, indexed is calculated by the av···
+13
-1
appview/db/issues.go
+13
-1
appview/db/issues.go
······
······
+721
appview/db/label.go
+721
appview/db/label.go
···
···+func LabelDefinitionFromRecord(did, rkey string, record tangled.LabelDefinition) LabelDefinition {+} else if _, exists = valueSet[op.OperandValue]; !exists { // if value DNE, then deletion is no-op
+138
-31
appview/db/repos.go
+138
-31
appview/db/repos.go
············-if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &description, &spindle, &repo.Rkey); err != nil {···
···············
+26
appview/issues/issues.go
+26
appview/issues/issues.go
·········
·········
+1
-1
appview/issues/router.go
+1
-1
appview/issues/router.go
+240
appview/labels/labels.go
+240
appview/labels/labels.go
···
···+l.logger.Info("performed label op", "did", o.Did, "rkey", o.Rkey, "kind", o.Operation, "subjcet", o.Subject, "key", o.OperandKey)
+38
-37
appview/middleware/middleware.go
+38
-37
appview/middleware/middleware.go
······
······
+3
appview/pages/funcmap.go
+3
appview/pages/funcmap.go
+8
-6
appview/pages/pages.go
+8
-6
appview/pages/pages.go
·········
·········
+2
-2
appview/pages/repoinfo/repoinfo.go
+2
-2
appview/pages/repoinfo/repoinfo.go
+8
appview/pages/templates/labels/fragments/label.html
+8
appview/pages/templates/labels/fragments/label.html
···
···+<span class="flex items-center gap-2 font-normal normal-case rounded py-1 px-2 border border-gray-200 dark:border-gray-700 text-sm">
+6
appview/pages/templates/labels/fragments/labelDef.html
+6
appview/pages/templates/labels/fragments/labelDef.html
+119
appview/pages/templates/repo/fragments/addLabelModal.html
+119
appview/pages/templates/repo/fragments/addLabelModal.html
···
···+class="btn w-1/2 flex items-center gap-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"+<select name="operand-val" class="w-full p-1 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600">+<select name="operand-val" class="w-full p-1 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600">
+6
appview/pages/templates/repo/fragments/colorBall.html
+6
appview/pages/templates/repo/fragments/colorBall.html
···
-6
appview/pages/templates/repo/fragments/languageBall.html
-6
appview/pages/templates/repo/fragments/languageBall.html
···-style="background: radial-gradient(circle at 35% 35%, color-mix(in srgb, {{ langColor . }} 70%, white), {{ langColor . }} 30%, color-mix(in srgb, {{ langColor . }} 85%, black));"
···
+1
-1
appview/pages/templates/repo/index.html
+1
-1
appview/pages/templates/repo/index.html
···
+28
-2
appview/pages/templates/repo/issues/issue.html
+28
-2
appview/pages/templates/repo/issues/issue.html
·········
·········+class="bg-white w-full sm:w-96 dark:bg-gray-800 p-6 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50">+{{ template "repo/fragments/addLabelModal" (dict "root" $ "subject" $.Issue.AtUri.String "state" $.Issue.Labels) }}
+13
-3
appview/pages/templates/repo/issues/issues.html
+13
-3
appview/pages/templates/repo/issues/issues.html
······<a href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a>
······<a href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a>
+104
appview/pages/templates/repo/settings/fragments/addLabelDefModal.html
+104
appview/pages/templates/repo/settings/fragments/addLabelDefModal.html
···
···+<p class="text-gray-500 dark:text-gray-400">Labels can have a name and a value. Set the value type to "none" to create a simple label.</p>+<input class="w-full" type="text" id="label-name" name="name" required placeholder="improvement"/>+<select id="value-type" name="valueType" class="w-full p-3 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600">+<span class="group-open:hidden inline text-gray-500 dark:text-gray-400">{{ i "square-plus" "w-4 h-4" }}</span>+<span class="hidden group-open:inline text-gray-500 dark:text-gray-400">{{ i "square-minus" "w-4 h-4" }}</span>+<input type="text" id="enumValues" name="enumValues" placeholder="value1, value2, value3" class="w-full"/>+<p class="text-sm text-gray-400 dark:text-gray-500 mt-1">Enter comma-separated list of permitted values.</p>+<select id="scope" name="scope" class="w-full p-3 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600">+{{ $colors := list "#ef4444" "#3b82f6" "#10b981" "#f59e0b" "#8b5cf6" "#ec4899" "#06b6d4" "#64748b" }}+<input type="radio" name="color" value="{{ $color }}" class="sr-only peer" {{ if eq $i 0 }} checked {{ end }}>+{{ template "repo/fragments/colorBall" (dict "color" $color "classes" "size-4 peer-checked:size-8 transition-all") }}+class="btn w-1/2 flex items-center gap-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
+28
appview/pages/templates/repo/settings/fragments/labelListing.html
+28
appview/pages/templates/repo/settings/fragments/labelListing.html
···
···+class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
+42
appview/pages/templates/repo/settings/general.html
+42
appview/pages/templates/repo/settings/general.html
·········
······+class="bg-white w-full sm:w-96 dark:bg-gray-800 p-6 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50">+<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full">···
+1
-1
appview/pages/templates/repo/settings/pipelines.html
+1
-1
appview/pages/templates/repo/settings/pipelines.html
+1
-1
appview/pages/templates/user/fragments/repoCard.html
+1
-1
appview/pages/templates/user/fragments/repoCard.html
+1
-1
appview/pages/templates/user/overview.html
+1
-1
appview/pages/templates/user/overview.html
+6
-2
appview/pulls/pulls.go
+6
-2
appview/pulls/pulls.go
···
+302
-46
appview/repo/repo.go
+302
-46
appview/repo/repo.go
···············// SwapRecord is optional and should happen automagically, but given that it does not, we have to perform two requests······removingSpindle := newSpindle == "[[none]]" // see pages/templates/repo/settings/pipelines.html for more info on why we use this value·····················
···············// SwapRecord is optional and should happen automagically, but given that it does not, we have to perform two requests······removingSpindle := newSpindle == "[[none]]" // see pages/templates/repo/settings/pipelines.html for more info on why we use this value······+ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoNSID, newRepo.Did, newRepo.Rkey)+ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoNSID, newRepo.Did, newRepo.Rkey)···············
+2
appview/repo/router.go
+2
appview/repo/router.go
···
···
+8
-4
appview/state/router.go
+8
-4
appview/state/router.go
············-repo := repo.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.notifier, s.enforcer, logger)pipes := pipelines.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.enforcer)
············+repo := repo.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.notifier, s.enforcer, logger, s.validator)pipes := pipelines.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.enforcer)+ls := labels.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.enforcer)
+9
-8
appview/state/state.go
+9
-8
appview/state/state.go
···s.pages.Notice(w, "repo", fmt.Sprintf("You already have a repository by this name on %s", existingRepo.Knot))······
···s.pages.Notice(w, "repo", fmt.Sprintf("You already have a repository by this name on %s", existingRepo.Knot))······
-6
appview/strings/strings.go
-6
appview/strings/strings.go
······
······
+77
appview/validator/label.go
+77
appview/validator/label.go
···
···+return fmt.Errorf("label name contains invalid characters (use only letters, numbers, hyphens, and underscores)")+return fmt.Errorf("invalid value type: %q (must be one of: null, boolean, integer, string)", label.ValueType)+color = fmt.Sprintf("#%c%c%c%c%c%c", color[1], color[1], color[2], color[2], color[3], color[3])
+5
-1
cmd/gen.go
+5
-1
cmd/gen.go
···
···
+2
-2
knotserver/ingester.go
+2
-2
knotserver/ingester.go
···return fmt.Errorf("rejected pull record: not this knot, %s != %s", repo.Knot, h.c.Server.Hostname)···
···return fmt.Errorf("rejected pull record: not this knot, %s != %s", repo.Knot, h.c.Server.Hostname)···
-36
knotserver/util.go
-36
knotserver/util.go
···
+90
lexicons/label/definition.json
+90
lexicons/label/definition.json
···
···+"description": "The type definition of this label. Appviews may allow sorting for certain types."+"description": "The area of the repo this label may apply to, eg.: sh.tangled.repo.issue. Appviews may choose to respect this."+"description": "The hex value for the background color for the label. Appviews may choose to respect this."+"description": "Whether this label can be repeated for a given entity, eg.: [reviewer:foo, reviewer:bar]"
+64
lexicons/label/op.json
+64
lexicons/label/op.json
···
···+"description": "The subject (task, pull or discussion) of this label. Appviews may apply a `scope` check and refuse this op."+"description": "Stringified value of the label. This is first unstringed by appviews and then interpreted as a concrete value."
+8
-5
lexicons/repo/repo.json
+8
-5
lexicons/repo/repo.json
······
······
+6
-6
spindle/ingester.go
+6
-6
spindle/ingester.go
······-l.Info("different spindle configured", "did", record.Owner, "name", record.Name, "spindle", *record.Spindle, "domain", domain)
······+l.Info("different spindle configured", "name", record.Name, "spindle", *record.Spindle, "domain", domain)
+1
-1
spindle/xrpc/add_secret.go
+1
-1
spindle/xrpc/add_secret.go
+1
-1
spindle/xrpc/list_secrets.go
+1
-1
spindle/xrpc/list_secrets.go
+1
-1
spindle/xrpc/remove_secret.go
+1
-1
spindle/xrpc/remove_secret.go