mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-10 17:48:50 +08:00
feat(theming): Definitely not hacker mode. I don't know what you're talking about.
Summary: Add docs for new RetinaImg modes Test Plan: Not much to test, except that it looks good! Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1595
This commit is contained in:
parent
ded4da1505
commit
bc916a2530
43 changed files with 314 additions and 213 deletions
|
@ -17,10 +17,10 @@ class AccountSidebarSheetItem extends React.Component
|
|||
icon = <component selected={@props.select} />
|
||||
|
||||
else if _.isString(@props.item.icon)
|
||||
icon = <RetinaImg name={@props.item.icon} fallback="folder.png" colorfill={@props.select} />
|
||||
icon = <RetinaImg name={@props.item.icon} fallback="folder.png" mode={RetinaImg.Mode.ContentIsMask} />
|
||||
|
||||
else
|
||||
icon = <RetinaImg name={"folder.png"} colorfill={@props.select} />
|
||||
icon = <RetinaImg name={"folder.png"} mode={RetinaImg.Mode.ContentIsMask} />
|
||||
|
||||
<div className={classSet} onClick={@_onClick}>
|
||||
{icon}
|
||||
|
|
|
@ -21,7 +21,7 @@ class AccountSidebarTagItem extends React.Component
|
|||
'selected': @props.select
|
||||
|
||||
<div className={coontainerClass} onClick={@_onClick} id={@props.item.id}>
|
||||
<RetinaImg name={"#{@props.item.id}.png"} fallback={'folder.png'} colorfill={@props.select} />
|
||||
<RetinaImg name={"#{@props.item.id}.png"} fallback={'folder.png'} mode={RetinaImg.Mode.ContentIsMask} />
|
||||
<span className="name"> {@props.item.name}</span>
|
||||
{unread}
|
||||
</div>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
.item {
|
||||
color: @text-color-subtle;
|
||||
img.content-mask { background-color: @text-color-subtle; }
|
||||
font-size: @font-size-small;
|
||||
font-weight: 400;
|
||||
padding: 0 @spacing-standard;
|
||||
|
@ -47,9 +48,7 @@
|
|||
&.selected {
|
||||
background: @source-list-active-bg;
|
||||
color: @source-list-active-color;
|
||||
img.colorfill {
|
||||
background: @source-list-active-color;
|
||||
}
|
||||
img.content-mask { background-color: @source-list-active-color; }
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -10,7 +10,7 @@ class ComposeButton extends React.Component
|
|||
className="btn btn-toolbar"
|
||||
data-tooltip="Compose new message"
|
||||
onClick={@_onNewCompose}>
|
||||
<RetinaImg name="toolbar-compose.png"/>
|
||||
<RetinaImg name="toolbar-compose.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</button>
|
||||
|
||||
_onNewCompose: => Actions.composeNewBlankDraft()
|
||||
|
|
|
@ -155,7 +155,11 @@ class ComposerView extends React.Component
|
|||
<span className="header-action"
|
||||
data-tooltip="Popout composer"
|
||||
style={{display: ((@props.mode is "fullwindow") and 'none' or 'initial'), paddingLeft: "1.5em"}}
|
||||
onClick={@_popoutComposer}><RetinaImg name="composer-popout.png" style={{position: "relative", top: "-2px"}}/></span>
|
||||
onClick={@_popoutComposer}>
|
||||
<RetinaImg name="composer-popout.png"
|
||||
mode={RetinaImg.Mode.ContentIsMask}
|
||||
style={{position: "relative", top: "-2px"}}/>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -260,18 +264,18 @@ class ComposerView extends React.Component
|
|||
|
||||
<button className="btn btn-toolbar btn-trash" style={order: 100}
|
||||
data-tooltip="Delete draft"
|
||||
onClick={@_destroyDraft}><RetinaImg name="toolbar-trash.png" /></button>
|
||||
onClick={@_destroyDraft}><RetinaImg name="toolbar-trash.png" mode={RetinaImg.Mode.ContentIsMask} /></button>
|
||||
|
||||
<button className="btn btn-toolbar btn-attach" style={order: 50}
|
||||
data-tooltip="Attach file"
|
||||
onClick={@_attachFile}><RetinaImg name="toolbar-attach.png"/></button>
|
||||
onClick={@_attachFile}><RetinaImg name="toolbar-attach.png" mode={RetinaImg.Mode.ContentIsMask} /></button>
|
||||
|
||||
<div style={order: 0, flex: 1} />
|
||||
|
||||
<button className="btn btn-toolbar btn-emphasis btn-send" style={order: -100}
|
||||
data-tooltip="Send message"
|
||||
ref="sendButton"
|
||||
onClick={@_sendDraft}><RetinaImg name="toolbar-send.png" /> Send</button>
|
||||
onClick={@_sendDraft}><RetinaImg name="toolbar-send.png" mode={RetinaImg.Mode.ContentIsMask} /> Send</button>
|
||||
|
||||
</InjectedComponentSet>
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
|
||||
.header-action {
|
||||
color: @text-color-very-subtle;
|
||||
img.content-mask { background-color: @text-color-very-subtle; }
|
||||
font-size: @font-size-small;
|
||||
padding-left: 1em;
|
||||
&:hover {
|
||||
|
@ -93,6 +94,7 @@
|
|||
}
|
||||
|
||||
input, textarea, div[contenteditable] {
|
||||
color: @text-color;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
|
|
|
@ -3,114 +3,6 @@ _ = require "underscore"
|
|||
{EventedIFrame} = require 'nylas-component-kit'
|
||||
{Utils} = require 'nylas-exports'
|
||||
|
||||
EmailFixingStyles = """
|
||||
<style>
|
||||
/* Styles for an email iframe */
|
||||
@font-face {
|
||||
font-family: 'FaktPro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('FaktPro-Blond'), url('fonts/Fakt/FaktPro-Blond.otf'), local('Comic Sans MS');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'FaktPro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('FaktPro-Normal'), url('fonts/Fakt/FaktPro-Normal.otf'), local('Comic Sans MS');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'FaktPro';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local('FaktPro-Medium'), url('fonts/Fakt/FaktPro-Medium.otf'), local('Comic Sans MS');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'FaktPro';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: local('FaktPro-SemiBold'), url('fonts/Fakt/FaktPro-SemiBold.otf'), local('Comic Sans MS');
|
||||
}
|
||||
|
||||
/* Clean Message Display */
|
||||
html, body {
|
||||
font-family: "FaktPro", "Helvetica", "Lucidia Grande", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
|
||||
color: #313435;
|
||||
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
-webkit-text-size-adjust: auto;
|
||||
word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;
|
||||
}
|
||||
|
||||
strong, b, .bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
max-width: 840px;
|
||||
overflow: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #2794c3;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #1f7498;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #1f7498;
|
||||
}
|
||||
a img {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
body.heightDetermined {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
div,pre {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.gmail_extra,
|
||||
.gmail_quote,
|
||||
#divRplyFwdMsg,
|
||||
blockquote {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.show-quoted-text .gmail_extra,
|
||||
.show-quoted-text .gmail_quote,
|
||||
.show-quoted-text #divRplyFwdMsg,
|
||||
.show-quoted-text blockquote {
|
||||
display:inherit;
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
class EmailFrame extends React.Component
|
||||
@displayName = 'EmailFrame'
|
||||
|
||||
|
@ -140,7 +32,11 @@ class EmailFrame extends React.Component
|
|||
wrapperClass = if @props.showQuotedText then "show-quoted-text" else ""
|
||||
doc = React.findDOMNode(@).contentDocument
|
||||
doc.open()
|
||||
doc.write(EmailFixingStyles)
|
||||
|
||||
EmailFixingStyles = document.querySelector('[source-path*="email-frame.less"]')?.innerText
|
||||
EmailFixingStyles = EmailFixingStyles.replace(/.ignore-in-parent-frame/g, '')
|
||||
if (EmailFixingStyles)
|
||||
doc.write("<style>#{EmailFixingStyles}</style>")
|
||||
doc.write("<div id='inbox-html-wrapper' class='#{wrapperClass}'>#{@_emailContent()}</div>")
|
||||
doc.close()
|
||||
|
||||
|
|
|
@ -134,20 +134,20 @@ class MessageItem extends React.Component
|
|||
_renderMessageActions: =>
|
||||
<div className="message-actions-wrap">
|
||||
<div className="message-actions-ellipsis" onClick={@_onShowActionsMenu}>
|
||||
<RetinaImg name={"message-actions-ellipsis.png"}/>
|
||||
<RetinaImg name={"message-actions-ellipsis.png"} mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</div>
|
||||
<InjectedComponentSet className="message-actions"
|
||||
inline={true}
|
||||
matching={role:"MessageAction"}
|
||||
exposedProps={thread:@props.thread, message: @props.message}>
|
||||
<button className="btn btn-icon" onClick={@_onReply}>
|
||||
<RetinaImg name={"message-reply.png"}/>
|
||||
<RetinaImg name={"message-reply.png"} mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</button>
|
||||
<button className="btn btn-icon" onClick={@_onReplyAll}>
|
||||
<RetinaImg name={"message-reply-all.png"}/>
|
||||
<RetinaImg name={"message-reply-all.png"} mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</button>
|
||||
<button className="btn btn-icon" onClick={@_onForward}>
|
||||
<RetinaImg name={"message-forward.png"}/>
|
||||
<RetinaImg name={"message-forward.png"} mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</button>
|
||||
</InjectedComponentSet>
|
||||
</div>
|
||||
|
@ -221,13 +221,13 @@ class MessageItem extends React.Component
|
|||
<div className="collapse-control"
|
||||
style={top: "4px", left: "-17px"}
|
||||
onClick={=> @setState detailedHeaders: false}>
|
||||
<RetinaImg name={"message-disclosure-triangle-active.png"}/>
|
||||
<RetinaImg name={"message-disclosure-triangle-active.png"} mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</div>
|
||||
else
|
||||
<div className="collapse-control inactive"
|
||||
style={top: "3px"}
|
||||
onClick={=> @setState detailedHeaders: true}>
|
||||
<RetinaImg name={"message-disclosure-triangle.png"}/>
|
||||
<RetinaImg name={"message-disclosure-triangle.png"} mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</div>
|
||||
|
||||
# Eventually, _formatBody will run a series of registered body transformers.
|
||||
|
|
|
@ -98,7 +98,8 @@ class MessageList extends React.Component
|
|||
if @_hasReplyArea()
|
||||
<div className="footer-reply-area-wrap" onClick={@_onClickReplyArea}>
|
||||
<div className="footer-reply-area">
|
||||
<RetinaImg name="#{@_replyType()}-footer.png" /><span className="reply-text">Write a reply…</span>
|
||||
<RetinaImg name="#{@_replyType()}-footer.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
<span className="reply-text">Write a reply…</span>
|
||||
</div>
|
||||
</div>
|
||||
else return <div></div>
|
||||
|
|
|
@ -10,7 +10,7 @@ class ArchiveButton extends React.Component
|
|||
<button className="btn btn-toolbar btn-archive"
|
||||
data-tooltip="Archive"
|
||||
onClick={@_onArchive}>
|
||||
<RetinaImg name="toolbar-archive.png" />
|
||||
<RetinaImg name="toolbar-archive.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</button>
|
||||
|
||||
_onArchive: (e) =>
|
||||
|
|
|
@ -59,8 +59,8 @@ class ThreadTagsButton extends React.Component
|
|||
|
||||
render: =>
|
||||
button = <button className="btn btn-toolbar">
|
||||
<RetinaImg name="toolbar-tags.png"/>
|
||||
<RetinaImg name="toolbar-chevron.png"/>
|
||||
<RetinaImg name="toolbar-tags.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
<RetinaImg name="toolbar-chevron.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</button>
|
||||
|
||||
headerComponents = [
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
|
||||
.message-toolbar-arrow.down {
|
||||
order:101;
|
||||
margin-right: 0;
|
||||
padding-top:6px;
|
||||
}
|
||||
.message-toolbar-arrow.up {
|
||||
|
@ -130,6 +131,7 @@
|
|||
|
||||
.collapsed-from {
|
||||
font-weight: @font-weight-semi-bold;
|
||||
color: @text-color;
|
||||
// min-width: 60px;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
@ -249,10 +251,12 @@
|
|||
|
||||
.footer-reply-area-wrap {
|
||||
width: 100%;
|
||||
color: @text-color-very-subtle;
|
||||
border-top: 1px solid @border-color-divider;
|
||||
background: @background-primary;
|
||||
|
||||
color: @text-color-very-subtle;
|
||||
img.content-mask { background-color:@text-color-very-subtle; }
|
||||
|
||||
&:hover {
|
||||
cursor: default;
|
||||
}
|
||||
|
@ -371,7 +375,7 @@
|
|||
.column-MessageListSidebar {
|
||||
background-color: @background-off-primary;
|
||||
overflow: auto;
|
||||
border-left: 1px solid #ddd;
|
||||
border-left: 1px solid @border-color-divider;
|
||||
.flexbox-handle-horizontal div {
|
||||
border-right: 0;
|
||||
width: 1px;
|
||||
|
|
|
@ -23,8 +23,8 @@ class TemplatePicker extends React.Component
|
|||
|
||||
render: =>
|
||||
button = <button className="btn btn-toolbar">
|
||||
<RetinaImg name="toolbar-templates.png"/>
|
||||
<RetinaImg name="toolbar-chevron.png"/>
|
||||
<RetinaImg name="toolbar-templates.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
<RetinaImg name="toolbar-chevron.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</button>
|
||||
|
||||
headerComponents = [
|
||||
|
|
|
@ -36,6 +36,7 @@ class ModeSwitch extends React.Component
|
|||
<RetinaImg
|
||||
data-mode={'list'}
|
||||
name="toolbar-icon-listmode.png"
|
||||
mode={RetinaImg.Mode.ContentIsMask}
|
||||
active={@state.mode is 'list'}
|
||||
onClick={@_onSetMode}
|
||||
style={paddingRight:12} />
|
||||
|
@ -45,14 +46,15 @@ class ModeSwitch extends React.Component
|
|||
name="modeslider-knob.png"
|
||||
className="handle"
|
||||
style={top:4, left: knobX}/>
|
||||
<RetinaImg
|
||||
<RetinaImg
|
||||
data-mode={'split'}
|
||||
name="toolbar-icon-splitpanes.png"
|
||||
name="toolbar-icon-splitpanes.png"
|
||||
mode={RetinaImg.Mode.ContentIsMask}
|
||||
active={@state.mode is 'split'}
|
||||
onClick={@_onSetMode}
|
||||
style={paddingLeft:12} />
|
||||
</div>
|
||||
|
||||
|
||||
_onStateChanged: =>
|
||||
@setState(@_getStateFromStores())
|
||||
|
||||
|
|
|
@ -20,15 +20,15 @@ class ModeToggle extends React.Component
|
|||
render: =>
|
||||
return <div></div> unless @state.visible
|
||||
|
||||
<div className="mode-toggle"
|
||||
<div className="mode-toggle mode-#{@state.mode}"
|
||||
style={order:51, marginTop:10, marginRight:14}
|
||||
onClick={@_onToggleMode}>
|
||||
<RetinaImg
|
||||
name="toolbar-icon-toggle-pane.png"
|
||||
colorfill={@state.mode is 'split'}
|
||||
mode={RetinaImg.Mode.ContentIsMask}
|
||||
onClick={@_onToggleMode} />
|
||||
</div>
|
||||
|
||||
|
||||
_onStateChanged: =>
|
||||
@setState(@_getStateFromStores())
|
||||
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
@import 'ui-variables';
|
||||
|
||||
.mode-switch {
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
|
||||
.handle {
|
||||
position:absolute;
|
||||
transition: left .2s ease-out;
|
||||
}
|
||||
}
|
||||
.mode-toggle {
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
|
||||
.colorfill {
|
||||
background-color: @component-active-color;
|
||||
.content-mask {
|
||||
background-color: @text-color-subtle;
|
||||
}
|
||||
}
|
||||
.mode-toggle.mode-split {
|
||||
.content-mask {
|
||||
background-color: @component-active-color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,12 +69,12 @@ class ContainerView extends React.Component
|
|||
if @state.page is 'welcome'
|
||||
<div className="page" key={@state.page}>
|
||||
<div className="quit" onClick={@_fireQuit}>
|
||||
<RetinaImg name="onboarding-close.png"/>
|
||||
<RetinaImg name="onboarding-close.png" mode={RetinaImg.Mode.ContentPreserve} />
|
||||
</div>
|
||||
<RetinaImg name="onboarding-logo.png" className="logo"/>
|
||||
<h2>Welcome to Nylas</h2>
|
||||
|
||||
<RetinaImg name="onboarding-divider.png" />
|
||||
<RetinaImg name="onboarding-divider.png" mode={RetinaImg.Mode.ContentPreserve} />
|
||||
|
||||
<form role="form" className="thin-container">
|
||||
<div className="prompt">Enter your email address:</div>
|
||||
|
@ -95,12 +95,12 @@ class ContainerView extends React.Component
|
|||
else if @state.page == 'add-account'
|
||||
<div className="page" key={@state.page}>
|
||||
<div className="quit" onClick={@_fireDismiss}>
|
||||
<RetinaImg name="onboarding-close.png"/>
|
||||
<RetinaImg name="onboarding-close.png" mode={RetinaImg.Mode.ContentPreserve} />
|
||||
</div>
|
||||
<RetinaImg name="onboarding-logo.png" className="logo"/>
|
||||
<RetinaImg name="onboarding-logo.png" className="logo" mode={RetinaImg.Mode.ContentPreserve} />
|
||||
<h2>Connect an Account</h2>
|
||||
|
||||
<RetinaImg name="onboarding-divider.png" />
|
||||
<RetinaImg name="onboarding-divider.png" mode={RetinaImg.Mode.ContentPreserve} />
|
||||
|
||||
<form role="form" className="thin-container">
|
||||
<div className="prompt">Link accounts from other services to supercharge your email.</div>
|
||||
|
@ -119,7 +119,7 @@ class ContainerView extends React.Component
|
|||
})
|
||||
}
|
||||
<div className="back" onClick={@_fireMoveToPrevPage}>
|
||||
<RetinaImg name="onboarding-back.png"/>
|
||||
<RetinaImg name="onboarding-back.png" mode={RetinaImg.Mode.ContentPreserve} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.3); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fill {
|
||||
0% { opacity: 0; border-width: @checkSize / 2; }
|
||||
|
@ -22,7 +22,7 @@
|
|||
.onboarding-container {
|
||||
width:100%;
|
||||
height:100%;
|
||||
background-color: #f0f0f0;
|
||||
background-color: @gray-lighter;
|
||||
-webkit-animation: fadein 0.8s;
|
||||
text-align: center;
|
||||
|
||||
|
@ -38,7 +38,7 @@
|
|||
.logo {
|
||||
padding-top:40px;
|
||||
}
|
||||
|
||||
|
||||
h2 {
|
||||
margin-top: 27px;
|
||||
margin-bottom: 90px;
|
||||
|
@ -135,7 +135,7 @@
|
|||
bottom: 9%;
|
||||
box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
@ -152,7 +152,7 @@
|
|||
-webkit-animation: fill 0.5s forwards;
|
||||
-webkit-animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
|
||||
.check-icon {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
|
|
|
@ -52,6 +52,7 @@ class SearchBar extends React.Component
|
|||
<RetinaImg className="search-accessory search"
|
||||
name="searchloupe.png"
|
||||
key="accessory"
|
||||
mode={RetinaImg.Mode.ContentDark}
|
||||
onClick={@_doSearch} />
|
||||
<div className="search-accessory clear"
|
||||
key="clear"
|
||||
|
|
|
@ -34,7 +34,10 @@ class SidebarFullContactDetails extends React.Component
|
|||
profiles = @_profiles()
|
||||
return profiles.map (profile) =>
|
||||
<div className="social-profile">
|
||||
<RetinaImg name="#{profile.typeId}-icon.png" className="social-icon" />
|
||||
<RetinaImg
|
||||
className="social-icon"
|
||||
name="#{profile.typeId}-icon.png"
|
||||
mode={RetinaImg.Mode.ContentIsMask} />
|
||||
<div className="social-link">
|
||||
<a href={profile.url}>{@_username(profile)}</a>
|
||||
{@_twitterBio(profile)}
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
flex-shrink: 0;
|
||||
|
||||
.full-contact {
|
||||
color: @text-color;
|
||||
-webkit-user-select:text;
|
||||
img.content-mask { background-color: @text-color; }
|
||||
|
||||
h1.name {
|
||||
font-size: 20px;
|
||||
font-weight: @font-weight-normal;
|
||||
|
@ -41,7 +45,7 @@
|
|||
.social-profile {
|
||||
margin-top: 0.5em;
|
||||
.social-icon {
|
||||
padding-top: 6px;
|
||||
margin-top: 6px;
|
||||
float: left;
|
||||
}
|
||||
.social-link {
|
||||
|
|
|
@ -16,7 +16,7 @@ class ThreadBulkArchiveButton extends React.Component
|
|||
className="btn btn-toolbar"
|
||||
data-tooltip="Archive"
|
||||
onClick={@_onArchive}>
|
||||
<RetinaImg name="toolbar-archive.png" />
|
||||
<RetinaImg name="toolbar-archive.png" mode={RetinaImg.Mode.ContentIsMask} />
|
||||
</button>
|
||||
|
||||
_onArchive: =>
|
||||
|
@ -55,11 +55,12 @@ DownButton = React.createClass
|
|||
|
||||
render: ->
|
||||
<div className={@_classSet()} onClick={@_onClick}>
|
||||
<RetinaImg name="toolbar-down-arrow.png"/>
|
||||
<RetinaImg name="toolbar-down-arrow.png" mode={RetinaImg.Mode.ContentIsMask} />
|
||||
</div>
|
||||
|
||||
_classSet: ->
|
||||
classNames
|
||||
"btn-icon": true
|
||||
"message-toolbar-arrow": true
|
||||
"down": true
|
||||
"disabled": @state.disabled
|
||||
|
@ -77,11 +78,12 @@ UpButton = React.createClass
|
|||
|
||||
render: ->
|
||||
<div className={@_classSet()} onClick={@_onClick}>
|
||||
<RetinaImg name="toolbar-up-arrow.png"/>
|
||||
<RetinaImg name="toolbar-up-arrow.png" mode={RetinaImg.Mode.ContentIsMask} />
|
||||
</div>
|
||||
|
||||
_classSet: ->
|
||||
classNames
|
||||
"btn-icon": true
|
||||
"message-toolbar-arrow": true
|
||||
"up": true
|
||||
"disabled": @state.disabled
|
||||
|
|
|
@ -55,7 +55,9 @@ class ThreadList extends React.Component
|
|||
if hasDraft
|
||||
<div style={display: 'flex'}>
|
||||
<ThreadListParticipants thread={thread} />
|
||||
<RetinaImg name="icon-draft-pencil.png" className="draft-icon" />
|
||||
<RetinaImg name="icon-draft-pencil.png"
|
||||
className="draft-icon"
|
||||
mode={RetinaImg.Mode.ContentPreserve} />
|
||||
</div>
|
||||
else
|
||||
<ThreadListParticipants thread={thread} />
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
background: #fff;
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.19), inset 0 0 1px rgba(0,0,0,0.5);
|
||||
border-radius: @border-radius-base;
|
||||
color: @text-color;
|
||||
color: #313435;
|
||||
|
||||
font-weight: @font-weight-normal;
|
||||
text-align: center;
|
||||
|
|
|
@ -1 +1,50 @@
|
|||
@background-primary: #ffffff;
|
||||
@gray-base: #ffffff;
|
||||
@gray-darker: darken(@gray-base, 13.5%); // #222
|
||||
@gray-dark: darken(@gray-base, 20%); // #333
|
||||
@gray: darken(@gray-base, 33.5%); // #555
|
||||
@gray-light: darken(@gray-base, 46.7%); // #777
|
||||
@gray-lighter: darken(@gray-base, 92.5%); // #eee
|
||||
@white: #0a0b0c;
|
||||
|
||||
@background-off-primary: #333;
|
||||
@background-secondary: #2D2D2D;
|
||||
@background-tertiary: #6d7987;
|
||||
|
||||
@background-primary: #313131;
|
||||
@background-color: darken(#313131, 15%);
|
||||
@btn-default-bg-color: #404040;
|
||||
@accent-primary: #5AA8FA;
|
||||
@accent-primary-dark: #3087E1;
|
||||
|
||||
@text-color: #C2C2C2;
|
||||
@text-color-subtle: fadeout(@text-color, 20%);
|
||||
@text-color-very-subtle: fadeout(@text-color, 40%);
|
||||
@text-color-inverse: white;
|
||||
@text-color-inverse-subtle: fadeout(@text-color-inverse, 20%);
|
||||
@text-color-inverse-very-subtle: fadeout(@text-color-inverse, 50%);
|
||||
@text-color-heading: #FFF;
|
||||
|
||||
@border-primary-bg: lighten(@background-primary, 10%);
|
||||
@border-secondary-bg: lighten(@background-secondary, 10%);
|
||||
@border-tertiary-bg: lighten(@background-tertiary, 10%);
|
||||
@border-color-divider: @border-secondary-bg;
|
||||
|
||||
@input-bg: #242424;
|
||||
@input-border: @border-color-divider;
|
||||
|
||||
@list-bg: #333;
|
||||
@list-border: #383838;
|
||||
|
||||
@list-selected-color: @text-color-inverse;
|
||||
|
||||
@toolbar-background-color: @background-secondary;
|
||||
|
||||
.thread-icon:not(.thread-icon-unread) {
|
||||
-webkit-filter: invert(100%);
|
||||
}
|
||||
img.content-dark {
|
||||
-webkit-filter: invert(100%);
|
||||
}
|
||||
img.content-light {
|
||||
-webkit-filter: invert(100%);
|
||||
}
|
||||
|
|
|
@ -52,6 +52,8 @@
|
|||
{
|
||||
label: 'Developer'
|
||||
submenu: [
|
||||
{ label: 'Toggle Hacker Theme', command: 'application:toggle-theme' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open In Dev Mode...', command: 'application:open-dev' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open Detailed Logs', command: 'window:open-errorreporter-logs' }
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
{
|
||||
label: 'Developer'
|
||||
submenu: [
|
||||
{ label: 'Toggle Hacker Theme', command: 'application:toggle-theme' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open In &Dev Mode...', command: 'application:open-dev' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open Detailed Logs', command: 'window:open-errorreporter-logs' }
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
{
|
||||
label: 'Developer'
|
||||
submenu: [
|
||||
{ label: 'Toggle Hacker Theme', command: 'application:toggle-theme' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open In &Dev Mode...', command: 'application:open-dev' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open Detailed Logs', command: 'window:open-errorreporter-logs' }
|
||||
|
|
|
@ -249,6 +249,14 @@ class Application
|
|||
@windowManager.devMode = true
|
||||
@windowManager.ensurePrimaryWindowOnscreen()
|
||||
|
||||
@on 'application:toggle-theme', =>
|
||||
themes = @config.get('core.themes') ? []
|
||||
if 'ui-dark' in themes
|
||||
themes = _.without themes, 'ui-dark'
|
||||
else
|
||||
themes.push('ui-dark')
|
||||
@config.set('core.themes', themes)
|
||||
|
||||
if process.platform is 'darwin'
|
||||
@on 'application:about', -> Menu.sendActionToFirstResponder('orderFrontStandardAboutPanel:')
|
||||
@on 'application:bring-all-windows-to-front', -> Menu.sendActionToFirstResponder('arrangeInFront:')
|
||||
|
|
|
@ -53,10 +53,10 @@ class EmptyState extends React.Component
|
|||
{@state.message?.byline}
|
||||
</div>
|
||||
</div>
|
||||
<RetinaImg name="blank-bottom-left.png" className="bottom-left"/>
|
||||
<RetinaImg name="blank-top-left.png" className="top-left"/>
|
||||
<RetinaImg name="blank-bottom-right.png" className="bottom-right"/>
|
||||
<RetinaImg name="blank-top-right.png" className="top-right"/>
|
||||
<RetinaImg mode={RetinaImg.Mode.ContentLight} name="blank-bottom-left.png" className="bottom-left"/>
|
||||
<RetinaImg mode={RetinaImg.Mode.ContentLight} name="blank-top-left.png" className="top-left"/>
|
||||
<RetinaImg mode={RetinaImg.Mode.ContentLight} name="blank-bottom-right.png" className="bottom-right"/>
|
||||
<RetinaImg mode={RetinaImg.Mode.ContentLight} name="blank-top-right.png" className="top-right"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -102,8 +102,9 @@ class Popover extends React.Component
|
|||
pointerStyle =
|
||||
'position': 'absolute'
|
||||
'marginLeft': '50%'
|
||||
'width': 22.5
|
||||
'height': 11
|
||||
'zoom': 0.5
|
||||
'width': 45
|
||||
'height': 21
|
||||
'zIndex': 0
|
||||
|
||||
if @props.direction is 'up'
|
||||
|
|
|
@ -23,6 +23,12 @@ StylesImpactedByZoom = [
|
|||
# constant at require-time
|
||||
DEFAULT_RESOURCE_PATH = atom.getLoadSettings().resourcePath
|
||||
|
||||
Mode =
|
||||
ContentPreserve: 'original'
|
||||
ContentLight: 'light'
|
||||
ContentDark: 'dark'
|
||||
ContentIsMask: 'mask'
|
||||
|
||||
###
|
||||
Public: RetinaImg wraps the DOM's standard `<img`> tag and implements a `UIImage` style
|
||||
interface. Rather than specifying an image `src`, RetinaImg allows you to provide
|
||||
|
@ -31,45 +37,84 @@ display based on pixel density. Given `image.png`, on a Retina screen, it looks
|
|||
`image@2x.png`, `image.png`, `image@1x.png` in that order. It uses a lookup table and caches
|
||||
image names, so images generally resolve immediately.
|
||||
|
||||
RetinaImg also introduces the concept of image `modes`. Specifying an image mode
|
||||
is important for theming: it describes the content of your image, allowing theme
|
||||
developers to properly adjust it. The four modes are described below:
|
||||
|
||||
- `ContentPreserve`: Your image contains color or should not be adjusted by any theme.
|
||||
|
||||
- `ContentLight`: Your image is a grayscale image with light colors, intended to be shown
|
||||
against a dark background. If a theme developer changes the background to be light, they
|
||||
can safely apply CSS filters to invert or darken this image. This mode adds the
|
||||
`content-light` CSS class to the image.
|
||||
|
||||
- `ContentDark`: Your image is a grayscale image with dark colors, intended to be shown
|
||||
against a light background. If a theme developer changes the background to be dark, they
|
||||
can safely apply CSS filters to invert or brighten this image. This mode adds the
|
||||
`content-dark` CSS class to the image.
|
||||
|
||||
- `ContentIsMask`: This image provides alpha information only, and color should
|
||||
be based on the `background-color` of the RetinaImg. This mode adds the
|
||||
`content-mask` CSS class to the image, and leverages `-webkit-mask-image`.
|
||||
|
||||
Example: Icons displayed within buttons specify ContentIsMask, and their
|
||||
color is declared via CSS to be the same as the button text color. Changing
|
||||
`@text-color-subtle` in a theme changes both button text and button icons!
|
||||
|
||||
```css
|
||||
.btn-icon {
|
||||
color: @text-color-subtle;
|
||||
img.content-mask { background-color:@text-color-subtle; }
|
||||
}
|
||||
```
|
||||
|
||||
Section: Component Kit
|
||||
###
|
||||
class RetinaImg extends React.Component
|
||||
@displayName: 'RetinaImg'
|
||||
@Mode: Mode
|
||||
|
||||
###
|
||||
Public: React `props` supported by RetinaImg:
|
||||
|
||||
- `mode` (required) One of the RetinaImg.Mode constants. See above for details.
|
||||
- `name` (optional) A {String} image name to display.
|
||||
- `url` (optional) A {String} url of an image to display.
|
||||
May be an http, https, or `nylas://<packagename>/<path within package>` URL.
|
||||
- `fallback` (optional) A {String} image name to use when `name` cannot be found.
|
||||
- `selected` (optional) Appends "-selected" to the end of the image name when when true
|
||||
- `active` (optional) Appends "-active" to the end of the image name when when true
|
||||
- `colorfill` (optional) Adds -webkit-mask-image and other styles, and the .colorfill CSS
|
||||
class, so that setting a CSS background color will colorfill the image.
|
||||
- `style` (optional) An {Object} with additional styles to apply to the image.
|
||||
- `resourcePath` (options) Changes the default lookup location used to find the images.
|
||||
###
|
||||
@propTypes:
|
||||
mode: React.PropTypes.string.isRequired
|
||||
name: React.PropTypes.string
|
||||
url: React.PropTypes.string
|
||||
className: React.PropTypes.string
|
||||
style: React.PropTypes.object
|
||||
fallback: React.PropTypes.string
|
||||
selected: React.PropTypes.bool
|
||||
active: React.PropTypes.bool
|
||||
colorfill: React.PropTypes.bool
|
||||
resourcePath: React.PropTypes.string
|
||||
|
||||
render: ->
|
||||
path = @_pathFor(@props.name) ? @_pathFor(@props.fallback) ? ''
|
||||
path = @props.url ? @_pathFor(@props.name) ? @_pathFor(@props.fallback) ? ''
|
||||
pathIsRetina = path.indexOf('@2x') > 0
|
||||
className = undefined
|
||||
className = @props.className ? ''
|
||||
|
||||
style = @props.style ? {}
|
||||
style.WebkitUserDrag = 'none'
|
||||
style.zoom = if pathIsRetina then 0.5 else 1
|
||||
|
||||
if @props.colorfill
|
||||
if @props.mode is Mode.ContentIsMask
|
||||
style.WebkitMaskImage = "url('#{path}')"
|
||||
style.objectPosition = "10000px"
|
||||
className = "colorfill"
|
||||
className += " content-mask"
|
||||
else if @props.mode is Mode.ContentDark
|
||||
className += " content-dark"
|
||||
else if @props.mode is Mode.ContentLight
|
||||
className += " content-light"
|
||||
|
||||
for key, val of style
|
||||
val = "#{val}"
|
||||
|
|
|
@ -35,7 +35,9 @@ Token = React.createClass
|
|||
<div {...@dragSourceFor('token')}
|
||||
className={classes}
|
||||
onClick={@_onSelect}>
|
||||
<button className="action" onClick={@_onAction} style={marginTop: "2px"}><RetinaImg name="composer-caret.png" /></button>
|
||||
<button className="action" onClick={@_onAction} style={marginTop: "2px"}>
|
||||
<RetinaImg mode={RetinaImg.Mode.ContentIsMask} name="composer-caret.png" />
|
||||
</button>
|
||||
{@props.children}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ class ToolbarBack extends React.Component
|
|||
@displayName: 'ToolbarBack'
|
||||
render: =>
|
||||
<div className="item-back" onClick={@_onClick}>
|
||||
<RetinaImg name="sheet-back.png" />
|
||||
<RetinaImg name="sheet-back.png" mode={RetinaImg.Mode.ContentIsMask} />
|
||||
</div>
|
||||
|
||||
_onClick: =>
|
||||
|
@ -156,4 +156,4 @@ class Toolbar extends React.Component
|
|||
|
||||
state
|
||||
|
||||
module.exports = Toolbar
|
||||
module.exports = Toolbar
|
||||
|
|
|
@ -285,6 +285,7 @@ class ThemeManager
|
|||
|
||||
reloadBaseStylesheets: ->
|
||||
@requireStylesheet('../static/index')
|
||||
@requireStylesheet('../static/email-frame')
|
||||
if nativeStylesheetPath = fs.resolveOnLoadPath(process.platform, ['css', 'less'])
|
||||
@requireStylesheet(nativeStylesheetPath)
|
||||
|
||||
|
|
|
@ -46,10 +46,12 @@ button, html input[type="button"] {
|
|||
|
||||
color: @btn-default-text-color;
|
||||
background: @btn-default-bg-color;
|
||||
img.content-mask { background-color:@btn-default-text-color; }
|
||||
|
||||
&.btn-action {
|
||||
color: @btn-action-text-color;
|
||||
background: @btn-action-bg-color;
|
||||
img.content-mask { background-color:@btn-action-text-color; }
|
||||
}
|
||||
|
||||
&.btn-emphasis {
|
||||
|
@ -58,7 +60,7 @@ button, html input[type="button"] {
|
|||
color: @btn-emphasis-text-color;
|
||||
font-weight: @font-weight-medium;
|
||||
|
||||
img {-webkit-filter: brightness(100);}
|
||||
img.content-mask { background-color:@btn-emphasis-text-color; }
|
||||
}
|
||||
|
||||
&.btn-emphasis:active {
|
||||
|
@ -69,6 +71,7 @@ button, html input[type="button"] {
|
|||
&.btn-danger, .btn-destructive {
|
||||
color: @btn-danger-text-color;
|
||||
background: @btn-danger-bg-color;
|
||||
img.content-mask { background-color:@btn-danger-text-color; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,6 +91,7 @@ button, html input[type="button"] {
|
|||
border: 0;
|
||||
box-shadow: none;
|
||||
color: @text-color-subtle;
|
||||
img.content-mask { background-color:@text-color-subtle; }
|
||||
margin-right: 10px;
|
||||
outline: none !important;
|
||||
font-size: 20px;
|
||||
|
@ -98,6 +102,7 @@ button, html input[type="button"] {
|
|||
|
||||
&.inverse {
|
||||
color: @text-color-inverse;
|
||||
img.content-mask { background-color:@text-color-inverse; }
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
|
@ -105,16 +110,19 @@ button, html input[type="button"] {
|
|||
|
||||
&:active {
|
||||
color: @text-color-inverse;
|
||||
img.content-mask { background-color:@text-color-inverse; }
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: default;
|
||||
color: @text-color-link;
|
||||
img.content-mask { background-color:@text-color-link; }
|
||||
box-shadow: none;
|
||||
}
|
||||
&:active {
|
||||
color: @text-color-link-active;
|
||||
img.content-mask { background-color:@text-color-link-active; }
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,6 +140,11 @@
|
|||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
&.focused {
|
||||
.checkmark .inner {
|
||||
border:1px solid @accent-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
padding: 12px;
|
||||
|
@ -149,7 +154,7 @@
|
|||
.inner {
|
||||
width:14px;
|
||||
height:14px;
|
||||
border:1px solid @table-border-color;
|
||||
border:1px solid @list-border;
|
||||
border-radius: 2px;
|
||||
background: transparent;
|
||||
background-size: 12px 9px;
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
|
||||
.popover-pointer {
|
||||
background: transparent url('images/tooltip/tooltip-bg-pointer@2x.png') no-repeat;
|
||||
background-size: 22.5px 10.5px;
|
||||
-webkit-mask-image: url('images/tooltip/tooltip-bg-pointer@2x.png');
|
||||
background-color: @background-color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
.token {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
color: @text-color;
|
||||
padding: 0.4em @spacing-half 0.4em @spacing-half;
|
||||
padding-right: 1.5em;
|
||||
margin: 0 5px 5px 0;
|
||||
|
|
80
static/email-frame.less
Normal file
80
static/email-frame.less
Normal file
|
@ -0,0 +1,80 @@
|
|||
@import 'variables/ui-variables';
|
||||
@import 'ui-variables';
|
||||
|
||||
.ignore-in-parent-frame {
|
||||
|
||||
html, body {
|
||||
font-family: "FaktPro", "Helvetica", "Lucidia Grande", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
|
||||
color: @text-color;
|
||||
background-color: transparent !important;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
-webkit-text-size-adjust: auto;
|
||||
word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;
|
||||
}
|
||||
|
||||
strong, b, .bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
max-width: 840px;
|
||||
overflow: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
a {
|
||||
color: @text-color-link;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: @text-color-link-hover;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: darken(@text-color-link, 10%);
|
||||
}
|
||||
a img {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
body.heightDetermined {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
div,pre {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.gmail_extra,
|
||||
.gmail_quote,
|
||||
#divRplyFwdMsg,
|
||||
blockquote {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.show-quoted-text .gmail_extra,
|
||||
.show-quoted-text .gmail_quote,
|
||||
.show-quoted-text #divRplyFwdMsg,
|
||||
.show-quoted-text blockquote {
|
||||
display:inherit;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ input[type="url"] {
|
|||
font-size: @font-size-base;
|
||||
line-height: @line-height-computed;
|
||||
font-weight:400;
|
||||
background: @input-bg;
|
||||
|
||||
&.input-bordered {
|
||||
border-radius: @border-radius-base;
|
||||
|
|
|
@ -203,28 +203,6 @@
|
|||
@standard-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.21);
|
||||
@standard-shadow-up: 0 -1px 4px 0 rgba(0, 0, 0, 0.21);
|
||||
|
||||
|
||||
//============================== Tables ===============================//
|
||||
// Customizes the `.table` component with basic values, each used across
|
||||
// all table variations.
|
||||
|
||||
//** Padding for `<th>`s and `<td>`s.
|
||||
@table-cell-padding: 8px;
|
||||
//** Padding for cells in `.table-condensed`.
|
||||
@table-condensed-cell-padding: 5px;
|
||||
|
||||
//** Default background color used for all tables.
|
||||
@table-bg: transparent;
|
||||
//** Background color used for `.table-striped`.
|
||||
@table-bg-accent: #f9f9f9;
|
||||
//** Background color used for `.table-hover`.
|
||||
@table-bg-hover: #f5f5f5;
|
||||
@table-bg-active: @table-bg-hover;
|
||||
|
||||
//** Border color for table and cell borders.
|
||||
@table-border-color: #ddd;
|
||||
|
||||
|
||||
//=============================== Buttons ==============================//
|
||||
|
||||
@btn-shadow: @standard-shadow;
|
||||
|
|
|
@ -153,6 +153,7 @@ body.is-blurred {
|
|||
order:-999;
|
||||
padding-top: 5px;
|
||||
padding-left: @spacing-three-quarters;
|
||||
img.content-mask { background-color: @text-color-heading; }
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue