mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Colorized avatars
This commit is contained in:
parent
6948b1222b
commit
ff362cabbf
7 changed files with 59 additions and 13 deletions
|
@ -23,13 +23,13 @@ function AuthFrame({adminUrl, onLoad}) {
|
|||
);
|
||||
}
|
||||
|
||||
function CommentsBoxContainer({done, colorScheme}) {
|
||||
function CommentsBoxContainer({done, colorScheme, avatarSaturation}) {
|
||||
if (!done) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ShadowRoot>
|
||||
<CommentsBox colorScheme={colorScheme} />
|
||||
<CommentsBox colorScheme={colorScheme} avatarSaturation={avatarSaturation} />
|
||||
</ShadowRoot>
|
||||
);
|
||||
}
|
||||
|
@ -297,7 +297,7 @@ export default class App extends React.Component {
|
|||
return (
|
||||
<SentryErrorBoundary dsn={this.props.sentryDsn}>
|
||||
<AppContext.Provider value={this.getContextFromState()}>
|
||||
<CommentsBoxContainer done={done} colorScheme={this.props.colorScheme} />
|
||||
<CommentsBoxContainer done={done} colorScheme={this.props.colorScheme} avatarSaturation={this.props.avatarSaturation} />
|
||||
<AuthFrame adminUrl={this.props.adminUrl} onLoad={this.initSetup.bind(this)}/>
|
||||
</AppContext.Provider>
|
||||
</SentryErrorBoundary>
|
||||
|
|
|
@ -58,7 +58,7 @@ const AddForm = (props) => {
|
|||
<div className="flex mb-4 space-x-4 justify-start items-center">
|
||||
<Avatar />
|
||||
<div>
|
||||
<h4 className="text-lg font-sans font-bold mb-1 tracking-tight dark:text-neutral-300">{member.name}</h4>
|
||||
<h4 className="text-lg font-sans font-bold mb-1 tracking-tight dark:text-neutral-300">{member.name ? member.name : 'Anonymous'}</h4>
|
||||
<h6 className="text-[13px] text-neutral-400 font-sans">Now</h6>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,11 +5,49 @@ import {getInitials} from '../utils/helpers';
|
|||
class Avatar extends React.Component {
|
||||
static contextType = AppContext;
|
||||
|
||||
getHashOfString(str) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
hash = Math.abs(hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
normalizeHash(hash, min, max) {
|
||||
return Math.floor((hash % (max - min)) + min);
|
||||
}
|
||||
|
||||
generateHSL() {
|
||||
let commentMember = (this.props.comment ? this.props.comment.member : this.context.member);
|
||||
|
||||
if (!commentMember || !commentMember.name) {
|
||||
return [0,0,10];
|
||||
}
|
||||
|
||||
const saturation = this.props.saturation ? this.props.saturation : 50;
|
||||
|
||||
const hRange = [0, 360];
|
||||
const lRangeTop = Math.round(saturation / (100 / 30)) + 20;
|
||||
const lRangeBottom = lRangeTop - 10;
|
||||
const lRange = [lRangeBottom, lRangeTop];
|
||||
|
||||
const hash = this.getHashOfString(commentMember.name);
|
||||
const h = this.normalizeHash(hash, hRange[0], hRange[1]);
|
||||
const l = this.normalizeHash(hash, lRange[0], lRange[1]);
|
||||
|
||||
return [h, saturation, l];
|
||||
}
|
||||
|
||||
HSLtoString(hsl) {
|
||||
return `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`;
|
||||
}
|
||||
|
||||
getInitials() {
|
||||
let commentMember = (this.props.comment ? this.props.comment.member : this.context.member);
|
||||
|
||||
if (!commentMember || !commentMember.name) {
|
||||
return '';
|
||||
return getInitials('Anonymous');
|
||||
}
|
||||
return getInitials(commentMember.name);
|
||||
}
|
||||
|
@ -19,9 +57,14 @@ class Avatar extends React.Component {
|
|||
let initialsClasses = (this.props.size === 'small' ? 'text-sm' : 'text-base');
|
||||
let commentMember = (this.props.comment ? this.props.comment.member : this.context.member);
|
||||
|
||||
const bgColor = this.HSLtoString(this.generateHSL());
|
||||
const avatarStyle = {
|
||||
background: bgColor
|
||||
};
|
||||
|
||||
return (
|
||||
<figure className={`relative ${dimensionClasses}`}>
|
||||
<div className={`flex justify-center items-center rounded-full bg-black ${dimensionClasses}`}>
|
||||
<div className={`flex justify-center items-center rounded-full ${dimensionClasses}`} style={avatarStyle}>
|
||||
<p className={`text-white font-sans font-semibold ${initialsClasses}`}>{ this.getInitials() }</p>
|
||||
</div>
|
||||
<img className={`absolute top-0 left-0 rounded-full ${dimensionClasses}`} src={commentMember.avatar_image} alt="Avatar"/>
|
||||
|
|
|
@ -49,7 +49,7 @@ const Comment = (props) => {
|
|||
<div className={`flex flex-col ${commentMarginBottom}`}>
|
||||
<div>
|
||||
<div className="flex mb-4 space-x-4 justify-start items-center">
|
||||
<Avatar comment={comment} />
|
||||
<Avatar comment={comment} saturation={props.avatarSaturation} />
|
||||
<div>
|
||||
<h4 className="text-lg font-sans font-bold mb-1 tracking-tight dark:text-neutral-300">{comment.member.name}</h4>
|
||||
<h6 className="text-[13px] text-neutral-400 font-sans">{formatRelativeTime(comment.created_at)}</h6>
|
||||
|
@ -66,7 +66,7 @@ const Comment = (props) => {
|
|||
</div>
|
||||
{hasReplies &&
|
||||
<div className="ml-14 mt-14">
|
||||
<Replies comment={comment} />
|
||||
<Replies comment={comment} avatarSaturation={props.avatarSaturation} />
|
||||
</div>
|
||||
}
|
||||
{isInReplyMode &&
|
||||
|
|
|
@ -47,7 +47,7 @@ class CommentsBox extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const comments = !this.context.comments ? 'Loading...' : this.context.comments.slice().reverse().map(comment => <Comment comment={comment} key={comment.id} />);
|
||||
const comments = !this.context.comments ? 'Loading...' : this.context.comments.slice().reverse().map(comment => <Comment comment={comment} key={comment.id} avatarSaturation={this.props.avatarSaturation} />);
|
||||
|
||||
const containerClass = this.darkMode() ? 'dark' : '';
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ const Replies = (props) => {
|
|||
|
||||
return (
|
||||
<div>
|
||||
{comment.replies.map((reply => <Comment comment={reply} parent={comment} key={reply.id} isReply={true} />))}
|
||||
{comment.replies.map((reply => <Comment comment={reply} parent={comment} key={reply.id} isReply={true} avatarSaturation={props.avatarSaturation} />))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -35,7 +35,9 @@ function getSiteData() {
|
|||
const sentryDsn = scriptTag.dataset.sentryDsn;
|
||||
const postId = scriptTag.dataset.postId;
|
||||
const colorScheme = scriptTag.dataset.colorScheme;
|
||||
return {siteUrl, apiKey, apiUrl, sentryDsn, postId, adminUrl, colorScheme};
|
||||
const avatarSaturation = scriptTag.dataset.avatarSaturation;
|
||||
|
||||
return {siteUrl, apiKey, apiUrl, sentryDsn, postId, adminUrl, colorScheme, avatarSaturation};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
@ -55,12 +57,13 @@ function setup({siteUrl}) {
|
|||
|
||||
function init() {
|
||||
// const customSiteUrl = getSiteUrl();
|
||||
const {siteUrl: customSiteUrl, sentryDsn, postId, adminUrl, colorScheme} = getSiteData();
|
||||
const {siteUrl: customSiteUrl, sentryDsn, postId, adminUrl, colorScheme, avatarSaturation} = getSiteData();
|
||||
const siteUrl = customSiteUrl || window.location.origin;
|
||||
setup({siteUrl});
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
{<App adminUrl={adminUrl} siteUrl={siteUrl} customSiteUrl={customSiteUrl} sentryDsn={sentryDsn} postId={postId} colorScheme={colorScheme} />}
|
||||
{<App adminUrl={adminUrl} siteUrl={siteUrl} customSiteUrl={customSiteUrl} sentryDsn={sentryDsn} postId={postId} colorScheme={colorScheme} avatarSaturation={avatarSaturation} />}
|
||||
</React.StrictMode>,
|
||||
document.getElementById(ROOT_DIV_ID)
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue