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 ? (
<Link
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="/"
>
{this.state.siteRes.site.icon && showAvatars() && (
@ -235,7 +235,7 @@ export class Navbar extends Component<any, NavbarState> {
!this.state.expanded && '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">
<Link
class="nav-link"

View File

@ -163,7 +163,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
return (
<img
className={`img-fluid thumbnail rounded ${
(post.nsfw || post.community_nsfw) && 'img-blur'
post.nsfw || post.community_nsfw ? 'img-blur' : ''
}`}
src={src}
/>
@ -190,8 +190,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
if (isImage(post.url)) {
return (
<span
class="text-body pointer"
<div
class="text-body pointer d-inline-block position-relative"
data-tippy-content={i18n.t('expand_here')}
onClick={linkEvent(this, this.handleImageExpandClick)}
>
@ -199,12 +199,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<svg class="icon mini-overlay">
<use xlinkHref="#icon-image"></use>
</svg>
</span>
</div>
);
} else if (post.thumbnail_url) {
return (
<a
className="text-body"
class="text-body d-inline-block position-relative"
href={post.url}
target="_blank"
rel="noopener"
@ -265,10 +265,93 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}
}
listing() {
createdLine() {
let post = this.props.post;
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`}>
<button
className={`btn-animate btn btn-link p-0 ${
@ -301,18 +384,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</button>
)}
</div>
{!this.state.imageExpanded && (
<div class="col-3 col-sm-2 pr-0">
<div class="position-relative">{this.thumbnail()}</div>
</div>
)}
<div
class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
>
<div class="row">
<div className="col-12">
<div className="post-title">
<h5 className="mb-1 d-inline-block">
);
}
postTitleLine() {
let post = this.props.post;
return (
<div className="post-title overflow-hidden">
<h5>
{this.props.showBody && post.url ? (
<a
className={!post.stickied ? 'text-body' : 'text-primary'}
@ -332,23 +411,6 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post.name}
</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) && (
<>
{!this.state.imageExpanded ? (
@ -374,10 +436,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<div>
<span
class="pointer"
onClick={linkEvent(
this,
this.handleImageExpandClick
)}
onClick={linkEvent(this, this.handleImageExpandClick)}
>
<img
class="img-fluid img-expanded"
@ -429,82 +488,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{i18n.t('nsfw')}
</small>
)}
</h5>
</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 && (
<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> {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">
commentsLine(showVotes: boolean = false) {
let post = this.props.post;
return (
<ul class="d-flex align-items-center list-inline mb-1 text-muted small">
<li className="list-inline-item">
<Link
className="text-muted"
@ -521,7 +513,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
})}
</Link>
</li>
{this.state.upvotes !== this.state.score && (
{(showVotes || this.state.upvotes !== this.state.score) && (
<>
<li className="list-inline-item"></li>
<span
@ -529,26 +521,41 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
data-tippy-content={this.pointsTippy}
>
<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">
<use xlinkHref="#icon-arrow-up"></use>
</svg>
{this.state.upvotes}
</span>
</a>
</li>
<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">
<use xlinkHref="#icon-arrow-down"></use>
</svg>
{this.state.downvotes}
</span>
</a>
</li>
</span>
</>
)}
</ul>
{this.props.post.duplicates && (
);
}
duplicatesLine() {
return (
this.props.post.duplicates && (
<ul class="list-inline mb-1 small text-muted">
<>
<li className="list-inline-item mr-2">
@ -556,14 +563,18 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</li>
{this.props.post.duplicates.map(post => (
<li className="list-inline-item mr-2">
<Link to={`/post/${post.id}`}>
{post.community_name}
</Link>
<Link to={`/post/${post.id}`}>{post.community_name}</Link>
</li>
))}
</>
</ul>
)}
)
);
}
postActions() {
let post = this.props.post;
return (
<ul class="list-inline mb-1 text-muted font-weight-bold">
{UserService.Instance.user && (
<>
@ -578,9 +589,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}
>
<svg
class={`icon icon-inline ${
post.saved && 'text-warning'
}`}
class={`icon icon-inline ${post.saved && 'text-warning'}`}
>
<use xlinkHref="#icon-star"></use>
</svg>
@ -617,9 +626,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleDeleteClick)}
data-tippy-content={
!post.deleted
? i18n.t('delete')
: i18n.t('restore')
!post.deleted ? i18n.t('delete') : i18n.t('restore')
}
>
<svg
@ -672,9 +679,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleModLock)}
data-tippy-content={
post.locked
? i18n.t('unlock')
: i18n.t('lock')
post.locked ? i18n.t('unlock') : i18n.t('lock')
}
>
<svg
@ -691,9 +696,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleModSticky)}
data-tippy-content={
post.stickied
? i18n.t('unsticky')
: i18n.t('sticky')
post.stickied ? i18n.t('unsticky') : i18n.t('sticky')
}
>
<svg
@ -713,20 +716,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{!post.removed ? (
<span
class="pointer"
onClick={linkEvent(
this,
this.handleModRemoveShow
)}
onClick={linkEvent(this, this.handleModRemoveShow)}
>
{i18n.t('remove')}
</span>
) : (
<span
class="pointer"
onClick={linkEvent(
this,
this.handleModRemoveSubmit
)}
onClick={linkEvent(this, this.handleModRemoveSubmit)}
>
{i18n.t('restore')}
</span>
@ -778,8 +775,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</>
)}
{/* Community creators and admins can transfer community to another mod */}
{(this.amCommunityCreator || this.canAdmin) &&
this.isMod && (
{(this.amCommunityCreator || this.canAdmin) && this.isMod && (
<li className="list-inline-item">
{!this.state.showConfirmTransferCommunity ? (
<span
@ -809,8 +805,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="pointer d-inline-block"
onClick={linkEvent(
this,
this
.handleCancelShowConfirmTransferCommunity
this.handleCancelShowConfirmTransferCommunity
)}
>
{i18n.t('no')}
@ -827,20 +822,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{!post.banned ? (
<span
class="pointer"
onClick={linkEvent(
this,
this.handleModBanShow
)}
onClick={linkEvent(this, this.handleModBanShow)}
>
{i18n.t('ban_from_site')}
</span>
) : (
<span
class="pointer"
onClick={linkEvent(
this,
this.handleModBanSubmit
)}
onClick={linkEvent(this, this.handleModBanSubmit)}
>
{i18n.t('unban_from_site')}
</span>
@ -881,10 +870,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</span>
<span
class="pointer d-inline-block mr-1"
onClick={linkEvent(
this,
this.handleTransferSite
)}
onClick={linkEvent(this, this.handleTransferSite)}
>
{i18n.t('yes')}
</span>
@ -906,6 +892,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</>
)}
</ul>
);
}
removeAndBanDialogs() {
let post = this.props.post;
return (
<>
{this.state.showRemoveDialog && (
<form
class="form-inline"
@ -950,10 +943,89 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</div>
</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>
</>
);
}

6
ui/src/utils.ts vendored
View File

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