core: add avatar provider API

This commit is contained in:
undefined committed 2021-04-19 23:39:51 +08:00
1 parent 2db1570d1b
commit f1f956202f
42 files changed
+122 -80

No files matched your search

+1 -1
View File
@@ -7,7 +7,7 @@
"main": "package.json",
"scripts": {
"build": "node build/prepare.js && tsc -b",
"build:watch": "node build/build.js && tsc -b --watch",
"build:watch": "node build/prepare.js && tsc -b --watch",
"build:ui": "node packages/ui-default/build",
"build:ui:dev": "node packages/ui-default/build --dev",
"build:ui:production": "node packages/ui-default/build --production",
-2
View File
@@ -35,7 +35,6 @@ __langname: 한국어
'Role {1} already exists in domain {0}.': '역할 {1}이(가) 이미 {0}에 존재합니다.'
'Solved {0} problems, RP: {1} (No. {2})': '총 {0} 문제 해결.RP: {1} (No. {2})'
'The value `{1}` of {0} already exists.': '{0} 의 값 `{1}` 이 이미 존재합니다.'
'We use <a href="https://en.gravatar.com/" target="_blank">Gravatar</a> to present your avatar icon.': '<a href="https://cn.gravatar.com/" target="_blank">Gravatar</a>를 만들어 보세요.'
'Yes': 예
"Effects only when Difficulty is not 'Use algorithm calculated'.": 난이도가 알고리즘에 의해 정해지지 않은 경우에만 적용됩니다.
"Split by ', '.": “, ”“,”으로 구분
@@ -312,7 +311,6 @@ Formula blocks: 공식 블록
fs_upload: 업로드
Gender Visibility: 성별 공개
Gender: 성
Gravatar Email: Gravatar 이메일
Hard Deadline: 마감일
Hash: 해시
Have ALL PERMISSIONS in this domain: 이 그룹에 대한 모든 권한
+1 -2
View File
@@ -35,7 +35,6 @@ __langname: 简体中文
'Role {1} already exists in domain {0}.': '域 {0} 中已存在角色 {1}。'
'Solved {0} problems, RP: {1} (No. {2})': '解决了 {0} 道题目,RP: {1} (No. {2})'
'The value `{1}` of {0} already exists.': '{0} 的值 `{1}` 已经存在。'
'We use <a href="https://en.gravatar.com/" target="_blank">Gravatar</a> to present your avatar icon.': '我们使用 <a href="https://cn.gravatar.com/" target="_blank">Gravatar</a> 服务来呈现您的头像。'
'Yes': 是
"Effects only when Difficulty is not 'Use algorithm calculated'.": 仅当难度不为“使用算法计算”时才起效。
"Split by ',' '.'": 由“, ”或“,”分隔。
@@ -81,6 +80,7 @@ Attend Contest: 参加比赛
Attend contests: 参加比赛
Attended: 已参加
author: 作者
Avatar: 头像
Be Copied: 被复制
Begin at: 开始于
Begin Date: 开始日期
@@ -326,7 +326,6 @@ Formula blocks: 公式区块
fs_upload: 上传文件
Gender Visibility: 性别可见性
Gender: 性别
Gravatar Email: Gravatar Email 地址
Hard Deadline: 最终截止时间
Hash: 散列
Have ALL PERMISSIONS in this domain: 在此域中拥有全部权限
-2
View File
@@ -195,7 +195,6 @@ Forgot password or username?: 忘記密碼或者使用者名?
Formula blocks: 公式區塊
Gender Visibility: 性別可見性
Gender: 性別
Gravatar Email: Gravatar Email 地址
Hard Deadline: 最終截止時間
Have ALL PERMISSIONS in this domain: 在此域中擁有全部許可權
Help: 幫助
@@ -539,7 +538,6 @@ Visible to registered users: 對註冊使用者可見
Visit: 檢視
Vote problem solutions: 為題解投票
Vote: 投票
We use Gravatar to present your avatar icon.: 我們使用 Gravatar 服務來呈現您的頭像。
We will check code similarity after this contest.: 我們會在賽後檢查程式碼相似度。
WeChat Visibility: 微信可見性
WeChat: 微信
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "2.23.6",
"version": "2.23.7",
"bin": "bin/hydrooj.js",
"main": "dist/loader.js",
"typings": "dist/loader.d.ts",
+1
View File
@@ -13,6 +13,7 @@ export const builtinLib = [
'jwt', 'download', 'i18n', 'mail', 'useragent',
'crypto', 'misc', 'paginate', 'hash.hydro', 'rank',
'validator', 'ui', 'testdataConfig', 'difficulty', 'content',
'avatar',
];
export const builtinModel = [
+2 -2
View File
@@ -14,7 +14,7 @@ import { DOMAIN_SETTINGS, DOMAIN_SETTINGS_BY_KEY } from '../model/setting';
import {
DEFAULT_NODES, PERM, PERMS_BY_FAMILY, PRIV,
} from '../model/builtin';
import { gravatar } from '../lib/misc';
import avatar from '../lib/avatar';
import paginate from '../lib/paginate';
import {
Route, Handler, Types, param, query, post,
@@ -297,7 +297,7 @@ class DomainSearchHandler extends Handler {
async get(domainId: string, q: string) {
const ddocs = await domain.getPrefixSearch(q, 20);
for (let i = 0; i < ddocs.length; i++) {
ddocs[i].gravatar = ddocs[i].gravatar ? gravatar(ddocs[i].gravatar) : '/img/team_avatar.png';
ddocs[i].avatarUrl = ddocs[i].avatar ? avatar(ddocs[i].avatar, 64) : '/img/team_avatar.png';
}
this.response.body = ddocs;
}
+9 -9
View File
@@ -10,8 +10,9 @@ import * as bus from '../service/bus';
import {
Route, Connection, Handler, ConnectionHandler, param, Types,
} from '../service/server';
import * as misc from '../lib/misc';
import { md5 } from '../lib/crypto';
import { isPassword, isEmail } from '../lib/validator';
import avatar from '../lib/avatar';
import * as mail from '../lib/mail';
import * as contest from '../model/contest';
import message from '../model/message';
@@ -24,7 +25,6 @@ import * as discussion from '../model/discussion';
import token from '../model/token';
import * as training from '../model/training';
import { PERM, PRIV } from '../model/builtin';
import { isPassword, isEmail } from '../lib/validator';
const { geoip, useragent } = global.Hydro.lib;
@@ -309,13 +309,13 @@ class HomeDomainCreateHandler extends Handler {
@param('id', Types.Name)
@param('name', Types.Title)
@param('bulletin', Types.Content)
@param('gravatar', Types.Content, true, isEmail)
async post(_: string, id: string, name: string, bulletin: string, gravatar: string) {
@param('avatar', Types.Content, true, isEmail)
async post(_: string, id: string, name: string, bulletin: string, avatar: string) {
const doc = await domain.get(id);
if (doc) throw new DomainAlreadyExistsError(id);
gravatar = gravatar || this.user.gravatar || this.user.mail || 'guest@hydro.local';
avatar = avatar || this.user.avatar || `gravatar:${this.user.mail}` || 'gravatar:guest@hydro.local';
const domainId = await domain.add(id, this.user._id, name, bulletin);
await domain.edit(domainId, { gravatar });
await domain.edit(domainId, { avatar });
await domain.setUserRole(domainId, this.user._id, 'root');
this.response.redirect = this.url('domain_dashboard', { domainId });
this.response.body = { domainId };
@@ -338,7 +338,7 @@ class HomeMessagesHandler extends Handler {
if (!parsed[target]) {
parsed[target] = {
_id: target,
udoc: { ...udict[target], gravatar: misc.gravatar(udict[target].gravatar) },
udoc: { ...udict[target], avatarUrl: avatar(udict[target].avatar) },
messages: [],
};
}
@@ -358,7 +358,7 @@ class HomeMessagesHandler extends Handler {
async postSend(domainId: string, uid: number, content: string) {
const udoc = await user.getById('system', uid);
if (!udoc) throw new UserNotFoundError(uid);
if (udoc.gravatar) udoc.gravatar = misc.gravatar(udoc.gravatar);
if (udoc.avatar) udoc.avatarUrl = avatar(udoc.avatar);
const mdoc = await message.send(this.user._id, uid, content, message.FLAG_UNREAD);
this.back({ mdoc, udoc });
}
@@ -391,7 +391,7 @@ class HomeMessagesConnectionHandler extends ConnectionHandler {
async onMessageReceived(uid: number, mdoc: Mdoc) {
if (uid !== this.user._id) return;
const udoc = await user.getById(this.domainId, mdoc.from);
udoc.gravatar_url = misc.gravatar(udoc.gravatar, 64);
udoc.avatarUrl = avatar(udoc.avatar, 64);
this.send({ udoc, mdoc });
}
+3 -3
View File
@@ -4,6 +4,7 @@ import {
UserNotFoundError, SystemError, BlacklistedError,
UserFacingError, LoginError,
} from '../error';
import { User } from '../interface';
import {
Route, Handler, Types, param, post,
} from '../service/server';
@@ -17,9 +18,8 @@ import * as system from '../model/system';
import { PERM, PRIV } from '../model/builtin';
import { isEmail, isPassword, isUname } from '../lib/validator';
import { sendMail } from '../lib/mail';
import * as misc from '../lib/misc';
import paginate from '../lib/paginate';
import { User } from '../interface';
import avatar from '../lib/avatar';
class UserLoginHandler extends Handler {
async get() {
@@ -241,7 +241,7 @@ class UserSearchHandler extends Handler {
if (udoc) udocs.push(udoc);
if (!exactMatch) udocs.push(...await user.getPrefixList(domainId, q, 20));
for (const i in udocs) {
udocs[i].gravatar = misc.gravatar(udocs[i].gravatar || '');
udocs[i].avatarUrl = avatar(udocs[i].avatar);
}
this.response.body = udocs;
}
+1 -1
View File
@@ -301,7 +301,7 @@ export interface DomainDoc extends Record<string, any> {
_id: string,
owner: number,
roles: Dictionary<string>,
gravatar: string,
avatar: string,
bulletin: string,
_join?: any,
host?: string,
+20
View File
@@ -0,0 +1,20 @@
import { md5 } from './crypto';
type AvatarProvider = (src: string, size: number) => string;
export const providers: Record<string, AvatarProvider> = {
gravatar: (email, size) => `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${size || 32}`,
qq: (id) => `//q1.qlogo.cn/g?b=qq&nk=${/(\d+)/g.exec(id)[1]}&s=640`,
github: (id, size) => `//github.com/${id}.png?size=${Math.min(size, 460)}`,
};
function avatar(src: string, size = 64) {
const index = src.indexOf(':');
if (index === -1) return providers.gravatar('', size);
const [provider, str] = [src.substr(0, index), src.substr(index + 1, src.length)];
if (!providers[provider] || !str) return providers.gravatar('', size);
return providers[provider](str, size);
}
export default avatar;
global.Hydro.lib.avatar = avatar;
+1 -6
View File
@@ -1,7 +1,6 @@
import AnsiUp from 'ansi_up';
import { ObjectID } from 'mongodb';
import { size, formatSeconds } from '@hydrooj/utils/lib/utils';
import { md5 } from './crypto';
export { size, formatSeconds } from '@hydrooj/utils/lib/utils';
@@ -11,10 +10,6 @@ export function ansiToHtml(str: string) {
return AU.ansi_to_html(str);
}
export function gravatar(email: string, s = 32) {
return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${s}`;
}
export function datetimeSpan(dt: Date | ObjectID, relative = true, format = '%Y-%m-%d %H:%M:%S') {
if (!dt) return 'DATETIME_SPAN_ERROR';
if (dt instanceof ObjectID) dt = new Date(dt.generationTime * 1000);
@@ -53,5 +48,5 @@ export function* paginate(page: number, numPages: number) {
}
global.Hydro.lib.misc = {
gravatar, datetimeSpan, paginate, size, formatSeconds, ansiToHtml,
datetimeSpan, paginate, size, formatSeconds, ansiToHtml,
};
+1 -1
View File
@@ -47,7 +47,7 @@ class DomainModel {
name,
bulletin,
roles: {},
gravatar: '',
avatar: '',
};
await bus.serial('domain/create', ddoc);
await coll.insertOne(ddoc);
+3 -3
View File
@@ -107,8 +107,8 @@ PreferenceSetting(
);
AccountSetting(
Setting('setting_info', 'gravatar', null, 'text', 'Gravatar Email',
'We use <a href="https://en.gravatar.com/" target="_blank">Gravatar</a> to present your avatar icon.'),
Setting('setting_info', 'avatar', '', 'text', 'Avatar',
'Allow using gravatar:email qq:id github:name format.'),
Setting('setting_info', 'qq', null, 'text', 'QQ'),
Setting('setting_info', 'gender', builtin.USER_GENDER_OTHER, builtin.USER_GENDER_RANGE, 'Gender'),
Setting('setting_info', 'bio', null, 'markdown', 'Bio'),
@@ -122,7 +122,7 @@ AccountSetting(
DomainSetting(
Setting('setting_domain', 'name', 'New domain', 'text', 'name'),
Setting('setting_domain', 'gravatar', '', 'text', 'gravatar', 'Will be used as the domain icon.'),
Setting('setting_domain', 'avatar', '', 'text', 'avatar', 'Will be used as the domain icon.'),
Setting('setting_domain', 'bulletin', '', 'markdown', 'Bulletin'),
Setting('setting_storage', 'host', '', 'text', 'Custom host', null, FLAG_HIDDEN | FLAG_DISABLED),
);
+2 -2
View File
@@ -101,7 +101,7 @@ class UserModel {
_id: 0,
uname: 'Unknown User',
unameLower: 'unknown user',
gravatar: 'unknown@hydro.local',
avatar: 'gravatar:unknown@hydro.local',
mail: 'unknown@hydro.local',
mailLower: 'unknown@hydro.local',
salt: '',
@@ -213,7 +213,7 @@ class UserModel {
loginat: new Date(),
loginip: regip,
priv,
gravatar: mail,
avatar: `gravatar:${mail}`,
});
} catch (e) {
logger.warn('%o', e);
+7 -1
View File
@@ -1,6 +1,7 @@
/* eslint-disable no-await-in-loop */
import { DomainDoc } from './loader';
import type { DomainDoc, Udoc } from './interface';
import domain from './model/domain';
import user from './model/user';
import problem, { Field, Pdoc } from './model/problem';
export async function iterateAllDomain(cb: (ddoc: DomainDoc, current?: number, total?: number) => Promise<any>) {
@@ -8,6 +9,11 @@ export async function iterateAllDomain(cb: (ddoc: DomainDoc, current?: number, t
for (const i in ddocs) await cb(ddocs[i], +i, ddocs.length);
}
export async function iterateAllUser(cb: (udoc: Udoc, current?: number, total?: number) => Promise<any>) {
const udocs = await user.getMulti().toArray();
for (const i in udocs) await cb(udocs[i], +i, udocs.length);
}
interface PartialPdoc extends Pdoc {
[key: string]: any,
}
+3 -2
View File
@@ -28,8 +28,8 @@ import {
CsrfTokenError, InvalidOperationError, MethodNotAllowedError,
NotFoundError, HydroError, SystemError,
} from '../error';
import * as misc from '../lib/misc';
import { isContent, isName, isTitle } from '../lib/validator';
import avatar from '../lib/avatar';
import user from '../model/user';
import domain from '../model/domain';
import * as system from '../model/system';
@@ -322,7 +322,7 @@ export class HandlerCommon {
async renderHTML(name: string, context: any): Promise<string> {
const UserContext: any = {
...this.user,
gravatar: misc.gravatar(this.user.gravatar || '', 128),
avatar: avatar(this.user.avatar || '', 128),
perm: this.user.perm.toString(),
viewLang: this.translate('__id'),
};
@@ -508,6 +508,7 @@ export class Handler extends HandlerCommon {
this.user = await user.getById(domainId, this.session.uid, this.session.scope);
}
if (this.user._id === 0 && this.session.viewLang) this.user.viewLang = this.session.viewLang;
this.user.avatarUrl = avatar(this.user.avatar, 128);
this.csrfToken = this.getCsrfToken(this.session._id || String.random(32));
this.UiContext.csrfToken = this.csrfToken;
this.loginMethods = filter(Object.keys(global.Hydro.lib), (str) => str.startsWith('oauth_'))
+9 -1
View File
@@ -10,12 +10,13 @@ import { Progress } from './ui';
import { Rdoc } from './interface';
import { Logger } from './logger';
import { streamToBuffer } from './utils';
import { iterateAllDomain, iterateAllProblem } from './pipelineUtils';
import { iterateAllDomain, iterateAllProblem, iterateAllUser } from './pipelineUtils';
import gridfs from './service/gridfs';
import storage from './service/storage';
import db from './service/db';
import difficultyAlgorithm from './lib/difficulty';
import problem from './model/problem';
import user from './model/user';
import domain from './model/domain';
import * as document from './model/document';
import * as system from './model/system';
@@ -299,6 +300,13 @@ const scripts: UpgradeScript[] = [
});
return true;
},
async function _22_23() {
await iterateAllUser(async (udoc) => {
if (!udoc.gravatar) return;
await user.setById(udoc._id, { avatar: `gravatar:${udoc.gravatar}` }, { gravatar: '' });
});
return true;
},
];
export default scripts;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/migrate-vijos",
"version": "1.4.1",
"version": "1.4.2",
"main": "package.json",
"repository": "git@github.com:hydro-dev/Hydro.git",
"author": "undefined <i@undefined.moe>",
+5 -2
View File
@@ -23,7 +23,7 @@ const tasks = {
hash: doc.hash.split('|')[1],
hashType: doc.hash.split('|')[0] === 'vj4' ? 'hydro' : doc.hash.split('|')[0],
priv: doc.priv,
gravatar: doc.gravatar,
avatar: `gravatar:${doc.gravatar}`,
mail: doc.mail,
mailLower: doc.mail_lower,
regat: doc.regat,
@@ -256,7 +256,10 @@ const tasks = {
return res;
},
},
gravatar: 'gravatar',
gravatar: {
field: 'avatar',
processer: (gravatar) => `gavatar:${gravatar}`,
},
bulletin: 'bulletin',
},
'fs.files': async (doc) => doc,
+2 -2
View File
@@ -104,8 +104,8 @@ export class QueryProblemSetAPIHandler extends Handler {
nickname: udoc.displayName || udoc.uname,
bio: udoc.bio,
avatar: {
type: 'gravatar',
key: udoc.gravatar,
type: udoc.avatar.split(':')[0],
key: udoc.avatar.split(':')[1],
},
isAdmin: udoc.hasPriv(PRIV.PRIV_MANAGE_ALL_DOMAIN) || udoc.hasPerm(PERM.PERM_ADMIN),
acceptedProblemCount: udoc.nAccept,
+2 -2
View File
@@ -9,7 +9,7 @@ const Xss = require('xss');
const { findFileSync } = require('@hydrooj/utils/lib/utils');
const markdown = require('./markdown');
const { misc, buildContent } = global.Hydro.lib;
const { misc, buildContent, avatar } = global.Hydro.lib;
const xss = new Xss.FilterXSS({
whiteList: {
@@ -187,7 +187,7 @@ env.addGlobal('typeof', (o) => typeof o);
env.addGlobal('datetimeSpan', misc.datetimeSpan);
env.addGlobal('paginate', misc.paginate);
env.addGlobal('size', misc.size);
env.addGlobal('gravatar', misc.gravatar);
env.addGlobal('avatarUrl', avatar);
env.addGlobal('formatSeconds', misc.formatSeconds);
env.addGlobal('model', global.Hydro.model);
env.addGlobal('ui', global.Hydro.ui);
@@ -16,7 +16,7 @@ function renderItem(domain) {
return tpl`
<div class="media">
<div class="media__left medium">
<img class="small domain-profile-avatar" src="${domain.gravatar}" width="30" height="30">
<img class="small domain-profile-avatar" src="${domain.avatarUrl}" width="30" height="30">
</div>
<div class="media__body medium">
<div class="domain-select__name">${domain.name}</div>
@@ -16,7 +16,7 @@ function renderItem(user) {
return tpl`
<div class="media">
<div class="media__left medium">
<img class="small user-profile-avatar" src="${user.gravatar}" width="30" height="30">
<img class="small user-profile-avatar" src="${user.avatarUrl}" width="30" height="30">
</div>
<div class="media__body medium">
<div class="user-select__uname">${user.uname}</div>
@@ -23,7 +23,7 @@ const messagePage = new AutoloadPage('messagePage', () => {
console.log('Received message', msg);
if (msg.mdoc.flag & FLAG_ALERT) {
// Is alert
new InfoDialog({
new InfoDialog({
$body: tpl`
<div class="typo">
<p>${i18n('Message from {0}:', msg.mdoc.from)}</p>
@@ -34,7 +34,7 @@ const messagePage = new AutoloadPage('messagePage', () => {
// Is message
new Notification({
title: msg.udoc.uname,
avatar: msg.udoc.gravatar_url,
avatar: msg.udoc.avatarUrl,
message: msg.mdoc.content,
duration: 15000,
action: () => window.open(`/home/messages?uid=${msg.udoc._id}`, '_blank'),
@@ -52,8 +52,8 @@ export default connect(mapStateToProps, null)(class MessagePadDialogueContentCon
isSelf={reply.from === UserContext._id}
faceUrl={
reply.from === UserContext._id
? UserContext.gravatar
: this.props.item.udoc.gravatar
? UserContext.avatarUrl
: this.props.item.udoc.avatarUrl
}
>
<div>{reply.content}</div>
@@ -39,7 +39,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(class MessagePadDial
key={dialogue._id}
userName={dialogue.udoc.uname}
summary={(_.last(dialogue.messages)?.content) || ''}
faceUrl={dialogue.udoc.gravatar}
faceUrl={dialogue.udoc.avatarUrl}
active={dialogue._id === this.props.activeId}
onClick={() => this.props.handleClick(dialogue._id)}
/>
+3
View File
@@ -1,4 +1,7 @@
{
"exclude": [
"node_modules"
],
"compilerOptions": {
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
-2
View File
@@ -34,7 +34,6 @@ __langname: 简体中文
'Role {1} already exists in domain {0}.': '域 {0} 中已存在角色 {1}。'
'Solved {0} problems, RP: {1} (No. {2})': '解决了 {0} 道题目,RP: {1} (No. {2})'
'The value `{1}` of {0} already exists.': '{0} 的值 `{1}` 已经存在。'
'We use <a href="https://en.gravatar.com/" target="_blank">Gravatar</a> to present your avatar icon.': '我们使用 <a href="https://cn.gravatar.com/" target="_blank">Gravatar</a> 服务来呈现您的头像。'
'Yes': 是
"Effects only when Difficulty is not 'Use algorithm calculated'.": 仅当难度不为“使用算法计算”时才起效。
"Split by ', '.": 由“, ”或“,”分隔。
@@ -317,7 +316,6 @@ Formula blocks: 公式区块
fs_upload: 上传文件
Gender Visibility: 性别可见性
Gender: 性别
Gravatar Email: Gravatar Email 地址
Hard Deadline: 最终截止时间
Hash: 散列
Have ALL PERMISSIONS in this domain: 在此域中拥有全部权限
-2
View File
@@ -194,7 +194,6 @@ Forgot password or username?: 忘記密碼或者使用者名?
Formula blocks: 公式區塊
Gender Visibility: 性別可見性
Gender: 性別
Gravatar Email: Gravatar Email 地址
Hard Deadline: 最終截止時間
Have ALL PERMISSIONS in this domain: 在此域中擁有全部許可權
Help: 幫助
@@ -538,7 +537,6 @@ Visible to registered users: 對註冊使用者可見
Visit: 檢視
Vote problem solutions: 為題解投票
Vote: 投票
We use Gravatar to present your avatar icon.: 我們使用 Gravatar 服務來呈現您的頭像。
We will check code similarity after this contest.: 我們會在賽後檢查程式碼相似度。
WeChat Visibility: 微信可見性
WeChat: 微信
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/ui-default",
"version": "4.6.6",
"version": "4.6.7",
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0",
"main": "hydro.js",
@@ -31,7 +31,7 @@
<li class="dczcomments__item">
<div class="media">
<div class="media__left top">
<img src="{{ gravatar(handler.user.gravatar, 60) }}" width="60" height="60" class="medium user-profile-avatar">
<img src="{{ avatarUrl(handler.user.avatar, 60) }}" width="60" height="60" class="medium user-profile-avatar">
</div>
<div class="media__body top no-heading">
<div class="hasjs--hide">
@@ -55,7 +55,7 @@
<li class="dczcomments__reply commentbox-container">
<div class="media">
<div class="media__left top">
<img src="{{ gravatar(handler.user.gravatar, 50) }}" width="50" height="50" class="medium user-profile-avatar">
<img src="{{ avatarUrl(handler.user.avatar, 50) }}" width="50" height="50" class="medium user-profile-avatar">
</div>
<div class="media__body top no-heading">
<div class="commentbox-placeholder"></div>
@@ -72,7 +72,7 @@
<li class="dczcomments__item">
<div class="media">
<div class="media__left top">
<img src="{{ gravatar(udoc.gravatar, 60) }}" width="60" height="60" class="medium user-profile-avatar">
<img src="{{ avatarUrl(udoc.avatar, 60) }}" width="60" height="60" class="medium user-profile-avatar">
</div>
<div class="media__body top no-heading">
<div class="clearfix">
@@ -108,7 +108,7 @@
<li class="dczcomments__reply{% if mode_create %} commentbox-container{% endif %}">
<div class="media">
<div class="media__left top">
<img src="{{ gravatar(udoc.gravatar, 50) }}" width="50" height="50" class="medium user-profile-avatar">
<img src="{{ avatarUrl(udoc.avatar, 50) }}" width="50" height="50" class="medium user-profile-avatar">
</div>
<div class="media__body top no-heading">
<div class="clearfix">
@@ -2,7 +2,7 @@
{% if udoc %}
<span class="user-profile-link">
{% if avatar %}
<img class="small user-profile-avatar v-center" src="{{ gravatar(udoc.gravatar|default('')) }}" width="20" height="20">
<img class="small user-profile-avatar v-center" src="{{ avatarUrl(udoc.avatar|default('')) }}" width="20" height="20">
{% endif %}
<a class="user-profile-name" href="{{ url('user_detail', uid=udoc._id) }}">
{% if udoc.displayName %}
@@ -94,7 +94,7 @@
<div class="section__body">
<div class="media sidebar-user-profile">
<div class="media__left">
<img src="{{ gravatar(udoc.gravatar, 80) }}" width="80" height="80" class="medium user-profile-avatar">
<img src="{{ avatarUrl(udoc.avatar, 80) }}" width="80" height="80" class="medium user-profile-avatar">
</div>
<div class="media__body">
<p>
@@ -36,8 +36,8 @@
required:true
}) }}
{{ form.form_text({
label:'Gravatar',
name:'gravatar'
label:'Avatar',
name:'avatar'
}) }}
<div class="row"><div class="columns">
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
@@ -20,7 +20,7 @@
</div>
<div class="section__body typo">
<dl class="large horizontal">
<dt>{{ _('Gravatar') }}</dt><dd><img src="{{ gravatar(domain.gravatar) }}" width="32" height="32"></dd>
<dt>{{ _('Avatar') }}</dt><dd><img src="{{ avatarUrl(domain.avatar) }}" width="32" height="32"></dd>
<dt>{{ _('Name') }}</dt><dd>{{ domain.name }}</dd>
</dl>
</div>
@@ -30,7 +30,7 @@
{%- for ddoc in ddocs -%}
<tr>
<td class="col--icon">
<img src="{{ gravatar(ddoc.gravatar) }}" width="32" height="32">
<img src="{{ avatarUrl(ddoc.avatar, 32) }}" width="32" height="32">
</td>
<td class="col--name">
{{ ddoc['name'] }} ({{ ddoc['_id'] }})
@@ -19,7 +19,7 @@
</div>
<div class="section__body typo">
<dl class="large horizontal">
<dt>{{ _('Gravatar') }}</dt><dd><img src="{{ gravatar(domain.gravatar) }}" width="32" height="32"></dd>
<dt>{{ _('Avatar') }}</dt><dd><img src="{{ avatarUrl(domain.avatar) }}" width="32" height="32"></dd>
<dt>{{ _('Name') }}</dt><dd>{{ domain.name }}</dd>
</dl>
</div>
@@ -13,7 +13,7 @@
<div class="profile-header__content">
<div class="media">
<div class="media__left">
<img src="{{ gravatar(udoc.gravatar, 120) }}" width="120" height="120" class="large user-profile-avatar">
<img src="{{ avatarUrl(udoc.avatar, 120) }}" width="120" height="120" class="large user-profile-avatar">
</div>
<div class="media__body profile-header__main">
<h1>
+1 -1
View File
@@ -10,7 +10,7 @@ const name = process.argv[2];
const IGNORE_CHECK = ['en'];
const IGNORE_MISSING = [
'?', 'AC', 'Gravatar', 'ID', 'MD5', 'URL',
'?', 'AC', 'ID', 'MD5', 'URL',
];
const RE_TEMPLATE = /_\(['"]([\s\S])*?['"]\)/gmi;
const RE_UI = /i18n\(['"]([\s\S])*?['"](,.*?)?\)/gmi;
+22 -8
View File
@@ -265,14 +265,14 @@ export namespace Time {
export function errorMessage(err: Error | string) {
const t = typeof err === 'string' ? err : err.stack;
const q = t.split('\n');
for (let i = 0; i < q.length; i++) {
if (!q[i].startsWith(' at')) continue;
if (q[i].includes(`${sep}@hydrooj${sep}`)) q[i] = q[i].split(`@hydrooj${sep}`)[1];
else if (q[i].includes(`${sep}hydrooj${sep}`)) q[i] = ` at hydrooj${sep}${q[i].split(`hydrooj${sep}`)[1]}`;
}
if (typeof err === 'string') return q.join('\n');
err.stack = q.join('\n');
const parsed = t
.replace(/[A-Z]:\\.+\\@hydrooj\\/g, '\\')
.replace(/\/.+\/@hydrooj\//g, '\\')
.replace(/[A-Z]:\\.+\\hydrooj\\/g, 'hydrooj\\')
.replace(/\/.+\/hydrooj\//g, 'hydrooj/')
.replace(/\\/g, '/');
if (typeof err === 'string') return parsed;
err.stack = parsed;
return err;
}
@@ -376,3 +376,17 @@ export async function retry(arg0: number | Function, func: any, ...args: any[])
return res;
}
}
export function CallableInstance(property = '__call__') {
let func;
if (typeof property === 'function') func = property;
else func = this.constructor.prototype[property];
const apply = function __call__(...args) { return func.apply(apply, ...args); };
Object.setPrototypeOf(apply, this.constructor.prototype);
Object.getOwnPropertyNames(func).forEach((p) => {
Object.defineProperty(apply, p, Object.getOwnPropertyDescriptor(func, p));
});
return apply;
}
CallableInstance.prototype = Object.create(Function.prototype);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/utils",
"version": "1.0.5",
"version": "1.0.6",
"description": "hydrooj utils",
"main": "package.json",
"repository": "https://github.com/hydro-dev/Hydro.git",