Open /connect sidecar links in iframe (#5203) [deploy]

* Use iframe for connect sidecar

* Modify css of lefthand sidebar

* Add proper sidecar markup

* Update tests
This commit is contained in:
Ben Halpern 2019-12-21 15:46:36 -05:00 committed by GitHub
parent cb3a0c462c
commit 5812ecc383
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 234 additions and 567 deletions

View file

@ -1,7 +1,7 @@
'use strict';
function initializePWAFunctionality() {
if (window.matchMedia('(display-mode: standalone)').matches) {
if (window.matchMedia('(display-mode: standalone)').matches || window.frameElement) {
document
.getElementById('pwa-nav-buttons')
.classList.add('pwa-nav-buttons--showing');

View file

@ -147,8 +147,8 @@ article {
width: 81%;
font-family: $helvetica;
@media screen and (max-width: 550px) {
width: 88%;
@media screen and (max-width: 750px) {
width: 90%;
}
@media screen and (max-width: 430px) {
@ -576,8 +576,8 @@ article {
margin-top: -0.8em;
}
@media screen and (max-width: 550px) {
width: 90%;
@media screen and (max-width: 750px) {
width: 92%;
}
@media screen and (max-width: 430px) {

View file

@ -6,7 +6,7 @@
.chat-page-wrapper {
margin: 85px auto 0px;
width: 96%;
max-width: calc(1440px + 10vw);
max-width: calc(1540px + 10vw);
}
.live-chat {
@ -91,8 +91,8 @@
}
@media screen and (min-width: 1300px) {
width: calc(280px + 3vw);
min-width: calc(280px);
width: calc(220px + 3vw);
min-width: 220px;
}
&.chat__channels--placeholder {
@ -423,8 +423,10 @@
flex-direction: column;
flex: 0 1 auto;
width: 100%;
min-width: 55%;
min-width: 50%;
@media screen and (min-width: 1440px) {
min-width: 55%;
}
@media screen and (max-width: 426px) {
overflow-x: hidden;
}
@ -465,7 +467,7 @@
}
.activechatchannel__messages {
font-size: 15px;
font-size: calc(15px + 0.05vw);
padding: 10px 0px;
flex-grow: 1;
height: 300px;
@ -505,7 +507,7 @@
theme-container-accent-background,
#ededed);
width: 100%;
height: 70px;
height: 60px;
}
.activechatchannel__incomingcall {
@ -523,26 +525,32 @@
.activechatchannel__activecontent {
order: 2;
flex: 0 1 auto;
border: 1px solid $outline-color;
border-left: 2px solid $outline-color;
@include themeable(border,
theme-border,
1px solid $outline-color);
padding: 13px;
min-width: 45%;
min-width: 50%;
@media screen and (min-width: 1440px) {
min-width: 45%;
}
-webkit-overflow-scrolling: touch;
@include themeable(background,
theme-container-accent-background,
white);
theme-background,
$lightest-gray);
z-index: 200;
position: relative;
box-sizing: border-box;
overflow-y: auto;
max-width: 96%;
max-width: 100%;
@media screen and (max-width: 426px) {
padding: 10px;
min-width: 50%;
width: calc(95% - 45px);
width: calc(100% - 16px);
position: fixed;
height: calc(100% - 90px);
height: calc(100% - 86px);
margin-left: -48px;
margin-top: -1px;
}
@media screen and (min-width: 1000px) {
@ -574,52 +582,40 @@
padding: 0px;
background: transparent;
position: absolute;
left: 6px;
top: -3px;
left: 4px;
top: 4px;
@include themeable(background,
theme-container-accent-background,
white);
z-index: 10;
height: 29px;
width: 29px;
line-height: 10px;
padding-bottom: 5px;
border-radius: 100px;
@include themeable(border,
theme-border,
1px solid $medium-gray);
}
.activechatchannel__activeArticle {
position: absolute;
top: 36px;
top: 0px;
left: 0;
right: 0;
bottom: 0;
border-top: 1px solid $light-medium-gray !important;
}
.activechatchannel__activeArticleDetails {
.activechatchannel__activeArticle iframe {
position: absolute;
top: -26px;
right: 2px;
left: 35px;
font-family: $monospace;
font-size: 0.8em;
a {
@include themeable(color,
theme-container-color,
$dark-gray);
}
.activechatchannel__activeArticleDetailsPath {
display: inline-block;
max-width: calc(100% - 15px);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
img {
height: 1.1em;
width: 1.1em;
margin-left: 10px;
vertical-align: 2px;
opacity: 0.86;
&:hover {
opacity: 1;
}
}
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100%;
width: 100%;
border: 0px;
}
@ -678,68 +674,6 @@
}
}
.chat .activechatchannel__activeArticle .container {
position: absolute;
top: 0px;
left: 0;
right: 0;
bottom: 30px;
overflow-y: scroll;
border-radius: 0px;
margin-top: 0px !important;
pre {
width: 97%;
margin-left: -3%;
padding-left: 4%;
padding-right: 7%;
}
}
.activechatchannel__activeArticleActions {
position: absolute;
bottom: 0;
left: 0px;
right: 0px;
padding: 17px;
@include themeable(background,
theme-container-accent-background,
white);
z-index: 20;
border-top: 1px solid $light-medium-gray;
border-bottom: 1px solid $light-medium-gray;
border-right: 1px solid $light-medium-gray;
button {
margin-right: 15px;
width: 54px;
@include themeable(background,
theme-container-background,
#f2f3f5);
border-radius: 100px !important;
border-width: 0;
padding: 5px 10px;
&.active {
background-color: $green;
&.unicorn-reaction-button {
background-color: $purple;
}
&.readinglist-reaction-button {
background: lighten($bold-blue, 32%);
}
}
img {
width: 22px;
height: 22px;
}
}
}
.activecontent__githubrepo {}
.activecontent__githubrepoheader {
@ -1200,26 +1134,6 @@
}
}
.chat .container {
box-shadow: 0px 0px 0px #ffffff !important;
border: 0px !important;
margin-top: 8px !important;
.title {
width: 95%;
font-size: 18px;
h1 {
font-size: 28px !important;
}
}
.body {
font-size: 20px;
width: 96%;
}
}
.chatchanneljumpback {
position: relative;
}
@ -1267,6 +1181,64 @@
display: flex;
}
.chatchannels__richlink {
border: 1px solid darken($light-medium-gray, 15%);
border-radius: 3px;
box-shadow: 3px 3px 0px darken($light-medium-gray, 15%);
color: $black;
display: block;
max-width: 670px;
padding: 0px;
margin-bottom: 6px;
@include themeable(background,
theme-container-background,
white);
.chatchannels__richlinkmainimage {
position: relative;
width: 100%;
margin: auto;
background: transparent no-repeat center center;
background-size: cover;
z-index: 2;
padding-top: 42%;
}
h1 {
margin-top: 15px;
padding: 0px calc(10px + 0.2vw);
img {
height: 1.2em;
width: 1.2em;
margin-right: 5px;
vertical-align: -0.25em;
&.chatchannels__richlinkprofilepic {
border-radius: 100px;
@include themeable(border,
theme-border,
1px solid $light-medium-gray);
}
}
}
h4 {
margin-top: -12px;
margin-bottom: 15px;
@include themeable(color,
theme-secondary-color,
$medium-gray);
font-weight: 500;
padding: 0px calc(10px + 0.2vw);
img {
height: 22px;
width: 22px;
border-radius: 100px;
margin-right: 3px;
@include themeable(border,
theme-border,
1px solid $light-medium-gray);
vertical-align: -6px;
}
}
}
.message__delete__modal {
position: fixed;
background-color: rgba(255, 255, 255, 0.25);

View file

@ -261,6 +261,19 @@ body.trusted-status-true .trusted-visible-block {
display: block !important;
}
body.hidden-shell {
.top-bar, footer {
display: none;
}
.container {
margin-top: 20px;
}
.universal-page-content-wrapper {
margin-top: 0px !important;
margin-bottom: 30px;
}
}
.delete-confirm.container {
padding: 150px 10px;
min-height: 300px;

View file

@ -4,113 +4,8 @@ exports[`<Article /> should load article 1`] = `
<div
class="activechatchannel__activeArticle"
>
<div
class="activechatchannel__activeArticleDetails"
>
<a
href="/princesscarolyn/your-approval-means-nothing-to-me-42640"
rel="noopener noreferrer"
target="_blank"
>
<span
class="activechatchannel__activeArticleDetailsPath"
>
/princesscarolyn/your-approval-means-nothing-to-me-42640
</span>
</a>
</div>
<div
class="container"
>
<section>
<div
class="image image-final"
style={
Object {
"backgroundImage": "url(princess_carolyn_is_perfect.png",
}
}
/>
</section>
<div
class="title"
>
<h1>
Your Approval Means Nothing to Me
</h1>
<h3>
<a
class="author"
data-content="/users/00001"
href="/princesscarolyn"
>
<img
alt="princesscarolyn"
class="profile-pic"
src="/princesscarolyn.png"
/>
<span>
Princess Carolyn
</span>
<span
class="published-at"
>
| July 30, 2014
</span>
</a>
</h3>
</div>
<div
class="body"
>
<div
dangerouslySetInnerHTML={
Object {
"__html": "That woman can knock a drink back like a Kennedy at a wake for another Kennedy, but I'll be damned if she doesn't get s*** done!",
}
}
/>
</div>
</div>
<div
class="activechatchannel__activeArticleActions"
>
<button
class="heart-reaction-button "
data-category="like"
onClick={[Function]}
type="button"
>
<img
alt="like reaction"
data-category="like"
src=""
/>
</button>
<button
class="unicorn-reaction-button "
data-category="unicorn"
onClick={[Function]}
type="button"
>
<img
alt="unicorn reaction"
data-category="unicorn"
src=""
/>
</button>
<button
class="readinglist-reaction-button "
data-category="readinglist"
onClick={[Function]}
type="button"
>
<img
alt="readinglist reaction"
data-category="readinglist"
src=""
/>
</button>
</div>
<iframe
src="/princesscarolyn/your-approval-means-nothing-to-me-42640"
/>
</div>
`;

View file

@ -27,10 +27,10 @@ exports[`<ChannelDetails /> as a moderator should render and test snapshot 1`] =
<img
alt="channeluser1 profile"
class="channeldetails__userprofileimage"
data-content="users/userid1"
data-content="sidecar-user"
/>
<a
data-content="users/by_username?url=channeluser1"
data-content="sidecar-user"
href="/channeluser1"
style={
Object {
@ -48,10 +48,10 @@ exports[`<ChannelDetails /> as a moderator should render and test snapshot 1`] =
<img
alt="channeluser2 profile"
class="channeldetails__userprofileimage"
data-content="users/userid2"
data-content="sidecar-user"
/>
<a
data-content="users/by_username?url=channeluser2"
data-content="sidecar-user"
href="/channeluser2"
style={
Object {
@ -131,12 +131,12 @@ preact-render-spy (1 nodes)
<img
class="channeldetails__userprofileimage"
alt="channeluser1 profile"
data-content="users/userid1"
data-content="sidecar-user"
/>
<a
href="/channeluser1"
style="padding: 3px 0px;"
data-content="users/by_username?url=channeluser1"
data-content="sidecar-user"
>
channel user 1
</a>
@ -145,12 +145,12 @@ preact-render-spy (1 nodes)
<img
class="channeldetails__userprofileimage"
alt="channeluser2 profile"
data-content="users/userid2"
data-content="sidecar-user"
/>
<a
href="/channeluser2"
style="padding: 3px 0px;"
data-content="users/by_username?url=channeluser2"
data-content="sidecar-user"
>
channel user 2
</a>
@ -244,12 +244,12 @@ preact-render-spy (1 nodes)
<img
class="channeldetails__userprofileimage"
alt="channeluser1 profile"
data-content="users/userid1"
data-content="sidecar-user"
/>
<a
href="/channeluser1"
style="padding: 3px 0px;"
data-content="users/by_username?url=channeluser1"
data-content="sidecar-user"
>
channel user 1
</a>
@ -258,12 +258,12 @@ preact-render-spy (1 nodes)
<img
class="channeldetails__userprofileimage"
alt="channeluser2 profile"
data-content="users/userid2"
data-content="sidecar-user"
/>
<a
href="/channeluser2"
style="padding: 3px 0px;"
data-content="users/by_username?url=channeluser2"
data-content="sidecar-user"
>
channel user 2
</a>
@ -314,10 +314,10 @@ exports[`<ChannelDetails /> as a user should render and test snapshot 1`] = `
<img
alt="channeluser1 profile"
class="channeldetails__userprofileimage"
data-content="users/userid1"
data-content="sidecar-user"
/>
<a
data-content="users/by_username?url=channeluser1"
data-content="sidecar-user"
href="/channeluser1"
style={
Object {
@ -335,10 +335,10 @@ exports[`<ChannelDetails /> as a user should render and test snapshot 1`] = `
<img
alt="channeluser2 profile"
class="channeldetails__userprofileimage"
data-content="users/userid2"
data-content="sidecar-user"
/>
<a
data-content="users/by_username?url=channeluser2"
data-content="sidecar-user"
href="/channeluser2"
style={
Object {

View file

@ -8,7 +8,7 @@ exports[`<Message /> should render and test snapshot 1`] = `
class="chatmessage__profilepic"
>
<a
data-content="users/undefined"
data-content="sidecar-user"
href="/asdf"
rel="noopener noreferrer"
target="_blank"
@ -16,7 +16,7 @@ exports[`<Message /> should render and test snapshot 1`] = `
<img
alt="asdf profile"
class="chatmessagebody__profileimage"
data-content="users/undefined"
data-content="sidecar-user"
role="presentation"
src=""
/>
@ -42,7 +42,7 @@ exports[`<Message /> should render and test snapshot 1`] = `
>
<a
class="chatmessagebody__username--link"
data-content="users/undefined"
data-content="sidecar-user"
href="/asdf"
rel="noopener noreferrer"
target="_blank"

View file

@ -33,19 +33,7 @@ const sampleResponse = JSON.stringify({
const userArticle = {
type_of: 'article',
id: '42640',
cover_image: 'princess_carolyn_is_perfect.png',
path: '/princesscarolyn/your-approval-means-nothing-to-me-42640',
title: 'Your Approval Means Nothing to Me',
readable_publish_date: 'July 30, 2014',
body_html:
"That woman can knock a drink back like a Kennedy at a wake for another Kennedy, but I'll be damned if she doesn't get s*** done!",
user: {
id: '00001',
username: 'princesscarolyn',
name: 'Princess Carolyn',
profile_image_90: '/princesscarolyn.png',
},
};
const getArticle = () => <Article resource={userArticle} />;
@ -67,59 +55,7 @@ describe('<Article />', () => {
true,
);
expect(
context.find('.activechatchannel__activeArticleDetails').exists(),
context.find('iframe').exists(),
).toEqual(true);
expect(
context
.find('.activechatchannel__activeArticleDetails')
.childAt(0)
.attr('href'),
).toEqual(userArticle.path);
expect(
context.find('.activechatchannel__activeArticleDetailsPath').text(),
).toEqual(userArticle.path);
expect(
context
.find('.title')
.childAt(0)
.text(),
).toEqual(userArticle.title);
expect(context.find('.author').attr('href')).toEqual(
`/${userArticle.user.username}`,
);
expect(context.find('.profile-pic').attr('src')).toEqual(
userArticle.user.profile_image_90,
);
expect(context.find('.author').text()).toEqual(
`${userArticle.user.name} | ${userArticle.readable_publish_date}`,
);
expect(context.find('.published-at').text()).toEqual(
` | ${userArticle.readable_publish_date}`,
);
expect(
context
.find('.body')
.childAt(0)
.attr('dangerouslySetInnerHTML'),
).toEqual({ __html: userArticle.body_html });
// checks reaction
expect(
context.find('.activechatchannel__activeArticleActions').exists(),
).toEqual(true);
expect(context.find('.heart-reaction-button').exists()).toEqual(true);
expect(context.find('.unicorn-reaction-button').exists()).toEqual(true);
expect(context.find('.readinglist-reaction-button').exists()).toEqual(true);
// checks that only heart class has active
expect(context.find('.heart-reaction-button').attr('className')).toEqual(
'heart-reaction-button active',
);
expect(context.find('.unicorn-reaction-button').attr('className')).toEqual(
'unicorn-reaction-button ',
);
expect(
context.find('.readinglist-reaction-button').attr('className'),
).toEqual('readinglist-reaction-button ');
});
});

View file

@ -107,7 +107,7 @@ describe('<ChannelDetails />', () => {
.childAt(1)
.attr('data-content'),
).toEqual(
`users/by_username?url=${moddetails.channel_users[i].username}`,
`sidecar-user`,
);
}
@ -293,7 +293,7 @@ describe('<ChannelDetails />', () => {
.childAt(1)
.attr('data-content'),
).toEqual(
`users/by_username?url=${userdetails.channel_users[i].username}`,
`sidecar-user`,
);
}

View file

@ -1,11 +1,5 @@
import { h, Component } from 'preact';
import PropTypes from 'prop-types';
// eslint-disable-next-line import/no-unresolved
import heartImage from 'images/emoji/emoji-one-heart.png';
// eslint-disable-next-line import/no-unresolved
import unicornImage from 'images/emoji/emoji-one-unicorn.png';
// eslint-disable-next-line import/no-unresolved
import bookmarkImage from 'images/emoji/emoji-one-bookmark.png';
export default class Article extends Component {
static propTypes = {
@ -13,205 +7,12 @@ export default class Article extends Component {
id: PropTypes.string,
}).isRequired,
};
constructor(props) {
super(props);
this.state = {
userReactions: [],
optimisticUserReaction: null,
};
}
componentDidMount() {
const { resource } = this.props;
fetch(`/reactions?article_id=${resource.id}`, {
Accept: 'application/json',
'Content-Type': 'application/json',
credentials: 'same-origin',
})
.then(response => response.json())
.then(this.displayReactions)
.catch(this.displayReactionsFailure);
}
displayReactions = response => {
this.setState({ userReactions: response.reactions });
};
displayReactionsFailure = response => {
// eslint-disable-next-line no-console
console.log(response);
};
handleNewReactionResponse = response => {
let { userReactions: oldUserReactions } = this.state;
const foundReactions = oldUserReactions.filter(obj => {
return obj.category === response.category;
});
if (foundReactions.length === 0 && response.result === 'create') {
oldUserReactions.push({ category: response.category });
} else {
oldUserReactions = oldUserReactions.filter(obj => {
return obj.category !== response.category;
});
}
this.setState({
userReactions: oldUserReactions,
optimisticUserReaction: null,
});
};
handleNewReactionFailure = response => {
// eslint-disable-next-line no-console
console.log(response);
};
handleReactionClick = e => {
e.preventDefault();
const { target } = e;
const { resource: article } = this.props;
this.setState({ optimisticUserReaction: target.dataset.category });
fetch('/reactions', {
method: 'POST',
headers: {
Accept: 'application/json',
'X-CSRF-Token': window.csrfToken,
'Content-Type': 'application/json',
},
body: JSON.stringify({
reactable_type: 'Article',
reactable_id: article.id,
category: target.dataset.category,
}),
credentials: 'same-origin',
})
.then(response => response.json())
.then(this.handleNewReactionResponse)
.catch(this.handleNewReactionFailure);
};
actionButton = props => {
const types = {
heart: ['heart-reaction-button', 'like', heartImage],
unicorn: ['unicorn-reaction-button', 'unicorn', unicornImage],
readinglist: [
'readinglist-reaction-button',
'readinglist',
bookmarkImage,
],
};
const curType = types[props.reaction];
return (
<button
type="button"
className={`${curType[0]} ${props.reactedClass}`}
onClick={this.handleReactionClick}
data-category={curType[1]}
>
<img
src={curType[2]}
data-category={curType[1]}
alt={`${curType[1]} reaction`}
/>
</button>
);
};
render() {
const { resource: article } = this.props;
let heartReactedClass = '';
let unicornReactedClass = '';
let bookmarkReactedClass = '';
const { state } = this;
state.userReactions.forEach(reaction => {
if (
reaction.category === 'like' ||
state.optimisticUserReaction === 'like'
) {
heartReactedClass = 'active';
}
if (
reaction.category === 'unicorn' ||
state.optimisticUserReaction === 'unicorn'
) {
unicornReactedClass = 'active';
}
if (
reaction.category === 'readinglist' ||
state.optimisticUserReaction === 'readinglist'
) {
bookmarkReactedClass = 'active';
}
});
let coverImage = '';
if (article.cover_image) {
coverImage = (
<section>
<div
className="image image-final"
style={{ backgroundImage: `url(${article.cover_image}` }}
/>
</section>
);
}
return (
<div className="activechatchannel__activeArticle">
<div className="activechatchannel__activeArticleDetails">
<a href={article.path} target="_blank" rel="noopener noreferrer">
<span className="activechatchannel__activeArticleDetailsPath">
{article.path}
</span>
</a>
</div>
<div className="container">
{coverImage}
<div className="title">
<h1>{article.title}</h1>
<h3>
<a
href={`/${article.user.username}`}
className="author"
data-content={`/users/${article.user.id}`}
>
<img
className="profile-pic"
src={article.user.profile_image_90}
alt={article.user.username}
/>
<span>
{article.user.name}
{' '}
</span>
<span className="published-at">
{' '}
|
{' '}
{article.readable_publish_date}
</span>
</a>
</h3>
</div>
<div className="body">
{/* eslint-disable-next-line react/no-danger */}
<div dangerouslySetInnerHTML={{ __html: article.body_html }} />
</div>
</div>
<div className="activechatchannel__activeArticleActions">
<this.actionButton
reaction="heart"
reactedClass={heartReactedClass}
/>
<this.actionButton
reaction="unicorn"
reactedClass={unicornReactedClass}
/>
<this.actionButton
reaction="readinglist"
reactedClass={bookmarkReactedClass}
/>
</div>
<iframe src={article.path} title={article.title} />
</div>
);
}

View file

@ -111,12 +111,12 @@ class ChannelDetails extends Component {
className="channeldetails__userprofileimage"
src={user.profile_image}
alt={`${user.username} profile`}
data-content={`users/${user.id}`}
data-content='sidecar-user'
/>
<a
href={`/${user.username}`}
style={{ color: user.darker_color, padding: '3px 0px' }}
data-content={`users/by_username?url=${user.username}`}
data-content='sidecar-user'
>
{user.name}
</a>

View file

@ -681,7 +681,8 @@ export default class Chat extends Component {
}
const { target } = e;
if (target.dataset.content) {
const content = target.dataset.content || target.parentElement.dataset.content
if (content) {
e.preventDefault();
e.stopPropagation();
@ -695,24 +696,14 @@ export default class Chat extends Component {
this.setActiveContent,
null,
);
} else if (target.dataset.content.startsWith('users/')) {
this.setActiveContentState(activeChannelId, {
type_of: 'loading-user',
});
getContent(
`/api/${target.dataset.content}`,
this.setActiveContent,
null,
);
} else if (target.dataset.content.startsWith('articles/')) {
} else if (content.startsWith('sidecar') || content.startsWith('article')) { // article is legacy which can be removed shortly
this.setActiveContentState(activeChannelId, {
type_of: 'loading-post',
});
getContent(
`/api/${target.dataset.content}`,
this.setActiveContent,
null,
);
this.setActiveContent({
path: target.href || target.parentElement.href,
type_of: 'article'
})
} else if (target.dataset.content === 'exit') {
this.setActiveContentState(activeChannelId, null);
}
@ -1202,7 +1193,7 @@ export default class Chat extends Component {
<a
href={`/${activeChannel.channel_username}`}
onClick={this.triggerActiveContent}
data-content={`users/by_username?url=${activeChannel.channel_username}`}
data-content='sidecar-user'
>
{activeChannel.channel_modified_slug}
</a>
@ -1231,18 +1222,18 @@ export default class Chat extends Component {
const dataContent =
activeChannel.channel_type === 'direct'
? `users/by_username?url=${activeChannel.channel_username}`
? 'sidecar-user'
: `chat_channels/${activeChannelId}`;
return (
<div
<a
className="activechatchannel__channelconfig"
onClick={this.triggerActiveContent}
onKeyUp={e => {
if (e.keyCode === 13) this.triggerActiveContent(e);
}}
role="button"
tabIndex="0"
href={`/${activeChannel.channel_username}`}
data-content={dataContent}
>
<img
@ -1250,7 +1241,7 @@ export default class Chat extends Component {
alt="channel config"
data-content={dataContent}
/>
</div>
</a>
);
};

View file

@ -39,7 +39,7 @@ const Message = ({
href={`/${user}`}
target="_blank"
rel="noopener noreferrer"
data-content={`users/${userID}`}
data-content='sidecar-user'
onClick={onContentTrigger}
>
<img
@ -47,7 +47,7 @@ const Message = ({
className="chatmessagebody__profileimage"
src={profileImageUrl}
alt={`${user} profile`}
data-content={`users/${userID}`}
data-content='sidecar-user'
onClick={onContentTrigger}
/>
</a>
@ -65,7 +65,7 @@ const Message = ({
href={`/${user}`}
target="_blank"
rel="noopener noreferrer"
data-content={`users/${userID}`}
data-content='sidecar-user'
onClick={onContentTrigger}
>
{user}

View file

@ -50,18 +50,49 @@ class Message < ApplicationRecord
def append_rich_links(html)
doc = Nokogiri::HTML(html)
rich_style = "border: 1px solid #0a0a0a; border-radius: 3px; padding: 8px;"
doc.css("a").each do |anchor|
if (article = rich_link_article(anchor))
html += "<a style='color: #0a0a0a' href='#{article.path}'
target='_blank' data-content='articles/#{article.id}'>
<h1 style='#{rich_style}' data-content='articles/#{article.id}'>
#{article.title}</h1></a>".html_safe
if article = rich_link_article(anchor)
html += "<a href='#{article.path}'
class='chatchannels__richlink'
target='_blank' data-content='sidecar-article'>
#{"<div class='chatchannels__richlinkmainimage' style='background-image:url(" + cl_path(article.main_image) + ")' data-content='sidecar-article' ></div>" if article.main_image.present?}
<h1 data-content='sidecar-article'>#{article.title}</h1>
<h4 data-content='sidecar-article'><img src='#{ProfileImage.new(article.cached_user).get(90)}' /> #{article.cached_user.name}#{article.readable_publish_date}</h4>
</a>".html_safe
elsif tag = rich_link_tag(anchor)
html += "<a href='/t/#{tag.name}'
class='chatchannels__richlink'
target='_blank' data-content='sidecar-tag'>
<h1 data-content='sidecar-tag'>
#{"<img src='" + cl_path(tag.badge.badge_image_url) + "' data-content='sidecar-tag' style='transform:rotate(-5deg)' />" if tag.badge_id.present?}
##{tag.name}
</h1>
</a>".html_safe
elsif user = rich_user_link(anchor)
html += "<a href='#{user.path}'
class='chatchannels__richlink'
target='_blank' data-content='sidecar-user'>
<h1 data-content='sidecar-user'>
<img src='#{ProfileImage.new(user).get(90)}' data-content='sidecar-user' class='chatchannels__richlinkprofilepic' />
#{user.name}
</h1>
</a>".html_safe
end
end
html
end
def cl_path(img_src)
ActionController::Base.helpers.
cl_image_path(img_src,
type: "fetch",
width: 725,
crop: "limit",
flags: "progressive",
fetch_format: "auto",
sign_url: true)
end
def determine_user_validity
return unless chat_channel
@ -83,6 +114,14 @@ class Message < ApplicationRecord
Article.find_by(slug: link["href"].split("/")[4].split("?")[0]) if link["href"].include?("//#{ApplicationConfig['APP_DOMAIN']}/") && link["href"].split("/")[4]
end
def rich_link_tag(link)
Tag.find_by(name: link["href"].split("/t/")[1].split("/")[0]) if link["href"].include?("//#{ApplicationConfig['APP_DOMAIN']}/t/")
end
def rich_user_link(link)
User.find_by(username: link["href"].split("/")[3].split("/")[0]) if link["href"].include?("//#{ApplicationConfig['APP_DOMAIN']}/")
end
def send_email_if_appropriate
recipient = direct_receiver
return if !chat_channel.direct? ||

View file

@ -139,6 +139,9 @@
if (localStorage.getItem('config_minimize_newest_listings') === 'yes') {
document.getElementsByTagName("body")[0].classList.add("config_minimize_newest_listings");
}
if (window.frameElement) {
document.getElementsByTagName("body")[0].classList.add("hidden-shell");
}
} catch(e) {
console.log(e)
}

View file

@ -3,6 +3,7 @@ require "rails_helper"
RSpec.describe Message, type: :model do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:tag) { create(:tag) }
let(:chat_channel) { create(:chat_channel) }
let(:message) { create(:message, user: user) }
@ -40,12 +41,28 @@ RSpec.describe Message, type: :model do
let_it_be(:article) { create(:article) }
describe "#message_html" do
it "creates rich link with proper link" do
it "creates rich link with proper link for article" do
message.message_markdown = "hello http://#{ApplicationConfig['APP_DOMAIN']}#{article.path}"
message.validate!
expect(message.message_html).to include(article.title)
expect(message.message_html).to include("data-content")
expect(message.message_html).to include("sidecar-article")
end
it "creates rich link with proper link for user" do
message.message_markdown = "hello http://#{ApplicationConfig['APP_DOMAIN']}#{user.path}"
message.validate!
expect(message.message_html).to include(user.name)
expect(message.message_html).to include("sidecar-user")
end
it "creates rich link with proper link for tag" do
message.message_markdown = "hello http://#{ApplicationConfig['APP_DOMAIN']}/t/#{tag.name}"
message.validate!
expect(message.message_html).to include(tag.name)
expect(message.message_html).to include("sidecar-tag")
end
it "creates rich link with non-rich link" do