Skip to content

Commit

Permalink
Merge pull request #104 from amur-tiger/develop
Browse files Browse the repository at this point in the history
Release 0.8.1
  • Loading branch information
amur-tiger committed Mar 30, 2024
2 parents d38754b + f85df4b commit 0be3b64
Show file tree
Hide file tree
Showing 20 changed files with 437 additions and 134 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# FanFiction Enhancements

## 0.8.1

* Changed: Some improvements to dark mode ([#101](https://github.com/amur-tiger/fanfiction-enhancements/issues/101))

## 0.8.0

* Changed: Move alert and favorite count into their respective buttons ([#77](https://github.com/amur-tiger/fanfiction-enhancements/issues/77))
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fanfiction-enhancements",
"version": "0.8.0",
"version": "0.8.1",
"author": "Arne 'TigeR' Linck",
"description": "FanFiction.net Enhancements",
"license": "MIT",
Expand Down
154 changes: 118 additions & 36 deletions scripts/jsx-transform.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import path from "node:path";
import fs from "node:fs/promises";
import type { Plugin } from "esbuild";
import babel, { type PluginObj } from "@babel/core";
import babel, { type NodePath, type PluginObj } from "@babel/core";
import {
type ArrayExpression,
arrowFunctionExpression,
callExpression,
type CallExpression,
type Expression,
identifier,
importDeclaration,
importDefaultSpecifier,
isArrowFunctionExpression,
importSpecifier,
isArrayExpression,
isCallExpression,
isIdentifier,
isObjectExpression,
isObjectProperty,
isStringLiteral,
type Node,
type ObjectExpression,
stringLiteral,
} from "@babel/types";
import type { Scope } from "@babel/traverse";
// @ts-ignore
import presetTypescript from "@babel/preset-typescript";
// @ts-ignore
Expand Down Expand Up @@ -71,6 +80,66 @@ export default function jsxTransform(): Plugin {

function babelTransform(): PluginObj {
let renderVariableName = "";
let computeVariableName = "";

function getRenderName(scope: Scope) {
if (!renderVariableName) {
renderVariableName = scope.generateUid("render");
}
return renderVariableName;
}

function getComputeName(scope: Scope) {
if (!computeVariableName) {
computeVariableName = scope.generateUid("compute");
}
return computeVariableName;
}

function isJsxCall(node: Node | null | undefined) {
return (
isCallExpression(node) &&
(isIdentifier(node.callee, { name: "_jsx" }) || isIdentifier(node.callee, { name: "_jsxs" }))
);
}

function isReactiveCall(node: CallExpression) {
return (
(isIdentifier(node.callee, { name: renderVariableName }) ||
isIdentifier(node.callee, { name: computeVariableName })) &&
node.arguments.length === 1
);
}

function hasSignalCall(path: NodePath, withJsx = false): boolean {
let hasCall = false;
let hasJsx = false;

path.traverse({
ArrowFunctionExpression(p) {
p.skip();
},

CallExpression(p) {
if (isJsxCall(p.node)) {
p.skip();
hasJsx = true;
} else if (isReactiveCall(p.node)) {
p.skip();
} else {
p.stop();
hasCall = true;
}
},
});

return withJsx ? hasCall && hasJsx : hasCall;
}

function wrapCall(path: NodePath<Expression>, func: string) {
path.replaceWith(callExpression(identifier(func), [arrowFunctionExpression([], path.node)]));
path.skip();
}

return {
name: "jsxTransform",
Expand All @@ -92,47 +161,60 @@ function babelTransform(): PluginObj {
importDeclaration([importDefaultSpecifier(identifier(renderVariableName))], stringLiteral("@jsx/render")),
);
}
if (computeVariableName) {
path.node.body.push(
importDeclaration(
[importSpecifier(identifier(computeVariableName), identifier("compute"))],
stringLiteral("@jsx/render"),
),
);
}
},
},

CallExpression(path) {
if (isJsxCall(path.node) && isObjectExpression(path.node.arguments[1])) {
// checking if a call is a signal or not is too difficult at this point,
// just wrap any node that has any call into a new context
let hasCall = false;
path.traverse({
ArrowFunctionExpression(p) {
p.skip();
},

CallExpression(p) {
if (isJsxCall(p.node) || isRenderContextCall(p.node)) {
p.skip();
return;
}
CallExpression: {
exit(path) {
if (!(isJsxCall(path.node) && isObjectExpression(path.node.arguments[1])) || isReactiveCall(path.node)) {
// skip if not jsx()-call, skip if jsx()-call was already wrapped
return;
}

p.stop();
hasCall = true;
if (!renderVariableName) {
renderVariableName = path.scope.generateUid("render");
const props = path.get("arguments")[1] as NodePath<ObjectExpression>;
for (const prop of props.get("properties")) {
if (
!isObjectProperty(prop.node) ||
(isCallExpression(prop.node.value) && isIdentifier(prop.node.value.callee, { name: computeVariableName }))
) {
continue;
}

const value = prop.get("value");
if (Array.isArray(value)) {
continue;
}

if (isIdentifier(prop.node.key, { name: "children" })) {
// wrap each reactive child
if (isArrayExpression(value.node)) {
for (const item of (value as NodePath<ArrayExpression>).get("elements")) {
if (hasSignalCall(item as NodePath<Expression>)) {
wrapCall(item as NodePath<Expression>, getRenderName(item.scope));
}
}
} else {
if (hasSignalCall(value)) {
wrapCall(value as NodePath<Expression>, getRenderName(value.scope));
}
}
},
});

if (hasCall && !isArrowFunctionExpression(path.parent)) {
path.replaceWith(callExpression(identifier(renderVariableName), [arrowFunctionExpression([], path.node)]));
path.skip();
} else if (isStringLiteral(path.node.arguments[0])) {
// for intrinsic elements, wrap attributes (components have to be re-rendered whole)
if (hasSignalCall(prop)) {
wrapCall(value as NodePath<Expression>, getComputeName(value.scope));
}
}
}
}
},
},
},
};
}

function isJsxCall(node: CallExpression) {
return isIdentifier(node.callee, { name: "_jsx" }) || isIdentifier(node.callee, { name: "_jsxs" });
}

function isRenderContextCall(node: CallExpression) {
return isIdentifier(node.callee, { name: "render" }) && node.arguments.length === 1;
}
6 changes: 3 additions & 3 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ export interface ButtonProps {
class?: string;
title?: string;
disabled?: boolean;
onClick?: EventListenerOrEventListenerObject;
onClick?: (event: MouseEvent) => void;
children?: JSX.Children;
}

export default function Button({ class: className, title, disabled, onClick, children }: ButtonProps) {
return (
<span role="button" class={clsx("btn", { disabled }, className)} title={title} onClick={onClick}>
<button class={clsx("btn", { disabled }, className)} disabled={disabled} title={title} onClick={onClick}>
{children}
</span>
</button>
);
}
2 changes: 1 addition & 1 deletion src/components/ChapterList/ChapterList.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
}

.ffe-cl-chapter a {
color: var(--ffe-link-color);
color: var(--ffe-on-panel-link-color) !important;
}

.ffe-cl-words {
Expand Down
4 changes: 2 additions & 2 deletions src/components/ChapterList/ChapterList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import render from "@jsx/render";
import { getStory, type StoryData } from "../../api/story";
import type { Chapter } from "ffn-parser";
import { createSignal } from "../../signal/signal";
import getChapterRead from "../../api/chapter-read";
import ChapterListEntry from "./ChapterListEntry";
import "./ChapterList.css";
import render from "@jsx/render";

function hiddenChapterMapper(story: StoryData, isRead: (chapter: Chapter) => boolean, onShow: () => void) {
return (chapter: Chapter, idx: number, chapters: Chapter[]) => {
Expand Down Expand Up @@ -43,7 +43,7 @@ function hiddenChapterMapper(story: StoryData, isRead: (chapter: Chapter) => boo

return (
<li class="ffe-cl-chapter ffe-cl-collapsed">
<a onclick={onShow}>
<a onClick={onShow}>
Show {count - 2} hidden chapter{count !== 3 && "s"}
</a>
</li>
Expand Down
2 changes: 2 additions & 0 deletions src/components/CircularProgress/CircularProgress.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
}

.ffe-cp-circle-background {
fill: none;
stroke: var(--ffe-on-button-color-faint);
}

.ffe-cp-circle-foreground {
transform: rotate(-90deg);
fill: none;
stroke: var(--ffe-primary-color);
}
4 changes: 1 addition & 3 deletions src/components/CircularProgress/CircularProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,16 @@ export default function CircularProgress({ progress, size = 24 }: CircularProgre
cx={size / 2}
cy={size / 2}
r={(size - strokeWidth) / 2}
fill="none"
stroke-width={strokeWidth}
/>
<circle
class="ffe-cp-circle-foreground"
cx={size / 2}
cy={size / 2}
r={(size - strokeWidth) / 2}
fill="none"
stroke-width={strokeWidth}
transform-origin={`${size / 2} ${size / 2}`}
stroke-dasharray={`${dash} ${circumference - dash}`}
style={`transform-origin: ${size / 2} ${size / 2}`}
/>
</svg>
</span>
Expand Down
13 changes: 5 additions & 8 deletions src/components/Rating/Rating.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,27 @@
background: gray;
padding: 3px 5px;
color: #fff !important;
border: 1px solid rgba(0, 0, 0, 0.2);
border: 1px solid var(--ffe-weak-divider-color);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
border-radius: 4px;
margin-right: 5px;
vertical-align: 2px;
}

.ffe-rating:hover {
border-bottom: 1px solid rgba(0, 0, 0, 0.2) !important;
border-bottom-color: transparent;
}

.ffe-rating-k,
.ffe-rating-kp {
background: #78ac40;
box-shadow: 0 1px 0 #90ce4d inset;
background: var(--ffe-rating-k-color);
}

.ffe-rating-t,
.ffe-rating-m {
background: #ffb400;
box-shadow: 0 1px 0 #ffd800 inset;
background: var(--ffe-rating-t-color);
}

.ffe-rating-ma {
background: #c03d2f;
box-shadow: 0 1px 0 #e64938 inset;
background: var(--ffe-rating-m-color);
}
4 changes: 2 additions & 2 deletions src/components/StoryCard/StoryCard.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@

.ffe-sc-alert:hover,
.ffe-sc-alert.ffe-active {
color: var(--ffe-alert-color);
color: var(--ffe-alert-color) !important;
}

.ffe-sc-favorite:hover,
.ffe-sc-favorite.ffe-active {
color: var(--ffe-favorite-color);
color: var(--ffe-favorite-color) !important;
}

.ffe-sc-follow-count {
Expand Down
4 changes: 2 additions & 2 deletions src/components/StoryCard/StoryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export default function StoryCard({ storyId }: StoryCardProps) {
{story.published && (
<span class="ffe-sc-footer-info">
<strong>Published:&nbsp;</strong>
<time datetime={toDate(story.published).toISOString()}>
<time dateTime={toDate(story.published).toISOString()}>
{toDate(story.published).toLocaleDateString("en")}
</time>
</span>
Expand All @@ -184,7 +184,7 @@ export default function StoryCard({ storyId }: StoryCardProps) {
{story.updated && (
<span class="ffe-sc-footer-info">
<strong>Updated:&nbsp;</strong>
<time datetime={toDate(story.updated).toISOString()}>{toDate(story.updated).toLocaleDateString("en")}</time>
<time dateTime={toDate(story.updated).toISOString()}>{toDate(story.updated).toLocaleDateString("en")}</time>
</span>
)}
</div>
Expand Down

0 comments on commit 0be3b64

Please sign in to comment.