-
-
Notifications
You must be signed in to change notification settings - Fork 11
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
base: master
Are you sure you want to change the base?
Changes from all commits
d89f3f3
cf92cbb
21b1a25
52f0a03
60dc623
9f450ea
04570e5
62366c0
9244f2d
eb50ac0
5cf5ddc
6af76fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ | |
} | ||
], | ||
"require": { | ||
"flarum/core": "^1.2.0" | ||
"flarum/core": "^1.7.0" | ||
}, | ||
"authors": [ | ||
{ | ||
|
This file was deleted.
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; | ||
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. | ||
|
@@ -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; | ||
|
||
|
@@ -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 | ||
|
@@ -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(); | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are all these |
||
this.textareaRows = getComputedStyle(e.currentTarget).getPropertyValue('--bio-max-lines') || '5'; | ||
|
||
this.editing = true; | ||
|
@@ -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(); | ||
|
@@ -210,6 +209,7 @@ export default class UserBio extends Component { | |
.catch(() => { | ||
this.tempBio = value; | ||
this.tempSelector = tempSelector; | ||
// @ts-ignore | ||
this.edit(); | ||
}) | ||
.then(() => { | ||
|
@@ -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(); | ||
|
||
|
@@ -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); | ||
} | ||
} |
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'), | ||
]; |
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>) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd type
Suggested change
|
||||||
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); | ||||||
}); | ||||||
} |
This file was deleted.
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(); | ||
}); |
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/*"] | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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... theoreticallyoninit
should only be called once, and since these are primitives it shouldn't really matter as there are no side effects? Just some thoughts.