layout 'modal'
+ before_action :set_interaction_type
before_action :set_status
before_action :set_body_classes
+ before_action :set_pack
def new
@remote_follow = RemoteFollow.new(session_params)
@hide_header = true
end
+ def set_pack
+ use_pack 'modal'
+ end
++
+ def set_interaction_type
+ @interaction_type = %w(reply reblog favourite).include?(params[:type]) ? params[:type] : 'reply'
+ end
end
if (parallaxComponents.length > 0 ) {
new Rellax('.parallax', { speed: -1 });
}
-
- const history = createHistory();
- const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status');
- const location = history.location;
-
- if (detailedStatuses.length === 1 && (!location.state || !location.state.scrolledToDetailedStatus)) {
- detailedStatuses[0].scrollIntoView();
- history.replace(location.pathname, { ...location.state, scrolledToDetailedStatus: true });
- }
});
-
- delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
- if (button !== 0) {
- return true;
- }
- window.location.href = target.href;
- return false;
- });
-
- delegate(document, '.status__content__spoiler-link', 'click', ({ target }) => {
- const contentEl = target.parentNode.parentNode.querySelector('.e-content');
-
- if (contentEl.style.display === 'block') {
- contentEl.style.display = 'none';
- target.parentNode.style.marginBottom = 0;
- } else {
- contentEl.style.display = 'block';
- target.parentNode.style.marginBottom = null;
- }
-
- return false;
- });
-
- delegate(document, '.modal-button', 'click', e => {
- e.preventDefault();
-
- let href;
-
- if (e.target.nodeName !== 'A') {
- href = e.target.parentNode.href;
- } else {
- href = e.target.href;
- }
-
- window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
- });
-
- delegate(document, '#account_display_name', 'input', ({ target }) => {
- const name = document.querySelector('.card .display-name strong');
- if (name) {
- if (target.value) {
- name.innerHTML = emojify(escapeTextContentForBrowser(target.value));
- } else {
- name.textContent = document.querySelector('#default_account_display_name').textContent;
- }
- }
- });
-
- delegate(document, '#account_avatar', 'change', ({ target }) => {
- const avatar = document.querySelector('.card .avatar img');
- const [file] = target.files || [];
- const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
-
- avatar.src = url;
- });
-
- delegate(document, '#account_header', 'change', ({ target }) => {
- const header = document.querySelector('.card .card__img img');
- const [file] = target.files || [];
- const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc;
-
- header.src = url;
- });
-
- delegate(document, '#account_locked', 'change', ({ target }) => {
- const lock = document.querySelector('.card .display-name i');
-
- if (target.checked) {
- lock.style.display = 'inline';
- } else {
- lock.style.display = 'none';
- }
- });
-
- delegate(document, '.input-copy input', 'click', ({ target }) => {
- target.select();
- });
-
- delegate(document, '.input-copy button', 'click', ({ target }) => {
- const input = target.parentNode.querySelector('.input-copy__wrapper input');
-
- input.focus();
- input.select();
-
- try {
- if (document.execCommand('copy')) {
- input.blur();
- target.parentNode.classList.add('copied');
-
- setTimeout(() => {
- target.parentNode.classList.remove('copied');
- }, 700);
- }
- } catch (err) {
- console.error(err);
- }
- });
}
loadPolyfills().then(main).catch(error => {
validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? }
validates_with UniqueUsernameValidator, if: -> { local? && will_save_change_to_username? }
validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? }
- validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
- validates :note, note_length: { maximum: 160 }, if: -> { local? && will_save_change_to_note? }
- validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? }
+ validates :display_name, length: { maximum: MAX_DISPLAY_NAME_LENGTH }, if: -> { local? && will_save_change_to_display_name? }
- validate :note_length_does_not_exceed_length_limit, if: -> { local? && will_save_change_to_note? }
++ validates :note, note_length: { maximum: MAX_NOTE_LENGTH }, if: -> { local? && will_save_change_to_note? }
+ validates :fields, length: { maximum: MAX_FIELDS }, if: -> { local? && will_save_change_to_fields? }
scope :remote, -> { where.not(domain: nil) }
scope :local, -> { where(domain: nil) }
# @option [Doorkeeper::Application] :application
# @option [String] :idempotency Optional idempotency key
# @return [Status]
- def call(account, text, in_reply_to = nil, **options)
- if options[:idempotency].present?
- existing_id = redis.get("idempotency:status:#{account.id}:#{options[:idempotency]}")
- return Status.find(existing_id) if existing_id
+ def call(account, options = {})
+ @account = account
+ @options = options
+ @text = @options[:text] || ''
+ @in_reply_to = @options[:thread]
+
+ return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
+
+ validate_media!
+ preprocess_attributes!
+
+ if scheduled?
+ schedule_status!
+ else
+ process_status!
+ postprocess_status!
+ bump_potential_friendship!
end
- media = validate_media!(options[:media_ids])
- status = nil
- if text.blank? && options[:spoiler_text].present?
- text = '.'
- text = media.find(&:video?) ? '📹' : '🖼' if media.size > 0
+ redis.setex(idempotency_key, 3_600, @status.id) if idempotency_given?
+
+ @status
+ end
+
+ private
+
+ def preprocess_attributes!
- @text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
++ if @text.blank? && @options[:spoiler_text].present?
++ @text = '.'
++ @text = @media.find(&:video?) ? '📹' : '🖼' if @media.size > 0
+ end
+ @visibility = @options[:visibility] || @account.user&.setting_default_privacy
+ @visibility = :unlisted if @visibility == :public && @account.silenced
+ @scheduled_at = @options[:scheduled_at]&.to_datetime
+ @scheduled_at = nil if scheduled_in_the_past?
+ end
- visibility = options[:visibility] || account.user&.setting_default_privacy
- visibility = :unlisted if visibility == :public && account.silenced
+ def process_status!
+ # The following transaction block is needed to wrap the UPDATEs to
+ # the media attachments when the status is created
ApplicationRecord.transaction do
- status = account.statuses.create!(text: text,
- media_attachments: media || [],
- thread: in_reply_to,
- sensitive: (options[:sensitive].nil? ? account.user&.setting_default_sensitive : options[:sensitive]) || options[:spoiler_text].present?,
- spoiler_text: options[:spoiler_text] || '',
- visibility: visibility,
- language: language_from_option(options[:language]) || account.user&.setting_default_language&.presence || LanguageDetector.instance.detect(text, account),
- application: options[:application])
+ @status = @account.statuses.create!(status_attributes)
end
- process_hashtags_service.call(status)
- process_mentions_service.call(status)
+ process_hashtags_service.call(@status)
+ process_mentions_service.call(@status)
+ end
- LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text?
- DistributionWorker.perform_async(status.id)
+ def schedule_status!
+ if @account.statuses.build(status_attributes).valid?
+ # The following transaction block is needed to wrap the UPDATEs to
+ # the media attachments when the scheduled status is created
- unless status.local_only?
- Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id)
- ActivityPub::DistributionWorker.perform_async(status.id)
+ ApplicationRecord.transaction do
+ @status = @account.scheduled_statuses.create!(scheduled_status_attributes)
+ end
+ else
+ raise ActiveRecord::RecordInvalid
end
+ end
- if options[:idempotency].present?
- redis.setex("idempotency:status:#{account.id}:#{options[:idempotency]}", 3_600, status.id)
+ def postprocess_status!
+ LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text?
+ DistributionWorker.perform_async(@status.id)
- Pubsubhubbub::DistributionWorker.perform_async(@status.stream_entry.id)
- ActivityPub::DistributionWorker.perform_async(@status.id)
++ unless @status.local_only?
++ Pubsubhubbub::DistributionWorker.perform_async(@status.stream_entry.id)
++ ActivityPub::DistributionWorker.perform_async(@status.id)
+ end
-
- bump_potential_friendship(account, status)
-
- status
end
- private
-
- def validate_media!(media_ids)
- return if media_ids.blank? || !media_ids.is_a?(Enumerable)
+ def validate_media!
+ return if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
- raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if media_ids.size > 4
+ raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4
- media = MediaAttachment.where(status_id: nil).where(id: media_ids.take(4).map(&:to_i))
+ @media = MediaAttachment.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i))
- raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if media.size > 1 && media.find(&:video?)
-
- media
+ raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?)
end
def language_from_option(str)
- else
= link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener'
·
- = link_to remote_interaction_path(status), class: 'modal-button detailed-status__link' do
+ = link_to remote_interaction_path(status, type: :reply), class: 'modal-button detailed-status__link' do
- = fa_icon('reply')
+ - if status.in_reply_to_id.nil?
+ = fa_icon('reply')
+ - else
+ = fa_icon('reply-all')
%span.detailed-status__reblogs>= number_to_human status.replies_count, strip_insignificant_zeros: true
= " "
·
%p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
%span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}
%a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
- .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
+ .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }<
+ = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
- - unless status.media_attachments.empty?
+ - if !status.media_attachments.empty?
- if status.media_attachments.first.video?
- video = status.media_attachments.first
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description
.status__action-bar
.status__action-bar__counter
- = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
+ = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
- = fa_icon 'reply fw'
+ - if status.in_reply_to_id.nil?
+ = fa_icon 'reply fw'
+ - else
+ = fa_icon 'reply-all fw'
.status__action-bar__counter__label= obscured_counter status.replies_count
- = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
+ = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
- if status.public_visibility? || status.unlisted_visibility?
= fa_icon 'retweet fw'
- elsif status.private_visibility?
<ul>
<li><em>Základné informácie o účte</em>: Ak sa na tomto serveri zaregistruješ, budeš môcť byť požiadaný/á zadať prezývku, emailovú adresu a heslo. Budeš tiež môcť zadať aj ďalšie profilové údaje, ako napríklad meno a životopis, a nahrať profilovú fotku aj obrázok v záhlaví. Tvoja prezývka, meno, životopis, profilová fotka a obrázok v záhlaví sú vždy zobrazené verejne.</li><li><em>Príspevky, sledovania a iné verejné informácie</em>:
+ Zoznam ľudí, ktorých sleduješ je zobrazený verejne, a to isté platí aj pre zoznam tvojích následovateľov. Keď pošleš správu, ukladá sa jej dátum a čas, ale aj z akej aplikácie bola poslaná. Správy môžu obsahovať mediálne prílohy, ako obrázky a videá. Verejné, a nezaradené príspevky sú verejne prístupné. Keď si pripneš príspevok na svoj profil, toto je tiež verejne dostupnou informáciou. Tvoje príspevky sú takisto doručené tvojím sledovateľom, a to aj v rámci iných serverov, kde je potom uložená kópia tvojho príspevku. Ak vymažeš príspevok, táto akcia bude takisto doručená tvojím sledovateľom. Vyzdvihnutie, alebo obľúbenie iného príspevku je vždy verejne viditeľné.</li>
+
+ <li><em>Priame príspevky, a príspevky iba pre sledovateľov</em>: Všetky príspevky sú uložené a spracované na serveri. Príspevky iba pre sledovateľov sú doručené tvojím sledovateľom a užívateľom ktorí sú v nich spomenutí, pričom priame príspevky sú doručené iba tím užívateľom ktorí sú v nich spomenutí. V niektorých prípadoch to môže znamenať, že tieto príspevkz sú doručené aj vrámci iných serverov, a kópie príspevkov sú tam uložené.
+ V dobrej viere robíme všetko preto, aby bol prístup k tímto príspevkom vymedzený iba pre oprávnených používateľov, ale môže sa stať, že iné servery v tomto ohľade zlyhajú. Preto je dôležité prezrieť si a zhodnotiť, na aké servery patria tvoji následovatelia. V nastaveniach si môžeš zapnúť voľbu ručne povoľovať a odmietať nových následovateľov.
+ <em>Prosím maj na pamäti, že správcovia tvojho, aj vzdialeného obdŕžiavajúceho servera majú možnosť vidieť dané príspevky a správy, ale aj že obdŕžitelia týchto správ si ich môzu odfotiť, skopírovať, alebo ich inak zdieľať. <em>Nezdieľaj žiadne nebezpečné, alebo ohrozujúce správy pomocou Mastodonu!</em></li>
+
+ <li><em>IPky a iné metadáta</em>: Keď sa prihlásiš, zaznamenáva sa IP adresa z ktorej si sa prihlásil/a, takisto ako aj názov tvojho prehliadača. Všetky zaznamenané sezóny sú pre teba dostupné na konktolu, alebo na zamietnutie prístupu v nastaveniach. Posledná použitá IP adresa je uložená až po dobu dvanástich mesiacov. Môžeme si tiež ponechať serverové záznamy, ktoré obsahujú IP adresu každej požiadavky na tento server.</li>
+ </ul>
+
+ <hr class="spacer" />
+
+ <h3 id="use">
title: Podmienky užívania, a pravidlá súkromia pre %{instance}
themes:
- contrast: Vysoký kontrast
default: Mastodon
mastodon-light: Mastodon (svetlý)
time:
- # config/app.yml for rails-settings-cached
- #
- # This file contains default values, and does not need to be edited
- # when configuring an instance. These settings may be changed by an
- # Administrator using the Web UI.
- #
- # For more information, see docs/Running-Mastodon/Administration-guide.md
- #
+ # This file contains default values, and does not need to be edited. All
+ # important settings can be changed from the admin interface.
+
defaults: &defaults
- site_title: Mastodon
+ site_title: 'dev.glitch.social'
site_short_description: ''
site_description: ''
site_extended_description: ''
expect(FeedManager.instance.filter?(:home, status, bob.id)).to be true
end
- status = PostStatusService.new.call(alice, 'Hey @jeff')
+ it 'returns true for status by followee mentioning muted account' do
+ bob.mute!(jeff)
+ bob.follow!(alice)
++ status = PostStatusService.new.call(alice, text: 'Hey @jeff')
+ expect(FeedManager.instance.filter?(:home, status, bob.id)).to be true
+ end
+
it 'returns true for reblog of a personally blocked domain' do
alice.block_domain!('example.com')
alice.follow!(jeff)