Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
edc4ddd
add hierarchical labels
DrEverr Feb 18, 2025
d65233b
refactor label color logic
DrEverr Feb 19, 2025
9035ca8
display flat label structure
DrEverr Feb 19, 2025
cd6353c
sort labels in LabelsFilter component
DrEverr Feb 19, 2025
fec5017
updating local labels at the end, for proper toggle groups
DrEverr Feb 19, 2025
c48c3c0
[85] Add version name to location hash url (#178)
DrEverr Feb 20, 2025
7241f59
Update readme (#172)
tomusdrw Feb 20, 2025
57265b5
Avoid removing commonPath if there is only one file. (#171)
tomusdrw Feb 20, 2025
69d78af
refactor toggleLabel to search newLabel by hooks
DrEverr Feb 20, 2025
f8c527f
[159] mass remove notes (#175)
DrEverr Feb 21, 2025
9e8787e
add hierarchical labels
DrEverr Feb 18, 2025
b4eb7ca
refactor label color logic
DrEverr Feb 19, 2025
6624ff9
display flat label structure
DrEverr Feb 19, 2025
bc3dd75
sort labels in LabelsFilter component
DrEverr Feb 19, 2025
ef4954d
updating local labels at the end, for proper toggle groups
DrEverr Feb 19, 2025
15995eb
refactor toggleLabel to search newLabel by hooks
DrEverr Feb 20, 2025
491682d
Merge branch '147-hierarchical-labels' of github.com:DrEverr/graypape…
DrEverr Feb 21, 2025
e5bd5ed
Implement hierarchical label structure in LabelsFilter component
DrEverr Feb 25, 2025
118ae21
refactor
DrEverr Feb 25, 2025
438d324
fix label filtering
DrEverr Feb 25, 2025
646acdf
refactor
DrEverr Feb 25, 2025
cfff3e1
fix delete all
DrEverr Feb 25, 2025
e7ffc31
deleted comment
DrEverr Feb 26, 2025
efad22d
refactor
DrEverr Feb 26, 2025
5258c05
refactor
DrEverr Feb 26, 2025
d58b7e7
refactor
DrEverr Feb 25, 2025
7a240e0
Merge branch '147-hierarchical-labels' of github.com:DrEverr/graypape…
DrEverr Feb 26, 2025
a6ee5bd
upped contrast to match with the downloaded pdf
DrEverr Feb 26, 2025
36d49ea
revert last commit
DrEverr Feb 26, 2025
184fc94
refactor
DrEverr Feb 28, 2025
c51989f
file name change
DrEverr Feb 28, 2025
8c10280
refactor notes import/export functions to enhance label filtering and…
DrEverr Feb 28, 2025
7241239
fix: standardize string delimiter
DrEverr Feb 28, 2025
d2313f8
shorter and cleaner traverseAndCollectNotes
DrEverr Mar 2, 2025
fffa90b
fix
DrEverr Mar 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,19 @@

A tool to help with reading and analyzing the Gray Paper.

# Updating available versions of the gray paper.
# Related repositories
- Gray Paper Reader community notes [graypaper-notes](https://github.com/fluffylabs/graypaper-notes).
- Gray Paper Archive [graypaper-archive](https://github.com/fluffylabs/graypaper-archive).

# Tooling

- [matrix-bot](./tools/matrix-bot) - Listens to Matrix channel messages and
collects the ones containing GP Reader links. These messages can later be
turned into notes JSON file.
- [links-check](./tools/links-check) - Scan a set of files for GP Reader links
and check their versions or generate notes JSON file.

# Updating available versions of the Gray Paper

Gray Paper versions are stored in [a separate repository](https://github.com/fluffylabs/graypaper-archive),
added as a git submodule.
Expand Down
22 changes: 22 additions & 0 deletions src/components/Label/Label.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,25 @@
border-radius: 6px;
color: #eee;
}

.label-tree {
display: flex;
flex-direction: column;
align-items: left;
color: #eee;
}

.label-tree-header {
text-align: left;
white-space: nowrap;
border-radius: 6px;
padding: 1px 5px;
margin: 2px;
color: #eee;
}

.label-tree-children {
display: flex;
justify-content: left;
gap: 10px;
}
123 changes: 122 additions & 1 deletion src/components/Label/Label.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,73 @@
import { LABEL_LOCAL, LABEL_REMOTE } from "../NotesProvider/consts/labels";
import { type IDecoratedNote, NoteSource } from "../NotesProvider/types/DecoratedNote";
import type { IStorageNote } from "../NotesProvider/types/StorageNote";
import "./Label.css";
import { useMemo } from "react";

export type ILabel = {
label: string;
isActive: boolean;
parent: ILabel | null;
children: ILabel[];
notes: IDecoratedNote[];
};

export function generateLabelTree(notes: IDecoratedNote[]): ILabel[] {
const local: ILabel = {
label: LABEL_LOCAL,
isActive: true,
parent: null,
children: [],
notes: [],
};
const remote: ILabel = {
label: LABEL_REMOTE,
isActive: true,
parent: null,
children: [],
notes: [],
};

function addToTree(labelPath: string[], note: IDecoratedNote, currentNode: ILabel) {
if (labelPath.length === 0) {
currentNode.notes.push(note);
return;
}

const [head, ...rest] = labelPath;
let childNode = currentNode.children.find((child) => child.label === head);
if (!childNode) {
childNode = {
label: head,
isActive: true,
parent: currentNode,
children: [],
notes: [],
};
currentNode.children.push(childNode);
}
addToTree(rest, note, childNode);
}

for (const note of notes) {
for (const label of note.original.labels) {
const parts = label.trim().split("/");
const root = note.source === NoteSource.Local ? local : remote;
if (parts.length === 1) {
if (parts[0] === LABEL_LOCAL || parts[0] === LABEL_REMOTE) {
root.notes.push(note);
continue;
}
}
addToTree(parts, note, root);
Comment on lines +56 to +62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (parts.length === 1) {
if (parts[0] === LABEL_LOCAL || parts[0] === LABEL_REMOTE) {
root.notes.push(note);
continue;
}
}
addToTree(parts, note, root);
// if the label starts with `local` or `remote` just trim the first part.
if (parts[0] === LABEL_LOCAL || parts[0] === LABEL_REMOTE) {
parts.shift();
}
addToTree(parts, note, root);

I think this is more correct way to handle that case. I wonder however where does this special casing come from? Do we currently have notes with explicit local or remote label within them? I think it's totally fine if they were displayed as local/local - the user may remove that label if it was added there unnecessarily.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our other mechanism continuously adds the ‘local’ label, resulting in ‘local/local’ on each page refresh.

}
}

return [local, remote];
}

export function Label({ label, prefix = "" }: { label: string; prefix?: string }) {
const backgroundColor = useMemo(() => labelToColor(label), [label]);
const backgroundColor = useMemo(() => labelToColor(label.split("/").pop() || ""), [label]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const backgroundColor = useMemo(() => labelToColor(label.split("/").pop() || ""), [label]);
const backgroundColor = useMemo(() => labelToColor(label), [label]);

Background color should depend on the whole "path" (i.e. remote label v0.4.5 should have different color than a local one with the same name)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it makes labels in notes and labels on the tree displays differently (w/ different color)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we should have full path in notes then.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

full path displayed on tree?
eg.
local
local/a
local/a/b
local/a/c

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I mean is that we should always use full path for color and display the text-label depending on the context:

  1. In the tree we just display shorter label (since it's nested)
  2. In the note we display full label (perhaps without the source part)

return (
<span style={{ backgroundColor }} className="label">
{prefix} {label}
Expand Down Expand Up @@ -41,3 +106,59 @@ function hslToHex(h: number, s: number, lightness: number) {
};
return `#${f(0)}${f(8)}${f(4)}`;
}

export function filterNotesByLabels(
labels: ILabel[],
{ onlyInactive }: { onlyInactive: boolean } = { onlyInactive: true },
): IStorageNote[] {
return filterDecoratedNotesByLabels(labels, { hasAllLabels: true, onlyInactive: onlyInactive }).map(
(note) => note.original,
);
}

export function filterDecoratedNotesByLabels(
labels: ILabel[],
{ hasAllLabels, onlyInactive }: { hasAllLabels: boolean; onlyInactive: boolean } = {
hasAllLabels: true,
onlyInactive: false,
},
): IDecoratedNote[] {
function traverseAndCollectNotes(labels: ILabel[], notes: Set<IDecoratedNote>, isActive = true) {
for (const label of labels) {
if (label.isActive === isActive) {
for (const note of label.notes) {
notes.add(note);
}
}

if (label.children) {
traverseAndCollectNotes(label.children, notes, isActive);
}
}
return notes;
}

// return notes from inactive labels
if (onlyInactive) {
return Array.from(traverseAndCollectNotes(labels, new Set(), false));
}

const activeNotes = traverseAndCollectNotes(labels, new Set());
if (hasAllLabels) {
const inactiveNotes = traverseAndCollectNotes(labels, new Set(), false);
for (const note of inactiveNotes) {
activeNotes.delete(note);
}
}
return Array.from(activeNotes);
}

export function getFullLabelName(label: ILabel): string {
let parentLabel = label.parent;
let fullName = label.label;
while (parentLabel) {
fullName = `${parentLabel.label}/${fullName}`;
parentLabel = parentLabel.parent;
}
return fullName;
}
4 changes: 2 additions & 2 deletions src/components/LabelsFilter/LabelsFilter.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
margin: 15px 0;
}

.labels .label-link {
.label-link {
opacity: 50%;
cursor: pointer;
}

.labels .label-link.active {
.label-link.active {
opacity: 100%;
}

52 changes: 32 additions & 20 deletions src/components/LabelsFilter/LabelsFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,54 @@
import "./LabelsFilter.css";
import { type MouseEventHandler, useCallback } from "react";
import { Label } from "../Label/Label";
import type { ILabel } from "../NotesProvider/hooks/useLabels";
import { useState } from "react";
import { type ILabel, Label, getFullLabelName } from "../Label/Label";

export type LabelsFilterProps = {
labels: ILabel[];
onToggleLabel: (label: string) => void;
onToggleLabel: (label: ILabel) => void;
};

export function LabelsFilter({ labels, onToggleLabel }: LabelsFilterProps) {
export function LabelsFilterTree({ labels, onToggleLabel }: LabelsFilterProps) {
return (
<div className="labels filter">
<div className="label-tree-content">
{labels.map((label) => (
<LabelLink key={label.label} label={label} onToggleLabel={onToggleLabel} />
<LabelNode key={label.label} label={label} onToggleLabel={onToggleLabel} />
))}
</div>
);
}

type LabelLinkProps = {
label: ILabel;
prefix?: string;
onToggleLabel: LabelsFilterProps["onToggleLabel"];
};

function LabelLink({ label, onToggleLabel }: LabelLinkProps) {
const selectLabel = useCallback<MouseEventHandler>(
(e) => {
e.preventDefault();
onToggleLabel(label.label);
},
[label, onToggleLabel],
);

function LabelNode({ label, onToggleLabel }: LabelLinkProps) {
const [expanded, setExpanded] = useState(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const [expanded, setExpanded] = useState(true);
const expanded = label.isActive;

const hasChildren = Object.keys(label.children).length > 0;
const prefix = hasChildren ? (expanded ? "▼" : "▶") : label.isActive ? "⊙" : "∅";
const clazz = `label-link ${label.isActive ? "active" : ""}`;
const ico = label.isActive ? "⊙" : "∅";

return (
<a href="#" className={clazz} onClick={selectLabel}>
<Label label={label.label} prefix={ico} />
</a>
<div className="label-node">
<div
className="label-node-header"
onClick={() => onToggleLabel(label)}
onKeyUp={(e) => e.key === "Enter" && setExpanded(!expanded)}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onKeyUp={(e) => e.key === "Enter" && setExpanded(!expanded)}

that shouldn't be needed. The group should be expanded when it's active and folded when inactive.

tabIndex={0}
role="button"
>
<div className={clazz}>
<Label key={getFullLabelName(label)} label={label.label} prefix={prefix} />
</div>
</div>
{expanded && hasChildren && (
<div className="label-node-content">
{Object.values(label.children).map((child) => (
<LabelNode key={child.label} label={child} onToggleLabel={onToggleLabel} />
))}
</div>
)}
</div>
);
}
4 changes: 3 additions & 1 deletion src/components/LocationProvider/LocationProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export function LocationProvider({ children }: ILocationProviderProps) {
const version =
newParams?.version.substring(0, SHORT_COMMIT_HASH_LENGTH) ||
metadata.versions[metadata.latest]?.hash.substring(0, SHORT_COMMIT_HASH_LENGTH);
const versionName = newParams ? metadata.versions[newParams.version]?.name : undefined;

const stringifiedParams = [];

Expand All @@ -58,7 +59,8 @@ export function LocationProvider({ children }: ILocationProviderProps) {
].join("");
}

window.location.hash = `${SEGMENT_SEPARATOR}${stringifiedParams.join(SEGMENT_SEPARATOR)}`;
const newHash = `${SEGMENT_SEPARATOR}${stringifiedParams.join(SEGMENT_SEPARATOR)}`;
window.location.hash = versionName ? `${newHash}?v=${versionName}` : newHash;
},
[metadata],
);
Expand Down
5 changes: 2 additions & 3 deletions src/components/NoteManager/NoteManager.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useContext, useEffect, useState } from "react";
import "./NoteManager.css";
import { validateMath } from "../../utils/validateMath";
import { LabelsFilter } from "../LabelsFilter/LabelsFilter";
import { LabelsFilterTree } from "../LabelsFilter/LabelsFilter";
import { type ILocationContext, LocationContext } from "../LocationProvider/LocationProvider";
import { type INotesContext, NotesContext } from "../NotesProvider/NotesProvider";
import { LABEL_LOCAL } from "../NotesProvider/consts/labels";
Expand Down Expand Up @@ -56,7 +56,6 @@ function Notes() {
selectionStart: locationParams.selectionStart,
selectionEnd: locationParams.selectionEnd,
version: locationParams.version,
// TODO [ToDr] user defined labels?
labels: [LABEL_LOCAL],
};

Expand Down Expand Up @@ -89,7 +88,7 @@ function Notes() {
</button>
</div>

<LabelsFilter labels={labels} onToggleLabel={handleToggleLabel} />
<LabelsFilterTree labels={labels} onToggleLabel={handleToggleLabel} />
{notes.map((note) => (
<Note key={note.key} note={note} onEditNote={handleUpdateNote} onDeleteNote={handleDeleteNote} />
))}
Expand Down
Loading