diff options
Diffstat (limited to 'ui')
-rw-r--r-- | ui/.eslintignore | 2 | ||||
-rw-r--r-- | ui/fuse.js | 26 | ||||
-rw-r--r-- | ui/src/components/comment-node.tsx | 18 | ||||
-rw-r--r-- | ui/src/components/main.tsx | 14 | ||||
-rw-r--r-- | ui/src/components/navbar.tsx | 16 | ||||
-rw-r--r-- | ui/src/components/post-listing.tsx | 11 | ||||
-rw-r--r-- | ui/src/components/search.tsx | 15 | ||||
-rw-r--r-- | ui/src/components/sidebar.tsx | 12 | ||||
-rw-r--r-- | ui/src/components/user.tsx | 82 | ||||
-rw-r--r-- | ui/src/interfaces.ts | 7 | ||||
-rw-r--r-- | ui/src/translations/en.ts | 1 | ||||
-rw-r--r-- | ui/src/utils.ts | 7 | ||||
-rw-r--r-- | ui/translation_report.ts | 20 |
13 files changed, 199 insertions, 32 deletions
diff --git a/ui/.eslintignore b/ui/.eslintignore new file mode 100644 index 00000000..aa6ed178 --- /dev/null +++ b/ui/.eslintignore @@ -0,0 +1,2 @@ +fuse.js +translation_report.ts @@ -1,11 +1,11 @@ -const { +import { FuseBox, Sparky, EnvPlugin, CSSPlugin, WebIndexPlugin, - QuantumPlugin -} = require('fuse-box'); + QuantumPlugin, +} from 'fuse-box'; // const transformInferno = require('../../dist').default const transformInferno = require('ts-transform-inferno').default; const transformClasscat = require('ts-transform-classcat').default; @@ -25,22 +25,22 @@ Sparky.task('config', _ => { before: [transformClasscat(), transformInferno()], }, alias: { - 'locale': 'moment/locale' - }, + locale: 'moment/locale', + }, plugins: [ EnvPlugin({ NODE_ENV: isProduction ? 'production' : 'development' }), CSSPlugin(), WebIndexPlugin({ title: 'Inferno Typescript FuseBox Example', template: 'src/index.html', - path: isProduction ? "/static" : "/" + path: isProduction ? '/static' : '/', }), isProduction && - QuantumPlugin({ - bakeApiIntoBundle: 'app', - treeshake: true, - uglify: true, - }), + QuantumPlugin({ + bakeApiIntoBundle: 'app', + treeshake: true, + uglify: true, + }), ], }); app = fuse.bundle('app').instructions('>index.tsx'); @@ -48,7 +48,9 @@ Sparky.task('config', _ => { // Sparky.task('version', _ => setVersion()); Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/')); Sparky.task('env', _ => (isProduction = true)); -Sparky.task('copy-assets', () => Sparky.src('assets/**/**.*').dest(isProduction ? 'dist/' : 'dist/static')); +Sparky.task('copy-assets', () => + Sparky.src('assets/**/**.*').dest(isProduction ? 'dist/' : 'dist/static') +); Sparky.task('dev', ['clean', 'config', 'copy-assets'], _ => { fuse.dev(); app.hmr().watch(); diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index f408ef27..34ec0dfb 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -17,7 +17,13 @@ import { BanType, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; -import { mdToHtml, getUnixTime, canMod, isMod } from '../utils'; +import { + mdToHtml, + getUnixTime, + canMod, + isMod, + pictshareAvatarThumbnail, +} from '../utils'; import * as moment from 'moment'; import { MomentTime } from './moment-time'; import { CommentForm } from './comment-form'; @@ -128,7 +134,15 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { className="text-info" to={`/u/${node.comment.creator_name}`} > - {node.comment.creator_name} + {node.comment.creator_avatar && ( + <img + height="32" + width="32" + src={pictshareAvatarThumbnail(node.comment.creator_avatar)} + class="rounded-circle mr-1" + /> + )} + <span>{node.comment.creator_name}</span> </Link> </li> {this.isMod && ( diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx index 0d6be91d..d1042f38 100644 --- a/ui/src/components/main.tsx +++ b/ui/src/components/main.tsx @@ -31,6 +31,7 @@ import { routeSortTypeToEnum, routeListingTypeToEnum, postRefetchSeconds, + pictshareAvatarThumbnail, } from '../utils'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@ -65,6 +66,9 @@ export class Main extends Component<any, MainState> { number_of_posts: null, number_of_comments: null, number_of_communities: null, + enable_downvotes: null, + open_registration: null, + enable_nsfw: null, }, admins: [], banned: [], @@ -341,7 +345,15 @@ export class Main extends Component<any, MainState> { {this.state.site.admins.map(admin => ( <li class="list-inline-item"> <Link class="text-info" to={`/u/${admin.name}`}> - {admin.name} + {admin.avatar && ( + <img + height="32" + width="32" + src={pictshareAvatarThumbnail(admin.avatar)} + class="rounded-circle mr-1" + /> + )} + <span>{admin.name}</span> </Link> </li> ))} diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index 306dc74f..f1a98941 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -13,7 +13,7 @@ import { GetSiteResponse, Comment, } from '../interfaces'; -import { msgOp } from '../utils'; +import { msgOp, pictshareAvatarThumbnail } from '../utils'; import { version } from '../version'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@ -151,7 +151,19 @@ export class Navbar extends Component<any, NavbarState> { class="nav-link" to={`/u/${UserService.Instance.user.username}`} > - {UserService.Instance.user.username} + <span> + {UserService.Instance.user.avatar && ( + <img + src={pictshareAvatarThumbnail( + UserService.Instance.user.avatar + )} + height="32" + width="32" + class="rounded-circle mr-2" + /> + )} + {UserService.Instance.user.username} + </span> </Link> </li> </> diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx index 61a4c865..1f3a74ac 100644 --- a/ui/src/components/post-listing.tsx +++ b/ui/src/components/post-listing.tsx @@ -25,6 +25,7 @@ import { isImage, isVideo, getUnixTime, + pictshareAvatarThumbnail, } from '../utils'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@ -248,7 +249,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> { <li className="list-inline-item"> <span>{i18n.t('by')} </span> <Link className="text-info" to={`/u/${post.creator_name}`}> - {post.creator_name} + {post.creator_avatar && ( + <img + height="32" + width="32" + src={pictshareAvatarThumbnail(post.creator_avatar)} + class="rounded-circle mr-1" + /> + )} + <span>{post.creator_name}</span> </Link> {this.isMod && ( <span className="mx-1 badge badge-light"> diff --git a/ui/src/components/search.tsx b/ui/src/components/search.tsx index bba7c5ad..d92c06e1 100644 --- a/ui/src/components/search.tsx +++ b/ui/src/components/search.tsx @@ -19,6 +19,7 @@ import { fetchLimit, routeSearchTypeToEnum, routeSortTypeToEnum, + pictshareAvatarThumbnail, } from '../utils'; import { PostListing } from './post-listing'; import { SortSelect } from './sort-select'; @@ -286,7 +287,19 @@ export class Search extends Component<any, SearchState> { <Link className="text-info" to={`/u/${(i.data as UserView).name}`} - >{`/u/${(i.data as UserView).name}`}</Link> + > + {(i.data as UserView).avatar && ( + <img + height="32" + width="32" + src={pictshareAvatarThumbnail( + (i.data as UserView).avatar + )} + class="rounded-circle mr-1" + /> + )} + <span>{`/u/${(i.data as UserView).name}`}</span> + </Link> </span> <span>{` - ${ (i.data as UserView).comment_score diff --git a/ui/src/components/sidebar.tsx b/ui/src/components/sidebar.tsx index 85f81a30..0e95666f 100644 --- a/ui/src/components/sidebar.tsx +++ b/ui/src/components/sidebar.tsx @@ -8,7 +8,7 @@ import { UserView, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; -import { mdToHtml, getUnixTime } from '../utils'; +import { mdToHtml, getUnixTime, pictshareAvatarThumbnail } from '../utils'; import { CommunityForm } from './community-form'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@ -194,7 +194,15 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { {this.props.moderators.map(mod => ( <li class="list-inline-item"> <Link class="text-info" to={`/u/${mod.user_name}`}> - {mod.user_name} + {mod.avatar && ( + <img + height="32" + width="32" + src={pictshareAvatarThumbnail(mod.avatar)} + class="rounded-circle mr-1" + /> + )} + <span>{mod.user_name}</span> </Link> </li> ))} diff --git a/ui/src/components/user.tsx b/ui/src/components/user.tsx index 6d6a2e0c..e97b26f9 100644 --- a/ui/src/components/user.tsx +++ b/ui/src/components/user.tsx @@ -58,6 +58,7 @@ interface UserState { sort: SortType; page: number; loading: boolean; + avatarLoading: boolean; userSettingsForm: UserSettingsForm; userSettingsLoading: boolean; deleteAccountLoading: boolean; @@ -78,6 +79,7 @@ export class User extends Component<any, UserState> { number_of_comments: null, comment_score: null, banned: null, + avatar: null, }, user_id: null, username: null, @@ -87,6 +89,7 @@ export class User extends Component<any, UserState> { posts: [], admins: [], loading: true, + avatarLoading: false, view: this.getViewFromProps(this.props), sort: this.getSortTypeFromProps(this.props), page: this.getPageFromProps(this.props), @@ -96,6 +99,7 @@ export class User extends Component<any, UserState> { default_sort_type: null, default_listing_type: null, lang: null, + avatar: null, auth: null, }, userSettingsLoading: null, @@ -203,7 +207,17 @@ export class User extends Component<any, UserState> { ) : ( <div class="row"> <div class="col-12 col-md-8"> - <h5>/u/{this.state.user.name}</h5> + <h5> + {this.state.user.avatar && ( + <img + height="80" + width="80" + src={this.state.user.avatar} + class="rounded-circle mr-2" + /> + )} + <span>/u/{this.state.user.name}</span> + </h5> {this.selects()} {this.state.view == View.Overview && this.overview()} {this.state.view == View.Comments && this.comments()} @@ -425,6 +439,39 @@ export class User extends Component<any, UserState> { <div class="form-group"> <div class="col-12"> <label> + <T i18nKey="avatar">#</T> + </label> + <form class="d-inline"> + <label + htmlFor="file-upload" + class="pointer ml-4 text-muted small font-weight-bold" + > + <img + height="80" + width="80" + src={ + this.state.userSettingsForm.avatar + ? this.state.userSettingsForm.avatar + : 'https://via.placeholder.com/300/000?text=Avatar' + } + class="rounded-circle" + /> + </label> + <input + id="file-upload" + type="file" + accept="image/*,video/*" + name="file" + class="d-none" + disabled={!UserService.Instance.user} + onChange={linkEvent(this, this.handleImageUpload)} + /> + </form> + </div> + </div> + <div class="form-group"> + <div class="col-12"> + <label> <T i18nKey="language">#</T> </label> <select @@ -739,6 +786,38 @@ export class User extends Component<any, UserState> { this.setState(this.state); } + handleImageUpload(i: User, event: any) { + event.preventDefault(); + let file = event.target.files[0]; + const imageUploadUrl = `/pictshare/api/upload.php`; + const formData = new FormData(); + formData.append('file', file); + + i.state.avatarLoading = true; + i.setState(i.state); + + fetch(imageUploadUrl, { + method: 'POST', + body: formData, + }) + .then(res => res.json()) + .then(res => { + let url = `${window.location.origin}/pictshare/${res.url}`; + if (res.filetype == 'mp4') { + url += '/raw'; + } + i.state.userSettingsForm.avatar = url; + console.log(url); + i.state.avatarLoading = false; + i.setState(i.state); + }) + .catch(error => { + i.state.avatarLoading = false; + i.setState(i.state); + alert(error); + }); + } + handleUserSettingsSubmit(i: User, event: any) { event.preventDefault(); i.state.userSettingsLoading = true; @@ -802,6 +881,7 @@ export class User extends Component<any, UserState> { this.state.userSettingsForm.default_listing_type = UserService.Instance.user.default_listing_type; this.state.userSettingsForm.lang = UserService.Instance.user.lang; + this.state.userSettingsForm.avatar = UserService.Instance.user.avatar; } document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`; window.scrollTo(0, 0); diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 8b860887..7020ea49 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -80,11 +80,13 @@ export interface User { default_sort_type: SortType; default_listing_type: ListingType; lang: string; + avatar?: string; } export interface UserView { id: number; name: string; + avatar?: string; fedi_name: string; published: string; number_of_posts: number; @@ -98,6 +100,7 @@ export interface CommunityUser { id: number; user_id: number; user_name: string; + avatar?: string; community_id: number; community_name: string; published: string; @@ -116,6 +119,7 @@ export interface Community { published: string; updated?: string; creator_name: string; + creator_avatar?: string; category_name: string; number_of_subscribers: number; number_of_posts: number; @@ -141,6 +145,7 @@ export interface Post { published: string; updated?: string; creator_name: string; + creator_avatar?: string; community_name: string; community_removed: boolean; community_deleted: boolean; @@ -172,6 +177,7 @@ export interface Comment { banned: boolean; banned_from_community: boolean; creator_name: string; + creator_avatar?: string; score: number; upvotes: number; downvotes: number; @@ -474,6 +480,7 @@ export interface UserSettingsForm { default_sort_type: SortType; default_listing_type: ListingType; lang: string; + avatar?: string; auth: string; } diff --git a/ui/src/translations/en.ts b/ui/src/translations/en.ts index 60a73a30..10862081 100644 --- a/ui/src/translations/en.ts +++ b/ui/src/translations/en.ts @@ -28,6 +28,7 @@ export const en = { cancel: 'Cancel', preview: 'Preview', upload_image: 'upload image', + avatar: 'Avatar', formatting_help: 'formatting help', view_source: 'view source', unlock: 'unlock', diff --git a/ui/src/utils.ts b/ui/src/utils.ts index 98645fe3..29aabdcf 100644 --- a/ui/src/utils.ts +++ b/ui/src/utils.ts @@ -338,3 +338,10 @@ export function objectFlip(obj: any) { }); return ret; } + +export function pictshareAvatarThumbnail(src: string): string { + // sample url: http://localhost:8535/pictshare/gs7xuu.jpg + let split = src.split('pictshare'); + let out = `${split[0]}pictshare/96x96${split[1]}`; + return out; +} diff --git a/ui/translation_report.ts b/ui/translation_report.ts index da4cd3aa..1975cfa5 100644 --- a/ui/translation_report.ts +++ b/ui/translation_report.ts @@ -10,15 +10,15 @@ import { nl } from './src/translations/nl'; import { it } from './src/translations/it'; let files = [ - {t: de, n: 'de'}, - {t: eo, n: 'eo'}, - {t: es, n: 'es'}, - {t: fr, n: 'fr'}, - {t: it, n: 'it'}, - {t: nl, n: 'nl'}, - {t: ru, n: 'ru'}, - {t: sv, n: 'sv'}, - {t: zh, n: 'zh'}, + { t: de, n: 'de' }, + { t: eo, n: 'eo' }, + { t: es, n: 'es' }, + { t: fr, n: 'fr' }, + { t: it, n: 'it' }, + { t: nl, n: 'nl' }, + { t: ru, n: 'ru' }, + { t: sv, n: 'sv' }, + { t: zh, n: 'zh' }, ]; let masterKeys = Object.keys(en.translation); @@ -27,7 +27,7 @@ report += '--- | --- | ---\n'; for (let file of files) { let keys = Object.keys(file.t.translation); - let pct: number = (keys.length / masterKeys.length * 100); + let pct: number = (keys.length / masterKeys.length) * 100; let missing = difference(masterKeys, keys); report += `${file.n} | ${pct.toFixed(0)}% | ${missing} \n`; } |