Skip to content

Commit 4cd3b26

Browse files
authored
feat: added vocab search (#24)
1 parent 6c2cf9b commit 4cd3b26

21 files changed

+607
-13
lines changed

.changeset/happy-turkeys-knock.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"gregmat-buddy": minor
3+
---
4+
5+
added vocab search page

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
# GregMat Buddy
22

3-
🏗️ IN ACTIVE DEVELOPMENT
4-
53
Adds useful quality of life features to the GregMat and PrepSwift website.
64

75
## Features
86

97
- Adds progress stats for PrepSwift
8+
- Allows you to search through vocab mountain
9+
- Auto marks PrepSwift lectures as complete when the video ends
1010
- Remembers your playback speed on GregMat and PrepSwift
1111
- Remembers if you dismissed a banner at the top of the website.
1212
- Adds custom keybinds for vocab mountain
1313
- Moves to next word automatically after you mark it
1414
- Adds vim keybinds for cursor movement
15+
- Tweak your enabled features in the extension options page
1516

1617
## Install
1718

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"autoprefixer": "^10.4.20",
2525
"bits-ui": "^0.21.16",
2626
"clsx": "^2.1.1",
27+
"fast-glob": "^3.3.2",
2728
"svelte": "^4.2.19",
2829
"svelte-check": "^3.8.6",
2930
"tailwind-merge": "^2.5.4",
@@ -34,8 +35,9 @@
3435
"wxt": "^0.19.13"
3536
},
3637
"dependencies": {
38+
"@formkit/auto-animate": "^0.8.2",
3739
"@vimeo/player": "^2.24.0",
38-
"fast-glob": "^3.3.2",
39-
"lucide-svelte": "^0.453.0"
40+
"fuse.js": "^7.0.0",
41+
"lucide-svelte": "^0.454.0"
4042
}
4143
}

pnpm-lock.yaml

+25-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<script lang="ts">
2+
import { Checkbox as CheckboxPrimitive } from "bits-ui";
3+
import Check from "lucide-svelte/icons/check";
4+
import Minus from "lucide-svelte/icons/minus";
5+
import { cn } from "@/utils";
6+
7+
type $$Props = CheckboxPrimitive.Props;
8+
type $$Events = CheckboxPrimitive.Events;
9+
10+
let className: $$Props["class"] = undefined;
11+
export let checked: $$Props["checked"] = false;
12+
export { className as class };
13+
</script>
14+
15+
<CheckboxPrimitive.Root
16+
class={cn(
17+
"border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer box-content h-4 w-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
18+
className,
19+
)}
20+
bind:checked
21+
{...$$restProps}
22+
on:click
23+
>
24+
<CheckboxPrimitive.Indicator
25+
class={cn("flex h-4 w-4 items-center justify-center text-current")}
26+
let:isChecked
27+
let:isIndeterminate
28+
>
29+
{#if isChecked}
30+
<Check class="h-3.5 w-3.5" />
31+
{:else if isIndeterminate}
32+
<Minus class="h-3.5 w-3.5" />
33+
{/if}
34+
</CheckboxPrimitive.Indicator>
35+
</CheckboxPrimitive.Root>

src/components/ui/checkbox/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Root from "./checkbox.svelte";
2+
export {
3+
Root,
4+
//
5+
Root as Checkbox,
6+
};

src/components/ui/popover/index.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Popover as PopoverPrimitive } from "bits-ui";
2+
import Content from "./popover-content.svelte";
3+
const Root = PopoverPrimitive.Root;
4+
const Trigger = PopoverPrimitive.Trigger;
5+
const Close = PopoverPrimitive.Close;
6+
7+
export {
8+
Root,
9+
Content,
10+
Trigger,
11+
Close,
12+
//
13+
Root as Popover,
14+
Content as PopoverContent,
15+
Trigger as PopoverTrigger,
16+
Close as PopoverClose,
17+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script lang="ts">
2+
import { Popover as PopoverPrimitive } from "bits-ui";
3+
import { cn, flyAndScale } from "@/utils";
4+
5+
type $$Props = PopoverPrimitive.ContentProps;
6+
let className: $$Props["class"] = undefined;
7+
export let transition: $$Props["transition"] = flyAndScale;
8+
export let transitionConfig: $$Props["transitionConfig"] = undefined;
9+
export { className as class };
10+
</script>
11+
12+
<PopoverPrimitive.Content
13+
{transition}
14+
{transitionConfig}
15+
class={cn(
16+
"bg-popover text-popover-foreground z-50 w-72 rounded-md border p-4 shadow-md outline-none",
17+
className,
18+
)}
19+
{...$$restProps}
20+
>
21+
<slot />
22+
</PopoverPrimitive.Content>
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
2+
3+
import Root from "./radio-group.svelte";
4+
import Item from "./radio-group-item.svelte";
5+
const Input = RadioGroupPrimitive.Input;
6+
7+
export {
8+
Root,
9+
Input,
10+
Item,
11+
//
12+
Root as RadioGroup,
13+
Input as RadioGroupInput,
14+
Item as RadioGroupItem,
15+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script lang="ts">
2+
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
3+
import Circle from "lucide-svelte/icons/circle";
4+
import { cn } from "@/utils";
5+
6+
type $$Props = RadioGroupPrimitive.ItemProps;
7+
type $$Events = RadioGroupPrimitive.ItemEvents;
8+
9+
let className: $$Props["class"] = undefined;
10+
export let value: $$Props["value"];
11+
export { className as class };
12+
</script>
13+
14+
<RadioGroupPrimitive.Item
15+
{value}
16+
class={cn(
17+
"border-primary text-primary ring-offset-background focus-visible:ring-ring aspect-square h-4 w-4 rounded-full border focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
18+
className,
19+
)}
20+
{...$$restProps}
21+
on:click
22+
>
23+
<div class="flex items-center justify-center">
24+
<RadioGroupPrimitive.ItemIndicator>
25+
<Circle class="h-2.5 w-2.5 fill-current text-current" />
26+
</RadioGroupPrimitive.ItemIndicator>
27+
</div>
28+
</RadioGroupPrimitive.Item>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script lang="ts">
2+
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
3+
import { cn } from "@/utils";
4+
5+
type $$Props = RadioGroupPrimitive.Props;
6+
7+
let className: $$Props["class"] = undefined;
8+
export let value: $$Props["value"] = undefined;
9+
export { className as class };
10+
</script>
11+
12+
<RadioGroupPrimitive.Root
13+
bind:value
14+
class={cn("grid gap-2", className)}
15+
{...$$restProps}
16+
>
17+
<slot />
18+
</RadioGroupPrimitive.Root>

src/entrypoints/background.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
export default defineBackground(() => {
2-
console.log('Hello background!', { id: browser.runtime.id });
2+
console.log("Hello background!", { id: browser.runtime.id });
3+
4+
browser.runtime.onMessage.addListener((req, sender) => {
5+
if (sender.id != browser.runtime.id) return;
6+
if (req.type === "redirect") {
7+
browser.tabs.create({ url: req.url });
8+
}
9+
});
310
});
+43
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,53 @@
11
import { registerUrl } from "@/utils";
22
import { main as vocabMountain } from "./vocab-mountain";
3+
import { greVocabMountain, toeflVocabMountain } from "@/utils/storage";
34

45
export default defineContentScript({
56
matches: ["*://*.gregmat.com/*"],
67
runAt: "document_idle",
78
main() {
89
registerUrl("vocab-mountain", vocabMountain);
10+
storeVocab();
911
},
1012
});
13+
14+
async function storeVocab() {
15+
const greReq = fetch("https://www.gregmat.com/mountains/vocab-mountain");
16+
const toeflReq = fetch(
17+
"https://www.gregmat.com/mountains/toefl-vocab-mountain"
18+
);
19+
20+
const req = await Promise.all([greReq, toeflReq]);
21+
22+
const body = await Promise.all([req[0].text(), req[1].text()]);
23+
24+
const parser = new DOMParser();
25+
26+
const lookup = [greVocabMountain, toeflVocabMountain] as const;
27+
28+
body.forEach((b, idx) => {
29+
const doc = parser.parseFromString(b, "text/html");
30+
31+
const data = doc.getElementById("__NUXT_DATA__");
32+
if (!data) return;
33+
34+
const json = JSON.parse(data.innerHTML);
35+
36+
const processed = json[7].map((idx: number) =>
37+
json[json[idx].mountain_contents]
38+
.map((entry: number) => json[entry])
39+
.map((word: any) => {
40+
const temp = document.createElement("div");
41+
temp.innerHTML = json[word.description];
42+
return {
43+
title: json[word.title],
44+
description: json[word.description],
45+
pronunciation: json[word.pronunciation],
46+
text: temp.textContent?.replaceAll(/[\n\t]+/g, " ").trim() ?? "",
47+
};
48+
})
49+
);
50+
51+
lookup[idx].setValue(processed);
52+
});
53+
}

0 commit comments

Comments
 (0)