Skip to content

Commit

Permalink
Merge branch 'main' into fix/422
Browse files Browse the repository at this point in the history
  • Loading branch information
EthanThatOneKid committed May 19, 2022
2 parents f9db6ab + 6b5b45a commit f53a6da
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 56 deletions.
5 changes: 5 additions & 0 deletions src/lib/constants/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ export const links = {
v2: 'https://acmcsuf.com/blog/262',
'create-html': 'https://github.com/mikeploythai/create-html',
apply: 'https://forms.gle/eFpFuiDW6fyRrbUN6',
election: 'https://forms.gle/m2MrswqTuxUsdb4y5',
f22positions:
'https://www.notion.so/jn42/2022-23-Board-Elections-3bbf4851a9334b199b512bd5a5c4b5f7',
f22apply:
'https://docs.google.com/forms/d/e/1FAIpQLSfK16Y3REQza6_5mHeCIr6vLo8-bcCvU6_xR-bCUgERBq86bg/viewform?usp=sf_link',
} as const;
21 changes: 15 additions & 6 deletions src/lib/ical/parse.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { Temporal } from '@js-temporal/polyfill';
import { DEBUG } from '$lib/constants';
import { walkICAL, makeAcmEvent, AcmEvent, ICALParseOptions } from './utils';
import { walkICAL, makeAcmEvent, AcmEvent } from './utils';

export interface ICALParseOptions {
filterBefore?: boolean;
referenceDate?: Temporal.ZonedDateTimeLike;
maxEvents?: number;
}

export function parse(rawICAL: string, options?: ICALParseOptions): AcmEvent[] {
const acmEvents: AcmEvent[] = [];

const refDate = options.referenceDate ?? Temporal.Now.zonedDateTimeISO('America/Los_Angeles');
const filterBefore = options.filterBefore !== undefined ? options.filterBefore : true;

for (const icalEvent of walkICAL(rawICAL)) {
const acmEvent = makeAcmEvent(icalEvent, refDate);

// skip events that have already ended (except when in debug mode)
if (!acmEvent.hasEnded || DEBUG) {
acmEvents.push(acmEvent);
if (filterBefore && acmEvent.hasEnded) {
continue;
}

acmEvents.push(acmEvent);
}

const sortedAcmEvents = acmEvents.sort((one, two) =>
Expand All @@ -22,8 +30,9 @@ export function parse(rawICAL: string, options?: ICALParseOptions): AcmEvent[] {

// serve a set amount of events when in debug mode
// @see <https://etok.codes/acmcsuf.com/pull/329>
if (DEBUG) {
return sortedAcmEvents.slice(-1 * (options.maxEvents ?? 5));
if (options.maxEvents !== undefined) {
const eventsAmt = options.maxEvents ?? 5;
return sortedAcmEvents.slice(0, eventsAmt);
}

return sortedAcmEvents;
Expand Down
42 changes: 21 additions & 21 deletions src/lib/ical/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
makeEventLink,
wrapText,
walkICAL,
replaceHtmlWithExternalLinks,
replaceHtmlLinkTargets,
} from './utils';

test('parses sample ICAL payload', () => {
Expand Down Expand Up @@ -77,35 +77,35 @@ test('wraps long text into lines broken at column 100 3 times', () => {
assert(lines.at(-1).length === 1, 'where the last line is a single asterisk');
});

test('replaces HTML with external links', () => {
const actual = replaceHtmlWithExternalLinks(`<a href="https://example.com/">
test('replaces link targets with "_blank" in HTML string', () => {
const actual =
replaceHtmlLinkTargets(`<a title="example" target="_self" href="https://example.com/">Example Link</a>
<a title="example" target="_self"href="https://example.com/">Example Link</a>
<a target="_self" title="example" href="https://example.com/">Example Link</a>
<a title="example" target="_self" target="poggers" target="not poggers" href="https://example.com/">Example Link</a>
<a title="example" target="" href="https://example.com/">Example Link</a>
<a href="https://example.com/example/">https://example.com/example/</a>
<a title="example">Example Link</a>
<a href="https://example.com/">
<a>
<a title="example" href="https://example.com/">
<a title="example" target="_self" href="https://example.com/">
<a title="example" target="_blank" href="https://example.com/">
<article href="https://example.com/">example</article>
example`);
expect(actual).toBe(`<a href="https://example.com/" target="_blank">
expect(actual)
.toBe(`<a title="example" href="https://example.com/" target="_blank">Example Link</a>
<a title="example" href="https://example.com/" target="_blank">Example Link</a>
<a title="example" href="https://example.com/" target="_blank">Example Link</a>
<a title="example" href="https://example.com/" target="_blank">Example Link</a>
<a title="example" href="https://example.com/" target="_blank">Example Link</a>
<a href="https://example.com/example/" target="_blank">https://example.com/example/</a>
<a title="example">Example Link</a>
<a href="https://example.com/" target="_blank">
<a>
<a title="example" href="https://example.com/" target="_blank">
<a title="example" href="https://example.com/" target="_blank">
<a title="example" target="_blank" href="https://example.com/">
<article href="https://example.com/">example</article>
example`);
});

test('additional replaces HTML with external links tests', () => {
const actual =
replaceHtmlWithExternalLinks(`<a title="example" target="_self" href="https://example.com/">
<a title="example" target="_self"href="https://example.com/">
<a target="_self" title="example" href="https://example.com/">
<a title="example" target="_self" target="poggers" target="not poggers" href="https://example.com/">
<a title="example" target="" href="https://example.com/">
example`);
expect(actual).toBe(`<a title="example" href="https://example.com/" target="_blank">
<a title="example" href="https://example.com/" target="_blank">
<a title="example" href="https://example.com/" target="_blank">
<a title="example" href="https://example.com/" target="_blank">
<a title="example" href="https://example.com/" target="_blank">
<article href="https://example.com/">example</article>
example`);
});
16 changes: 5 additions & 11 deletions src/lib/ical/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ export interface AcmEvent {

export type ICALResolvable = string | string[] | ICALResolvable[] | { [k: string]: ICALResolvable };

export interface ICALParseOptions {
referenceDate?: Temporal.ZonedDateTimeLike;
maxEvents?: number;
}

/**
* The code in this function is derived from
* <https://github.com/adrianlee44/ical2json>.
Expand Down Expand Up @@ -152,11 +147,10 @@ export function produceSummary(title: string, description: string, selfLink: str
: title + ' — ' + selfLink;
}

export function replaceHtmlWithExternalLinks(html: string): string {
return html.replace(/<a .*href=".*".*>/gm, (match: string): string => {
if (match.includes('target="_blank"')) return match;
match = match.replace(/target=".*?"\W*/g, '');
return match.slice(0, match.length - 1) + ' target="_blank">';
export function replaceHtmlLinkTargets(html: string, withTarget = '_blank'): string {
return html.replace(/<a\W.*?href=".*?".*?>/gm, (match: string): string => {
match = match.replace(/target=".*?"\W*/gm, '');
return match.slice(0, match.length - 1) + ` target="${withTarget}">`;
});
}

Expand Down Expand Up @@ -192,7 +186,7 @@ export function parseDescription(
description = (description.substring(0, start) + description.substring(end)).trim();
}

description = replaceHtmlWithExternalLinks(description);
description = replaceHtmlLinkTargets(description);

return { description, variables };
}
Expand Down
8 changes: 8 additions & 0 deletions src/routes/blog/_index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, test, expect } from 'vitest';
import { cleanup, render } from '@testing-library/svelte';
import { posts as SAMPLE_POSTS } from './_testdata/posts';

import Blog from './index.svelte';

Expand All @@ -13,3 +14,10 @@ test('can find the correct page title', () => {
const { getByText } = render(Blog);
expect(getByText('The official acmCSUF blog.')).toBeDefined();
});

test('can render 3 blog posts', () => {
const MAX_POSTS = 3;
const { container } = render(Blog, { posts: SAMPLE_POSTS });
const actual = container.querySelectorAll('.blog-post').length;
expect(actual).toBe(MAX_POSTS);
});
5 changes: 3 additions & 2 deletions src/routes/blog/_query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ export async function fetchNewsletters(): Promise<Newsletter[]> {
headers: { Authorization: `token ${ghAccessToken}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query: newslettersQuery }),
});

const newsletters = formatNewsletters(await response.json());
return newsletters;
return newsletters.sort((a, b) => {
return new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf();
});
}
50 changes: 50 additions & 0 deletions src/routes/blog/_testdata/posts.ts

Large diffs are not rendered by default.

20 changes: 16 additions & 4 deletions src/routes/blog/index.json.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import type { RequestHandlerOutput } from '@sveltejs/kit/types/internal';
import { fetchNewsletters } from './_query';
import { DEBUG } from '$lib/constants';
import { posts } from './_testdata/posts';

const SAMPLE_POSTS = JSON.stringify(posts);

export async function get(): Promise<RequestHandlerOutput> {
return new Response(JSON.stringify(await fetchNewsletters()), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
// Uses sample data when DEBUG = 1 or env variables are not satisfied.
const isSatisfied =
import.meta.env.VITE_GH_ACCESS_TOKEN !== undefined &&
import.meta.env.VITE_GH_DISCUSSION_CATEGORY_ID !== undefined;

return new Response(
DEBUG || !isSatisfied ? SAMPLE_POSTS : JSON.stringify(await fetchNewsletters()),
{
status: 200,
headers: { 'Content-Type': 'application/json' },
}
);
}
5 changes: 3 additions & 2 deletions src/routes/blog/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

<ul>
{#each posts as post (post.id)}
<li>
<li class="blog-post">
<a href={`/blog/${post.id}`} sveltekit:prefetch>
<h2 class="headers">{post.title}</h2>
<div class="markdown-body">
Expand Down Expand Up @@ -95,11 +95,12 @@
cursor: pointer;
background-color: rgba(56, 182, 255, 0.25);
border-radius: 1em;
padding: 2em;
margin: 2em 0;
a {
text-decoration: none;
padding: 2em;
display: block;
.markdown-body {
max-height: 100px;
Expand Down
7 changes: 6 additions & 1 deletion src/routes/events/_index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ test('can find the correct page title', () => {
test('renders 10 event items', async () => {
const MAX_EVENTS = 10;
const TEST_DATA = readFileSync('./src/routes/events/_testdata/events.ics', 'utf-8');
const { container } = render(Events, { events: parse(TEST_DATA, { maxEvents: MAX_EVENTS }) });
const { container } = render(Events, {
events: parse(TEST_DATA, {
maxEvents: MAX_EVENTS,
filterBefore: false,
}),
});
expect(container.querySelectorAll('.event-box').length).toBe(MAX_EVENTS);
});
28 changes: 24 additions & 4 deletions src/routes/events/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
</script>

<svelte:head>
<title>Events | ACM at CSUF</title>
<title>Events / ACM at CSUF</title>
</svelte:head>

<Spacing --min="175px" --med="200px" --max="200px" />

<CommonHero>
<h2 slot="headline" class="size-lg">Curated events for growth and success</h2>
<p slot="text" class="size-xs">
<p slot="text" class="size-md">
Our student chapter hosts a multitude of events throughout each school semester, consisting of
workshops, info sessions, community building events, and much more!
<br /><br />
Expand All @@ -37,7 +37,10 @@

<Spacing --min="100px" --med="175px" --max="200px" />

<h2 class="size-lg headers">Upcoming Events 📅</h2>
<div class="main-header">
<h2 class="size-lg headers">Upcoming Events</h2>
<img src="assets/bluecalender.svg" alt="Blue Calender" />
</div>

<Spacing --med="16px" />

Expand All @@ -52,13 +55,30 @@
<Spacing --min="8px" --med="63px" --max="88px" />

<style lang="scss">
h2 {
.main-header {
display: flex;
justify-content: center;
text-align: center;
align-items: center;
flex-direction: row;
img {
display: block;
margin-left: 10px;
width: 30px;
height: 30px;
}
}
p {
text-align: center;
}
@media (max-width: 300px) {
.main-header {
img {
display: none;
}
}
}
</style>
10 changes: 5 additions & 5 deletions src/routes/paths/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<Spacing --min="175px" --med="200px" --max="200px" />

<CommonHero>
<h2 slot="headline" class="size-lg">What are paths?</h2>
<p slot="text" class="size-xs">
<h2 slot="headline" class="size-xl">What are paths?</h2>
<p slot="text" class="size-md">
Paths are committees that specialize in specific fields in the tech industry. We’ve designed
paths to be gateways for students to explore new fields, develop new interests, and enhance
skills that’ll benefit in the industry.
Expand All @@ -24,7 +24,7 @@
<Spacing --min="100px" --med="175px" --max="200px" />

<PathSection info={acmAlgo} textAlign={TextAlignment.Right}>
<p slot="content" class="size-xs">
<p slot="content" class="size-md">
This path is dedicated to building the programming proficiency of students.
<span class="brand-purple brand-em">Algo</span> focuses on mastering data structures and algorithms,
enhancing problem solving abilities, and exploration of competitive programming.
Expand All @@ -34,7 +34,7 @@
<Spacing --med="64px" />

<PathSection info={acmCreate} textAlign={TextAlignment.Left}>
<p slot="content" class="size-xs">
<p slot="content" class="size-md">
This path is dedicated to emphasizing the importance of product design and product management in
the tech industry. <span class="brand-pink brand-em">Create</span> focuses on educating students
about design principles, design tools, and the intricacies of conceptualization, development, and
Expand All @@ -45,7 +45,7 @@
<Spacing --med="64px" />

<PathSection info={acmDev} textAlign={TextAlignment.Right}>
<p slot="content" class="size-xs">
<p slot="content" class="size-md">
This path is dedicated to giving students the opportunity to explore tech via hands-on projects
and activities. <span class="brand-em brand-bluer">Dev</span> focuses on introducing students to
software development, and the various tech stacks used in the industry.
Expand Down
14 changes: 14 additions & 0 deletions static/assets/bluecalender.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit f53a6da

Please sign in to comment.