A better mobile view, upgrading darkly theme.

This commit is contained in:
Dessalines 2020-08-06 17:34:45 -04:00
parent 6fa9dc599c
commit c5443b6e82
5 changed files with 841 additions and 697 deletions

View File

@ -0,0 +1,104 @@
$white: #fff;
$gray-100: #f8f9fa;
$gray-200: #ebebeb;
$gray-300: #dee2e6;
$gray-400: #ced4da;
$gray-500: #adb5bd;
$gray-600: #888;
$gray-700: #444;
$gray-800: #303030;
$gray-900: #222;
$black: #000;
$blue: #375a7f;
$indigo: #6610f2;
$purple: #6f42c1;
$pink: #e83e8c;
$red: #e74c3c;
$orange: #fd7e14;
$yellow: #f39c12;
$green: #00bc8c;
$teal: #20c997;
$cyan: #3498db;
$primary: $blue;
$secondary: $gray-700;
$success: $green;
$info: $cyan;
$warning: $yellow;
$danger: $red;
$dark: $gray-300;
$yiq-contrasted-threshold: 175;
$body-bg: $gray-900;
$body-color: $gray-300;
$link-color: $success;
$font-family-sans-serif: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
$font-size-base: 0.9375rem;
$h1-font-size: 3rem;
$h2-font-size: 2.5rem;
$h3-font-size: 2rem;
$text-muted: $gray-600;
$table-accent-bg: $gray-800;
$table-border-color: $gray-700;
$input-border-color: $body-bg;
$input-group-addon-color: $gray-500;
$input-group-addon-bg: $gray-700;
$custom-file-color: $gray-500;
$custom-file-border-color: $body-bg;
$dropdown-bg: $gray-900;
$dropdown-border-color: $gray-700;
$dropdown-divider-bg: $gray-700;
$dropdown-link-color: $white;
$dropdown-link-hover-color: $white;
$dropdown-link-hover-bg: $primary;
$nav-link-padding-x: 2rem;
$nav-link-disabled-color: $gray-500;
$nav-tabs-border-color: $gray-700;
$nav-tabs-link-hover-border-color: $nav-tabs-border-color $nav-tabs-border-color transparent;
$nav-tabs-link-active-color: $white;
$nav-tabs-link-active-border-color: $nav-tabs-border-color $nav-tabs-border-color transparent;
$navbar-padding-y: 1rem;
$navbar-dark-color: rgba($white,.6);
$navbar-dark-hover-color: $white;
$navbar-light-color: rgba($white,.6);
$navbar-light-hover-color: $white;
$navbar-light-active-color: $white;
$navbar-light-toggler-border-color: rgba($gray-900, .1);
$pagination-color: $white;
$pagination-bg: $success;
$pagination-border-width: 0;
$pagination-border-color: transparent;
$pagination-hover-color: $white;
$pagination-hover-bg: lighten($success, 10%);
$pagination-hover-border-color: transparent;
$pagination-active-bg: $pagination-hover-bg;
$pagination-active-border-color: transparent;
$pagination-disabled-color: $white;
$pagination-disabled-bg: darken($success, 15%);
$pagination-disabled-border-color: transparent;
$jumbotron-bg: $gray-800;
$card-cap-bg: $gray-700;
$card-bg: $gray-800;
$popover-bg: $gray-800;
$popover-header-bg: $gray-700;
$toast-background-color: $gray-700;
$toast-header-background-color: $gray-800;
$modal-content-bg: $gray-800;
$modal-content-border-color: $gray-700;
$modal-header-border-color: $gray-700;
$progress-bg: $gray-700;
$list-group-bg: $gray-800;
$list-group-border-color: $gray-700;
$list-group-hover-bg: $gray-700;
$breadcrumb-bg: $gray-700;
$close-color: $white;
$close-text-shadow: none;
$pre-color: inherit;
$mark-bg: #333;
$custom-select-bg: $secondary;
$custom-select-color: $white;
$input-bg: $secondary;
$input-color: $white;
$input-disabled-bg: darken($secondary, 10%);;
$light: $gray-800;
$navbar-light-brand-color: $navbar-dark-active-color;
$navbar-light-brand-hover-color: $navbar-dark-active-color;

File diff suppressed because one or more lines are too long

View File

@ -184,7 +184,7 @@ export class Navbar extends Component<any, NavbarState> {
{!this.state.siteLoading ? ( {!this.state.siteLoading ? (
<Link <Link
title={this.state.siteRes.version} title={this.state.siteRes.version}
class="d-flex align-items-center navbar-brand mr-1" class="d-flex align-items-center navbar-brand mr-md-3"
to="/" to="/"
> >
{this.state.siteRes.site.icon && showAvatars() && ( {this.state.siteRes.site.icon && showAvatars() && (
@ -235,7 +235,7 @@ export class Navbar extends Component<any, NavbarState> {
!this.state.expanded && 'collapse' !this.state.expanded && 'collapse'
} navbar-collapse`} } navbar-collapse`}
> >
<ul class="ml-3 navbar-nav my-2 mr-auto"> <ul class="navbar-nav my-2 mr-auto">
<li class="nav-item"> <li class="nav-item">
<Link <Link
class="nav-link" class="nav-link"

View File

@ -163,7 +163,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
return ( return (
<img <img
className={`img-fluid thumbnail rounded ${ className={`img-fluid thumbnail rounded ${
(post.nsfw || post.community_nsfw) && 'img-blur' post.nsfw || post.community_nsfw ? 'img-blur' : ''
}`} }`}
src={src} src={src}
/> />
@ -190,8 +190,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
if (isImage(post.url)) { if (isImage(post.url)) {
return ( return (
<span <div
class="text-body pointer" class="text-body pointer d-inline-block position-relative"
data-tippy-content={i18n.t('expand_here')} data-tippy-content={i18n.t('expand_here')}
onClick={linkEvent(this, this.handleImageExpandClick)} onClick={linkEvent(this, this.handleImageExpandClick)}
> >
@ -199,12 +199,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<svg class="icon mini-overlay"> <svg class="icon mini-overlay">
<use xlinkHref="#icon-image"></use> <use xlinkHref="#icon-image"></use>
</svg> </svg>
</span> </div>
); );
} else if (post.thumbnail_url) { } else if (post.thumbnail_url) {
return ( return (
<a <a
className="text-body" class="text-body d-inline-block position-relative"
href={post.url} href={post.url}
target="_blank" target="_blank"
rel="noopener" rel="noopener"
@ -265,10 +265,93 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
} }
listing() { createdLine() {
let post = this.props.post; let post = this.props.post;
return ( return (
<div class="row"> <ul class="list-inline mb-1 text-muted small">
<li className="list-inline-item">
<UserListing
user={{
name: post.creator_name,
preferred_username: post.creator_preferred_username,
avatar: post.creator_avatar,
id: post.creator_id,
local: post.creator_local,
actor_id: post.creator_actor_id,
published: post.creator_published,
}}
/>
{this.isMod && (
<span className="mx-1 badge badge-light">{i18n.t('mod')}</span>
)}
{this.isAdmin && (
<span className="mx-1 badge badge-light">{i18n.t('admin')}</span>
)}
{(post.banned_from_community || post.banned) && (
<span className="mx-1 badge badge-danger">{i18n.t('banned')}</span>
)}
{this.props.showCommunity && (
<span>
<span class="mx-1"> {i18n.t('to')} </span>
<CommunityLink
community={{
name: post.community_name,
id: post.community_id,
local: post.community_local,
actor_id: post.community_actor_id,
icon: post.community_icon,
}}
/>
</span>
)}
</li>
<li className="list-inline-item"></li>
{post.url && !(hostname(post.url) == window.location.hostname) && (
<>
<li className="list-inline-item">
<a
className="text-muted font-italic"
href={post.url}
target="_blank"
title={post.url}
rel="noopener"
>
{hostname(post.url)}
</a>
</li>
<li className="list-inline-item"></li>
</>
)}
<li className="list-inline-item">
<span>
<MomentTime data={post} />
</span>
</li>
{post.body && (
<>
<li className="list-inline-item"></li>
<li className="list-inline-item">
{/* Using a link with tippy doesn't work on touch devices unfortunately */}
<Link
className="text-muted"
data-tippy-content={md.render(previewLines(post.body))}
data-tippy-allowHtml={true}
to={`/post/${post.id}`}
>
<svg class="mr-1 icon icon-inline">
<use xlinkHref="#icon-book-open"></use>
</svg>
</Link>
</li>
</>
)}
</ul>
);
}
voteBar() {
return (
<div className={`vote-bar col-1 pr-0 small text-center`}> <div className={`vote-bar col-1 pr-0 small text-center`}>
<button <button
className={`btn-animate btn btn-link p-0 ${ className={`btn-animate btn btn-link p-0 ${
@ -301,18 +384,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</button> </button>
)} )}
</div> </div>
{!this.state.imageExpanded && ( );
<div class="col-3 col-sm-2 pr-0"> }
<div class="position-relative">{this.thumbnail()}</div>
</div> postTitleLine() {
)} let post = this.props.post;
<div return (
class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`} <div className="post-title overflow-hidden">
> <h5>
<div class="row">
<div className="col-12">
<div className="post-title">
<h5 className="mb-1 d-inline-block">
{this.props.showBody && post.url ? ( {this.props.showBody && post.url ? (
<a <a
className={!post.stickied ? 'text-body' : 'text-primary'} className={!post.stickied ? 'text-body' : 'text-primary'}
@ -332,23 +411,6 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post.name} {post.name}
</Link> </Link>
)} )}
</h5>
{post.url && !(hostname(post.url) == window.location.hostname) && (
<small class="d-inline-block">
<a
className="ml-2 text-muted font-italic"
href={post.url}
target="_blank"
title={post.url}
rel="noopener"
>
{hostname(post.url)}
<svg class="ml-1 icon icon-inline">
<use xlinkHref="#icon-external-link"></use>
</svg>
</a>
</small>
)}
{(isImage(post.url) || this.props.post.thumbnail_url) && ( {(isImage(post.url) || this.props.post.thumbnail_url) && (
<> <>
{!this.state.imageExpanded ? ( {!this.state.imageExpanded ? (
@ -374,10 +436,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<div> <div>
<span <span
class="pointer" class="pointer"
onClick={linkEvent( onClick={linkEvent(this, this.handleImageExpandClick)}
this,
this.handleImageExpandClick
)}
> >
<img <img
class="img-fluid img-expanded" class="img-fluid img-expanded"
@ -429,82 +488,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{i18n.t('nsfw')} {i18n.t('nsfw')}
</small> </small>
)} )}
</h5>
</div> </div>
</div> );
</div> }
<div class="row">
<div className="details col-12">
<ul class="list-inline mb-1 text-muted small">
<li className="list-inline-item">
<span>{i18n.t('by')} </span>
<UserListing
user={{
name: post.creator_name,
preferred_username: post.creator_preferred_username,
avatar: post.creator_avatar,
id: post.creator_id,
local: post.creator_local,
actor_id: post.creator_actor_id,
published: post.creator_published,
}}
/>
{this.isMod && ( commentsLine(showVotes: boolean = false) {
<span className="mx-1 badge badge-light"> let post = this.props.post;
{i18n.t('mod')} return (
</span> <ul class="d-flex align-items-center list-inline mb-1 text-muted small">
)}
{this.isAdmin && (
<span className="mx-1 badge badge-light">
{i18n.t('admin')}
</span>
)}
{(post.banned_from_community || post.banned) && (
<span className="mx-1 badge badge-danger">
{i18n.t('banned')}
</span>
)}
{this.props.showCommunity && (
<span>
<span> {i18n.t('to')} </span>
<CommunityLink
community={{
name: post.community_name,
id: post.community_id,
local: post.community_local,
actor_id: post.community_actor_id,
icon: post.community_icon,
}}
/>
</span>
)}
</li>
<li className="list-inline-item"></li>
<li className="list-inline-item">
<span>
<MomentTime data={post} />
</span>
</li>
{post.body && (
<>
<li className="list-inline-item"></li>
<li className="list-inline-item">
{/* Using a link with tippy doesn't work on touch devices unfortunately */}
<Link
className="text-muted"
data-tippy-content={md.render(previewLines(post.body))}
data-tippy-allowHtml={true}
to={`/post/${post.id}`}
>
<svg class="mr-1 icon icon-inline">
<use xlinkHref="#icon-book-open"></use>
</svg>
</Link>
</li>
</>
)}
</ul>
<ul class="list-inline mb-1 text-muted small">
<li className="list-inline-item"> <li className="list-inline-item">
<Link <Link
className="text-muted" className="text-muted"
@ -521,7 +513,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
})} })}
</Link> </Link>
</li> </li>
{this.state.upvotes !== this.state.score && ( {(showVotes || this.state.upvotes !== this.state.score) && (
<> <>
<li className="list-inline-item"></li> <li className="list-inline-item"></li>
<span <span
@ -529,26 +521,41 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
data-tippy-content={this.pointsTippy} data-tippy-content={this.pointsTippy}
> >
<li className="list-inline-item"> <li className="list-inline-item">
<span className="text-muted"> <a
className={`btn-animate btn btn-link p-0 ${
this.state.my_vote == 1 ? 'text-info' : 'text-muted'
}`}
onClick={linkEvent(this, this.handlePostLike)}
>
<svg class="small icon icon-inline mr-1"> <svg class="small icon icon-inline mr-1">
<use xlinkHref="#icon-arrow-up"></use> <use xlinkHref="#icon-arrow-up"></use>
</svg> </svg>
{this.state.upvotes} {this.state.upvotes}
</span> </a>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
<span className="text-muted"> <a
className={`btn-animate btn btn-link p-0 ${
this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
}`}
onClick={linkEvent(this, this.handlePostDisLike)}
>
<svg class="small icon icon-inline mr-1"> <svg class="small icon icon-inline mr-1">
<use xlinkHref="#icon-arrow-down"></use> <use xlinkHref="#icon-arrow-down"></use>
</svg> </svg>
{this.state.downvotes} {this.state.downvotes}
</span> </a>
</li> </li>
</span> </span>
</> </>
)} )}
</ul> </ul>
{this.props.post.duplicates && ( );
}
duplicatesLine() {
return (
this.props.post.duplicates && (
<ul class="list-inline mb-1 small text-muted"> <ul class="list-inline mb-1 small text-muted">
<> <>
<li className="list-inline-item mr-2"> <li className="list-inline-item mr-2">
@ -556,14 +563,18 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</li> </li>
{this.props.post.duplicates.map(post => ( {this.props.post.duplicates.map(post => (
<li className="list-inline-item mr-2"> <li className="list-inline-item mr-2">
<Link to={`/post/${post.id}`}> <Link to={`/post/${post.id}`}>{post.community_name}</Link>
{post.community_name}
</Link>
</li> </li>
))} ))}
</> </>
</ul> </ul>
)} )
);
}
postActions() {
let post = this.props.post;
return (
<ul class="list-inline mb-1 text-muted font-weight-bold"> <ul class="list-inline mb-1 text-muted font-weight-bold">
{UserService.Instance.user && ( {UserService.Instance.user && (
<> <>
@ -578,9 +589,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${post.saved && 'text-warning'}`}
post.saved && 'text-warning'
}`}
> >
<use xlinkHref="#icon-star"></use> <use xlinkHref="#icon-star"></use>
</svg> </svg>
@ -617,9 +626,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleDeleteClick)} onClick={linkEvent(this, this.handleDeleteClick)}
data-tippy-content={ data-tippy-content={
!post.deleted !post.deleted ? i18n.t('delete') : i18n.t('restore')
? i18n.t('delete')
: i18n.t('restore')
} }
> >
<svg <svg
@ -672,9 +679,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleModLock)} onClick={linkEvent(this, this.handleModLock)}
data-tippy-content={ data-tippy-content={
post.locked post.locked ? i18n.t('unlock') : i18n.t('lock')
? i18n.t('unlock')
: i18n.t('lock')
} }
> >
<svg <svg
@ -691,9 +696,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleModSticky)} onClick={linkEvent(this, this.handleModSticky)}
data-tippy-content={ data-tippy-content={
post.stickied post.stickied ? i18n.t('unsticky') : i18n.t('sticky')
? i18n.t('unsticky')
: i18n.t('sticky')
} }
> >
<svg <svg
@ -713,20 +716,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{!post.removed ? ( {!post.removed ? (
<span <span
class="pointer" class="pointer"
onClick={linkEvent( onClick={linkEvent(this, this.handleModRemoveShow)}
this,
this.handleModRemoveShow
)}
> >
{i18n.t('remove')} {i18n.t('remove')}
</span> </span>
) : ( ) : (
<span <span
class="pointer" class="pointer"
onClick={linkEvent( onClick={linkEvent(this, this.handleModRemoveSubmit)}
this,
this.handleModRemoveSubmit
)}
> >
{i18n.t('restore')} {i18n.t('restore')}
</span> </span>
@ -778,8 +775,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</> </>
)} )}
{/* Community creators and admins can transfer community to another mod */} {/* Community creators and admins can transfer community to another mod */}
{(this.amCommunityCreator || this.canAdmin) && {(this.amCommunityCreator || this.canAdmin) && this.isMod && (
this.isMod && (
<li className="list-inline-item"> <li className="list-inline-item">
{!this.state.showConfirmTransferCommunity ? ( {!this.state.showConfirmTransferCommunity ? (
<span <span
@ -809,8 +805,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="pointer d-inline-block" class="pointer d-inline-block"
onClick={linkEvent( onClick={linkEvent(
this, this,
this this.handleCancelShowConfirmTransferCommunity
.handleCancelShowConfirmTransferCommunity
)} )}
> >
{i18n.t('no')} {i18n.t('no')}
@ -827,20 +822,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{!post.banned ? ( {!post.banned ? (
<span <span
class="pointer" class="pointer"
onClick={linkEvent( onClick={linkEvent(this, this.handleModBanShow)}
this,
this.handleModBanShow
)}
> >
{i18n.t('ban_from_site')} {i18n.t('ban_from_site')}
</span> </span>
) : ( ) : (
<span <span
class="pointer" class="pointer"
onClick={linkEvent( onClick={linkEvent(this, this.handleModBanSubmit)}
this,
this.handleModBanSubmit
)}
> >
{i18n.t('unban_from_site')} {i18n.t('unban_from_site')}
</span> </span>
@ -881,10 +870,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</span> </span>
<span <span
class="pointer d-inline-block mr-1" class="pointer d-inline-block mr-1"
onClick={linkEvent( onClick={linkEvent(this, this.handleTransferSite)}
this,
this.handleTransferSite
)}
> >
{i18n.t('yes')} {i18n.t('yes')}
</span> </span>
@ -906,6 +892,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</> </>
)} )}
</ul> </ul>
);
}
removeAndBanDialogs() {
let post = this.props.post;
return (
<>
{this.state.showRemoveDialog && ( {this.state.showRemoveDialog && (
<form <form
class="form-inline" class="form-inline"
@ -950,10 +943,89 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</div> </div>
</form> </form>
)} )}
</>
);
}
mobileThumbnail() {
return this.props.post.thumbnail_url || isImage(this.props.post.url) ? (
<div class="row">
<div class="col-8">{this.postTitleLine()}</div>
<div class="col-4">
{/* Post body prev or thumbnail */}
{!this.state.imageExpanded && this.thumbnail()}
</div>
</div>
) : (
this.postTitleLine()
);
}
showMobilePreview() {
return (
this.props.post.body &&
!this.props.showBody && (
<div
className="md-div mb-1"
dangerouslySetInnerHTML={{
__html: md.render(previewLines(this.props.post.body)),
}}
/>
)
);
}
listing() {
return (
<>
{/* The mobile view*/}
<div class="d-block d-sm-none">
<div class="row">
<div class="col-12">
{this.createdLine()}
{/* If it has a thumbnail, do a right aligned thumbnail */}
{this.mobileThumbnail()}
{/* Show a preview of the post body */}
{this.showMobilePreview()}
{this.commentsLine(true)}
{this.duplicatesLine()}
{this.postActions()}
{this.removeAndBanDialogs()}
</div>
</div>
</div>
{/* The larger view*/}
<div class="d-none d-sm-block">
<div class="row">
{this.voteBar()}
{!this.state.imageExpanded && (
<div class="col-sm-2 pr-0">
<div class="">{this.thumbnail()}</div>
</div>
)}
<div
class={`${
this.state.imageExpanded ? 'col-12' : 'col-12 col-sm-9'
}`}
>
<div class="row">
<div className="col-12">
{this.postTitleLine()}
{this.createdLine()}
{this.commentsLine()}
{this.duplicatesLine()}
{this.postActions()}
{this.removeAndBanDialogs()}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</>
); );
} }

6
ui/src/utils.ts vendored
View File

@ -982,10 +982,12 @@ function randomHsl() {
export function previewLines(text: string, lines: number = 3): string { export function previewLines(text: string, lines: number = 3): string {
// Use lines * 2 because markdown requires 2 lines // Use lines * 2 because markdown requires 2 lines
return text return (
text
.split('\n') .split('\n')
.slice(0, lines * 2) .slice(0, lines * 2)
.join('\n'); .join('\n') + '...'
);
} }
export function hostname(url: string): string { export function hostname(url: string): string {