Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend Refactor #59

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on: [workflow_dispatch, push, pull_request]

jobs:
run:
uses: flarum/framework/.github/workflows/REUSABLE_backend.yml@main
uses: flarum/framework/.github/workflows/REUSABLE_backend.yml@1.x
with:
enable_backend_testing: true
enable_phpstan: true
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ on: [workflow_dispatch, push, pull_request]

jobs:
run:
uses: flarum/framework/.github/workflows/REUSABLE_frontend.yml@main
uses: flarum/framework/.github/workflows/REUSABLE_frontend.yml@1.x
with:
enable_bundlewatch: false
enable_prettier: true
enable_typescript: true

frontend_directory: ./js
backend_directory: .
js_package_manager: npm
js_package_manager: yarn
main_git_branch: master

secrets:
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}
],
"require": {
"flarum/core": "^1.2.0"
"flarum/core": "^1.7.0"
},
"authors": [
{
Expand Down
File renamed without changes.
File renamed without changes.
2,578 changes: 0 additions & 2,578 deletions js/package-lock.json

This file was deleted.

23 changes: 15 additions & 8 deletions js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,27 @@
"name": "@fof/user-bio",
"prettier": "@flarum/prettier-config",
"dependencies": {
"@flarum/prettier-config": "^1.0.0",
"autolink-js": "1.0.2",
"flarum-tsconfig": "^1.0.2",
"flarum-webpack-config": "^2.0.2",
"webpack": "^5.92.1",
"webpack-cli": "^5.1.4"
"autolink-js": "1.0.2"
},
"devDependencies": {
"prettier": "^3.3.2"
"prettier": "^3.3.2",
"@flarum/prettier-config": "^1.0.0",
"flarum-tsconfig": "^1.0.3",
"flarum-webpack-config": "^2.0.2",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"typescript-coverage-report": "^0.6.1"
},
"scripts": {
"dev": "webpack --mode development --watch",
"build": "webpack --mode production",
"analyze": "cross-env ANALYZER=true yarn run build",
"format": "prettier --write src",
"format-check": "prettier --check src"
"format-check": "prettier --check src",
"clean-typings": "npx rimraf dist-typings && mkdir dist-typings",
"build-typings": "yarn run clean-typings && ([ -e src/@types ] && cp -r src/@types dist-typings/@types || true) && tsc && yarn run post-build-typings",
"post-build-typings": "find dist-typings -type f -name '*.d.ts' -print0 | xargs -0 sed -i 's,../src/@types,@types,g'",
"check-typings": "tsc --noEmit --emitDeclarationOnly false",
"check-typings-coverage": "typescript-coverage-report"
}
}
10 changes: 10 additions & 0 deletions js/src/@types/shims.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'flarum/common/models/User';

declare module 'flarum/common/models/User' {
export default interface User {
bio(): string;
bioHtml(): string | null;
canViewBio(): boolean;
canEditBio(): boolean;
}
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,35 @@
import app from 'flarum/forum/app';
import Component from 'flarum/common/Component';
import Component, { ComponentAttrs } from 'flarum/common/Component';
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
import Button from 'flarum/common/components/Button';
import classList from 'flarum/common/utils/classList';
import extractText from 'flarum/common/utils/extractText';

/**
* The `UserBio` component displays a user's bio, optionally letting the user
* edit it.
*/
export default class UserBio extends Component {
oninit(vnode) {
super.oninit(vnode);
/**
* Whether the bio is currently being edited.
*
* @type {boolean}
*/
this.editing = false;
import type Mithril from 'mithril';
import type User from 'flarum/common/models/User';
import type { NestedStringArray } from '@askvortsov/rich-icu-message-formatter';

/**
* Whether the bio is currently being saved.
*
* @type {boolean}
*/
this.loading = false;
export interface UserBioAttrs extends ComponentAttrs {
user: User;
editable: boolean;
}

/**
* The rows to show in the textarea by default when editing.
* This is set to 5 by default, but can be overridden by the `--bio-max-lines` CSS variable.
*
* @type {string}
*/
this.textareaRows = '5';
export default class UserBio extends Component<UserBioAttrs> {
editing: boolean = false;
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if having values moved from oninit to instance creation causes any issues or not... theoretically oninit should only be called once, and since these are primitives it shouldn't really matter as there are no side effects? Just some thoughts.

loading: boolean = false;
textareaRows: string = '5';
bioMaxLength: number = 200;
bioPlaceholder: string | NestedStringArray = '';
tempBio: unknown;
tempSelector: number | undefined;

oninit(vnode: Mithril.Vnode<UserBioAttrs>): void {
super.oninit(vnode);

/**
* The max configured character count the bio may be
*/
this.bioMaxLength = app.forum.attribute('fof-user-bio.maxLength');
this.bioMaxLength = app.forum.attribute<number>('fof-user-bio.maxLength');

/**
* The placeholder shown in the bio textbox when no input is set.
Expand All @@ -52,15 +44,17 @@ export default class UserBio extends Component {
});
}

view() {
view(): Mithril.Children {
const user = this.attrs.user;
const editable = this.attrs.editable && this.attrs.user.attribute('canEditBio');
const editable = this.attrs.editable && this.attrs.user.canEditBio();

let content;

if (this.editing) {
const tempBio = this.tempBio;
const value = tempBio ?? user.bio();

// @ts-ignore
const focusIfErrored = (vnode) => {
const textarea = vnode.dom;

Expand Down Expand Up @@ -113,13 +107,14 @@ export default class UserBio extends Component {
if (bioHtml) {
subContent = m.trust(bioHtml);
} else if (user.bio()) {
// @ts-ignore
subContent = m.trust('<p>' + $('<div/>').text(user.bio()).html().replace(/\n/g, '<br>').autoLink({ rel: 'nofollow ugc' }) + '</p>');
} else if (editable) {
subContent = <p className="UserBio-placeholder">{this.bioPlaceholder}</p>;
}
}

const maxLines = app.forum.attribute('fof-user-bio.maxLines') || 5;
const maxLines = app.forum.attribute<number>('fof-user-bio.maxLines') || 5;

content = (
<div
Expand Down Expand Up @@ -152,7 +147,7 @@ export default class UserBio extends Component {
);
}

onkeydown(e) {
onkeydown(e: KeyboardEvent): void {
// Allow keyboard navigation to turn editing mode on
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
Expand All @@ -162,27 +157,31 @@ export default class UserBio extends Component {

/**
* Edit the bio.
* @param {MouseEvent} e
*/
edit(e) {
edit(e: PointerEvent | KeyboardEvent | SubmitEvent): void {
// If the click is special, do not switch to editing mode.
// e.g. allows for Ctrl+Click to open a link in a new tab
if (e.ctrlKey || e.metaKey) return;
if ((e as KeyboardEvent).ctrlKey || (e as KeyboardEvent).metaKey) return;

e.preventDefault();

// Maintain the scroll position & cursor position when editing
const selection = window.getSelection();
const lineIndex = selection.anchorOffset;
const lineIndex = selection?.anchorOffset;

// Sometimes, links are clicked and the anchorNode is either null or the UserBio-content itself
const clickedNode = !selection.anchorNode || !e.target.className.includes('UserBio') ? e.target : selection.anchorNode;
// @ts-ignore
const clickedNode = !selection?.anchorNode || !e.target.className.includes('UserBio') ? e.target : selection.anchorNode;
// @ts-ignore
const lengthBefore = this.countTextLengthBefore(clickedNode);

// @ts-ignore
const currentScroll = e.currentTarget.scrollTop;
// @ts-ignore
const index = lengthBefore + lineIndex;

// Show the same number of lines to avoid layout shift
// @ts-ignore
Copy link
Member

Choose a reason for hiding this comment

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

Why are all these ts-ignores necessary?

this.textareaRows = getComputedStyle(e.currentTarget).getPropertyValue('--bio-max-lines') || '5';

this.editing = true;
Expand All @@ -194,7 +193,7 @@ export default class UserBio extends Component {
/**
* Save the bio.
*/
save(e) {
save(e: SubmitEvent): void {
e.preventDefault();

const value = this.$('textarea').val();
Expand All @@ -210,6 +209,7 @@ export default class UserBio extends Component {
.catch(() => {
this.tempBio = value;
this.tempSelector = tempSelector;
// @ts-ignore
this.edit();
})
.then(() => {
Expand All @@ -223,7 +223,7 @@ export default class UserBio extends Component {
m.redraw();
}

reset(e) {
reset(e: PointerEvent): void {
// Don't want to actually reset the form
e.preventDefault();

Expand All @@ -235,32 +235,28 @@ export default class UserBio extends Component {
}
}

isDirty() {
isDirty(): boolean {
const value = this.$('textarea').val();
const user = this.attrs.user;

return user.bio() !== value;
}

/**
*
* @param {Node} anchorNode
* @returns {number}
*/
countTextLengthBefore(anchorNode) {
countTextLengthBefore(anchorNode: Node): number {
if (!anchorNode || (anchorNode instanceof HTMLElement && anchorNode.className.includes('UserBio'))) return 0;

let length = 0;

if (anchorNode.previousSibling) {
// @ts-ignore
for (let prev = anchorNode.previousSibling; prev; prev = prev.previousSibling) {
// @ts-ignore
length += prev.textContent.length;
}
}

const parent = anchorNode.parentNode;

// We need to recursively call this function if the anchorNode is not a direct child of UserBio-content
// @ts-ignore
return length + this.countTextLengthBefore(anchorNode.parentNode);
}
}
File renamed without changes.
10 changes: 10 additions & 0 deletions js/src/forum/extend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Extend from 'flarum/common/extenders';
import User from 'flarum/common/models/User';

export default [
new Extend.Model(User) //
.attribute<string>('bio')
.attribute<string | null>('bioHtml')
.attribute<boolean>('canViewBio')
.attribute<boolean>('canEditBio'),
];
17 changes: 17 additions & 0 deletions js/src/forum/extenders/addBioToUserCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { extend } from 'flarum/common/extend';
import UserCard from 'flarum/forum/components/UserCard';
import UserBio from './../components/UserBio';

import type Mithril from 'mithril';
import type ItemList from 'flarum/common/utils/ItemList';
import type User from 'flarum/common/models/User';

export default function addBioToUserCard() {
extend(UserCard.prototype, 'infoItems', function (items: ItemList<Mithril.Children>) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd type this in the function itself - I believe this should work. (Does UserCard not have the attributes typed?)

Suggested change
extend(UserCard.prototype, 'infoItems', function (items: ItemList<Mithril.Children>) {
extend(UserCard.prototype, 'infoItems', function (items: ItemList<Mithril.Children>, this: any) {

const user: User = (this as any).attrs.user;

if (!user.canViewBio()) return;

items.add('bio', <UserBio user={user} editable={(this as any).attrs.editable} />, -100);
});
}
24 changes: 0 additions & 24 deletions js/src/forum/index.js

This file was deleted.

10 changes: 10 additions & 0 deletions js/src/forum/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'autolink-js';
import app from 'flarum/forum/app';
import addBioToUserCard from './extenders/addBioToUserCard';

export * from './components';
export { default as extend } from './extend';

app.initializers.add('fof-user-bio', () => {
addBioToUserCard();
});
12 changes: 5 additions & 7 deletions js/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
{
// Use Flarum's tsconfig as a starting point
"extends": "flarum-tsconfig",
// This will match all .ts, .tsx, .d.ts, .js, .jsx files in your `src` folder
// and also tells your Typescript server to read core's global typings for
// access to `dayjs` and `$` in the global namespace.
"include": ["src/**/*", "../vendor/flarum/core/js/dist-typings/@types/**/*"],
"include": ["src/**/*", "../vendor/*/*/js/dist-typings/@types/**/*", "@types/**/*"],
"compilerOptions": {
// This will output typings to `dist-typings`
"declarationDir": "./dist-typings",
"noUnusedLocals": true,
"noUnusedParameters": true,
"baseUrl": ".",
"paths": {
"flarum/*": ["../vendor/flarum/core/js/dist-typings/*"]
"flarum/*": ["../vendor/flarum/core/js/dist-typings/*"],
"@flarum/core/*": ["../vendor/flarum/core/js/dist-typings/*"]
}
}
}
Loading
Loading