Skip to content

Commit

Permalink
Merge commit '24ef8255b3f9b44cb54f49bc78fe3382a7070b1a' into glitch-s…
Browse files Browse the repository at this point in the history
…oc/merge-upstream

Conflicts:
- `app/helpers/accounts_helper.rb`:
  Upstream removed a helper, textually adjacent to a glitch-soc-only one.
  Not really a conflict.
  Removed the helper as upstream did.
- `app/views/layouts/embedded.html.haml`:
  Conflicts due to theming system.
  Adapted upstream's change to our theming system.
- `app/views/statuses/_simple_status.html.haml`:
  Removed upstream, but we had local changes.
  Removed as upstream did.
  • Loading branch information
ClearlyClaire committed Sep 12, 2024
2 parents 2d31cdb + 24ef825 commit 3465d39
Show file tree
Hide file tree
Showing 126 changed files with 991 additions and 2,169 deletions.
8 changes: 0 additions & 8 deletions app/helpers/accounts_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@ def acct(account)
end
end

def account_action_button(account)
return if account.memorial? || account.moved?

link_to ActivityPub::TagManager.instance.url_for(account), class: 'button logo-button', target: '_new' do
safe_join([logo_as_symbol, t('accounts.follow')])
end
end

def hide_followers_count?(account)
Setting.hide_followers_count || account.user&.settings&.[]('hide_followers_count')
end
Expand Down
36 changes: 0 additions & 36 deletions app/helpers/media_component_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,6 @@ def render_media_gallery_component(status, **options)
end
end

def render_card_component(status, **options)
component_params = {
sensitive: sensitive_viewer?(status, current_account),
card: serialize_status_card(status).as_json,
}.merge(**options)

react_component :card, component_params
end

def render_poll_component(status, **options)
component_params = {
disabled: true,
poll: serialize_status_poll(status).as_json,
}.merge(**options)

react_component :poll, component_params do
render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: prefers_autoplay? }
end
end

private

def serialize_media_attachment(attachment)
Expand All @@ -86,22 +66,6 @@ def serialize_media_attachment(attachment)
)
end

def serialize_status_card(status)
ActiveModelSerializers::SerializableResource.new(
status.preview_card,
serializer: REST::PreviewCardSerializer
)
end

def serialize_status_poll(status)
ActiveModelSerializers::SerializableResource.new(
status.preloadable_poll,
serializer: REST::PollSerializer,
scope: current_user,
scope_name: :current_user
)
end

def sensitive_viewer?(status, account)
if !account.nil? && account.id == status.account_id
status.sensitive
Expand Down
74 changes: 74 additions & 0 deletions app/javascript/entrypoints/embed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import './public-path';
import { createRoot } from 'react-dom/client';

import { afterInitialRender } from 'mastodon/../hooks/useRenderSignal';

import { start } from '../mastodon/common';
import { Status } from '../mastodon/features/standalone/status';
import { loadPolyfills } from '../mastodon/polyfills';
import ready from '../mastodon/ready';

start();

function loaded() {
const mountNode = document.getElementById('mastodon-status');

if (mountNode) {
const attr = mountNode.getAttribute('data-props');

if (!attr) return;

const props = JSON.parse(attr) as { id: string; locale: string };
const root = createRoot(mountNode);

root.render(<Status {...props} />);
}
}

function main() {
ready(loaded).catch((error: unknown) => {
console.error(error);
});
}

loadPolyfills()
.then(main)
.catch((error: unknown) => {
console.error(error);
});

interface SetHeightMessage {
type: 'setHeight';
id: string;
height: number;
}

function isSetHeightMessage(data: unknown): data is SetHeightMessage {
if (
data &&
typeof data === 'object' &&
'type' in data &&
data.type === 'setHeight'
)
return true;
else return false;
}

window.addEventListener('message', (e) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typings are not correct, it can be null in very rare cases
if (!e.data || !isSetHeightMessage(e.data) || !window.parent) return;

const data = e.data;

// We use a timeout to allow for the React page to render before calculating the height
afterInitialRender(() => {
window.parent.postMessage(
{
type: 'setHeight',
id: data.id,
height: document.getElementsByTagName('html')[0]?.scrollHeight,
},
'*',
);
});
});
37 changes: 0 additions & 37 deletions app/javascript/entrypoints/public.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,43 +37,6 @@ const messages = defineMessages({
},
});

interface SetHeightMessage {
type: 'setHeight';
id: string;
height: number;
}

function isSetHeightMessage(data: unknown): data is SetHeightMessage {
if (
data &&
typeof data === 'object' &&
'type' in data &&
data.type === 'setHeight'
)
return true;
else return false;
}

window.addEventListener('message', (e) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typings are not correct, it can be null in very rare cases
if (!e.data || !isSetHeightMessage(e.data) || !window.parent) return;

const data = e.data;

ready(() => {
window.parent.postMessage(
{
type: 'setHeight',
id: data.id,
height: document.getElementsByTagName('html')[0]?.scrollHeight,
},
'*',
);
}).catch((e: unknown) => {
console.error('Error in setHeightMessage postMessage', e);
});
});

function loaded() {
const { messages: localeData } = getLocale();

Expand Down
32 changes: 32 additions & 0 deletions app/javascript/hooks/useRenderSignal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// This hook allows a component to signal that it's done rendering in a way that
// can be used by e.g. our embed code to determine correct iframe height

let renderSignalReceived = false;

type Callback = () => void;

let onInitialRender: Callback;

export const afterInitialRender = (callback: Callback) => {
if (renderSignalReceived) {
callback();
} else {
onInitialRender = callback;
}
};

export const useRenderSignal = () => {
return () => {
if (renderSignalReceived) {
return;
}

renderSignalReceived = true;

if (typeof onInitialRender !== 'undefined') {
window.requestAnimationFrame(() => {
onInitialRender();
});
}
};
};
6 changes: 4 additions & 2 deletions app/javascript/mastodon/actions/statuses.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@ export function fetchStatusRequest(id, skipLoading) {
};
}

export function fetchStatus(id, forceFetch = false) {
export function fetchStatus(id, forceFetch = false, alsoFetchContext = true) {
return (dispatch, getState) => {
const skipLoading = !forceFetch && getState().getIn(['statuses', id], null) !== null;

dispatch(fetchContext(id));
if (alsoFetchContext) {
dispatch(fetchContext(id));
}

if (skipLoading) {
return;
Expand Down
90 changes: 90 additions & 0 deletions app/javascript/mastodon/components/copy_paste_text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useRef, useState, useCallback } from 'react';

import { FormattedMessage } from 'react-intl';

import classNames from 'classnames';

import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
import { useTimeout } from 'mastodon/../hooks/useTimeout';
import { Icon } from 'mastodon/components/icon';

export const CopyPasteText: React.FC<{ value: string }> = ({ value }) => {
const inputRef = useRef<HTMLTextAreaElement>(null);
const [copied, setCopied] = useState(false);
const [focused, setFocused] = useState(false);
const [setAnimationTimeout] = useTimeout();

const handleInputClick = useCallback(() => {
setCopied(false);

if (inputRef.current) {
inputRef.current.focus();
inputRef.current.select();
inputRef.current.setSelectionRange(0, value.length);
}
}, [setCopied, value]);

const handleButtonClick = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
void navigator.clipboard.writeText(value);
inputRef.current?.blur();
setCopied(true);
setAnimationTimeout(() => {
setCopied(false);
}, 700);
},
[setCopied, setAnimationTimeout, value],
);

const handleKeyUp = useCallback(
(e: React.KeyboardEvent) => {
if (e.key !== ' ') return;
void navigator.clipboard.writeText(value);
setCopied(true);
setAnimationTimeout(() => {
setCopied(false);
}, 700);
},
[setCopied, setAnimationTimeout, value],
);

const handleFocus = useCallback(() => {
setFocused(true);
}, [setFocused]);

const handleBlur = useCallback(() => {
setFocused(false);
}, [setFocused]);

return (
<div
className={classNames('copy-paste-text', { copied, focused })}
tabIndex={0}
role='button'
onClick={handleInputClick}
onKeyUp={handleKeyUp}
>
<textarea
readOnly
value={value}
ref={inputRef}
onClick={handleInputClick}
onFocus={handleFocus}
onBlur={handleBlur}
/>

<button className='button' onClick={handleButtonClick}>
<Icon id='copy' icon={ContentCopyIcon} />{' '}
{copied ? (
<FormattedMessage id='copypaste.copied' defaultMessage='Copied' />
) : (
<FormattedMessage
id='copypaste.copy_to_clipboard'
defaultMessage='Copy to clipboard'
/>
)}
</button>
</div>
);
};
7 changes: 7 additions & 0 deletions app/javascript/mastodon/components/logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ export const WordmarkLogo: React.FC = () => (
</svg>
);

export const IconLogo: React.FC = () => (
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
<title>Mastodon</title>
<use xlinkHref='#logo-symbol-icon' />
</svg>
);

export const SymbolLogo: React.FC = () => (
<img src={logo} alt='Mastodon' className='logo logo--icon' />
);
6 changes: 2 additions & 4 deletions app/javascript/mastodon/components/more_from_author.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ import PropTypes from 'prop-types';

import { FormattedMessage } from 'react-intl';

import { IconLogo } from 'mastodon/components/logo';
import { AuthorLink } from 'mastodon/features/explore/components/author_link';

export const MoreFromAuthor = ({ accountId }) => (
<div className='more-from-author'>
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
<use xlinkHref='#logo-symbol-icon' />
</svg>

<IconLogo />
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <AuthorLink accountId={accountId} /> }} />
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/mastodon/components/status_action_bar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const messages = defineMessages({
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
embed: { id: 'status.embed', defaultMessage: 'Embed' },
embed: { id: 'status.embed', defaultMessage: 'Get embed code' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
Expand Down
6 changes: 1 addition & 5 deletions app/javascript/mastodon/containers/status_container.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
unmuteAccount,
unblockAccount,
} from '../actions/accounts';
import { showAlertForError } from '../actions/alerts';
import { initBlockModal } from '../actions/blocks';
import {
replyCompose,
Expand Down Expand Up @@ -100,10 +99,7 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
onEmbed (status) {
dispatch(openModal({
modalType: 'EMBED',
modalProps: {
id: status.get('id'),
onError: error => dispatch(showAlertForError(error)),
},
modalProps: { id: status.get('id') },
}));
},

Expand Down
Loading

0 comments on commit 3465d39

Please sign in to comment.