mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 08:43:25 +08:00
DEV: Add site age and site activities section to the redesigned about page (#28214)
This commit continues on work laid out by 6039b513fe
to redesign the /about page. In this commit, we add the site age and a section on the right hand side to show site activities/statistics such as topics, posts, sign-ups, likes etc.
This commit is contained in:
parent
1a09d6b246
commit
5dbf812d32
|
@ -2,6 +2,7 @@ import Component from "@glimmer/component";
|
||||||
import { hash } from "@ember/helper";
|
import { hash } from "@ember/helper";
|
||||||
import { htmlSafe } from "@ember/template";
|
import { htmlSafe } from "@ember/template";
|
||||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||||
|
import { number } from "discourse/lib/formatter";
|
||||||
import dIcon from "discourse-common/helpers/d-icon";
|
import dIcon from "discourse-common/helpers/d-icon";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
import escape from "discourse-common/lib/escape";
|
import escape from "discourse-common/lib/escape";
|
||||||
|
@ -46,6 +47,61 @@ export default class AboutPage extends Component {
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
class: "site-creation-date",
|
||||||
|
icon: "calendar-alt",
|
||||||
|
text: this.siteAgeString,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
get siteActivities() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
icon: "scroll",
|
||||||
|
class: "topics",
|
||||||
|
activityText: I18n.t("about.activities.topics", {
|
||||||
|
count: this.args.model.stats.topics_7_days,
|
||||||
|
formatted_number: number(this.args.model.stats.topics_7_days),
|
||||||
|
}),
|
||||||
|
period: I18n.t("about.activities.periods.last_7_days"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "pencil-alt",
|
||||||
|
class: "posts",
|
||||||
|
activityText: I18n.t("about.activities.posts", {
|
||||||
|
count: this.args.model.stats.posts_last_day,
|
||||||
|
formatted_number: number(this.args.model.stats.posts_last_day),
|
||||||
|
}),
|
||||||
|
period: I18n.t("about.activities.periods.today"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "user-friends",
|
||||||
|
class: "active-users",
|
||||||
|
activityText: I18n.t("about.activities.active_users", {
|
||||||
|
count: this.args.model.stats.active_users_7_days,
|
||||||
|
formatted_number: number(this.args.model.stats.active_users_7_days),
|
||||||
|
}),
|
||||||
|
period: I18n.t("about.activities.periods.last_7_days"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "user-plus",
|
||||||
|
class: "sign-ups",
|
||||||
|
activityText: I18n.t("about.activities.sign_ups", {
|
||||||
|
count: this.args.model.stats.users_7_days,
|
||||||
|
formatted_number: number(this.args.model.stats.users_7_days),
|
||||||
|
}),
|
||||||
|
period: I18n.t("about.activities.periods.last_7_days"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "heart",
|
||||||
|
class: "likes",
|
||||||
|
activityText: I18n.t("about.activities.likes", {
|
||||||
|
count: this.args.model.stats.likes_count,
|
||||||
|
formatted_number: number(this.args.model.stats.likes_count),
|
||||||
|
}),
|
||||||
|
period: I18n.t("about.activities.periods.all_time"),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +122,22 @@ export default class AboutPage extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get siteAgeString() {
|
||||||
|
const creationDate = new Date(this.args.model.site_creation_date);
|
||||||
|
|
||||||
|
let diff = new Date() - creationDate;
|
||||||
|
diff /= 1000 * 3600 * 24 * 30;
|
||||||
|
|
||||||
|
if (diff < 1) {
|
||||||
|
return I18n.t("about.site_age.less_than_one_month");
|
||||||
|
} else if (diff < 12) {
|
||||||
|
return I18n.t("about.site_age.month", { count: Math.round(diff) });
|
||||||
|
} else {
|
||||||
|
diff /= 12;
|
||||||
|
return I18n.t("about.site_age.year", { count: Math.round(diff) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="about__header">
|
<section class="about__header">
|
||||||
<img class="about__banner" src={{@model.banner_image}} />
|
<img class="about__banner" src={{@model.banner_image}} />
|
||||||
|
@ -91,11 +163,29 @@ export default class AboutPage extends Component {
|
||||||
<div>{{htmlSafe @model.extended_site_description}}</div>
|
<div>{{htmlSafe @model.extended_site_description}}</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="about__right-side">
|
<section class="about__right-side">
|
||||||
<h4>{{i18n "about.contact"}}</h4>
|
<h3>{{i18n "about.contact"}}</h3>
|
||||||
{{#if this.contactInfo}}
|
{{#if this.contactInfo}}
|
||||||
<p>{{htmlSafe this.contactInfo}}</p>
|
<p>{{htmlSafe this.contactInfo}}</p>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<p>{{i18n "about.report_inappropriate_content"}}</p>
|
<p>{{i18n "about.report_inappropriate_content"}}</p>
|
||||||
|
<h3>{{i18n "about.site_activity"}}</h3>
|
||||||
|
<div class="about__activities">
|
||||||
|
{{#each this.siteActivities as |activity|}}
|
||||||
|
<div class="about__activities-item {{activity.class}}">
|
||||||
|
<span class="about__activities-item-icon">{{dIcon
|
||||||
|
activity.icon
|
||||||
|
}}</span>
|
||||||
|
<span class="about__activities-item-type">
|
||||||
|
<div
|
||||||
|
class="about__activities-item-count"
|
||||||
|
>{{activity.activityText}}</div>
|
||||||
|
<div
|
||||||
|
class="about__activities-item-period"
|
||||||
|
>{{activity.period}}</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.about {
|
.about {
|
||||||
&__main-content {
|
&__main-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 2fr 1fr;
|
grid-template-columns: 2.5fr 1fr;
|
||||||
column-gap: 4em;
|
column-gap: 4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,20 @@
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__activities-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__activities-item-icon {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__activities-item-period {
|
||||||
|
font-size: var(--font-down-2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section.about {
|
section.about {
|
||||||
|
|
|
@ -64,6 +64,10 @@ class About
|
||||||
GlobalPath.full_cdn_url(url)
|
GlobalPath.full_cdn_url(url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def site_creation_date
|
||||||
|
Discourse.site_creation_date
|
||||||
|
end
|
||||||
|
|
||||||
def moderators
|
def moderators
|
||||||
@moderators ||= User.where(moderator: true, admin: false).human_users.order("last_seen_at DESC")
|
@moderators ||= User.where(moderator: true, admin: false).human_users.order("last_seen_at DESC")
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,7 @@ class AboutSerializer < ApplicationSerializer
|
||||||
:description,
|
:description,
|
||||||
:extended_site_description,
|
:extended_site_description,
|
||||||
:banner_image,
|
:banner_image,
|
||||||
|
:site_creation_date,
|
||||||
:title,
|
:title,
|
||||||
:locale,
|
:locale,
|
||||||
:version,
|
:version,
|
||||||
|
@ -62,6 +63,10 @@ class AboutSerializer < ApplicationSerializer
|
||||||
render_redesigned_about_page?
|
render_redesigned_about_page?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def include_site_creation_date?
|
||||||
|
render_redesigned_about_page?
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def can_see_about_stats
|
def can_see_about_stats
|
||||||
|
|
|
@ -345,6 +345,27 @@ en:
|
||||||
active_user_count: "Active users"
|
active_user_count: "Active users"
|
||||||
contact: "Contact us"
|
contact: "Contact us"
|
||||||
contact_info: "In the event of a critical issue or urgent matter affecting this site, please contact us at %{contact_info}."
|
contact_info: "In the event of a critical issue or urgent matter affecting this site, please contact us at %{contact_info}."
|
||||||
|
site_activity: "Site activity"
|
||||||
|
activities:
|
||||||
|
topics:
|
||||||
|
one: "%{formatted_number} topic"
|
||||||
|
other: "%{formatted_number} topics"
|
||||||
|
posts:
|
||||||
|
one: "%{formatted_number} post"
|
||||||
|
other: "%{formatted_number} posts"
|
||||||
|
active_users:
|
||||||
|
one: "%{formatted_number} active user"
|
||||||
|
other: "%{formatted_number} active users"
|
||||||
|
sign_ups:
|
||||||
|
one: "%{formatted_number} sign-up"
|
||||||
|
other: "%{formatted_number} sign-ups"
|
||||||
|
likes:
|
||||||
|
one: "%{formatted_number} like"
|
||||||
|
other: "%{formatted_number} likes"
|
||||||
|
periods:
|
||||||
|
last_7_days: "in the last 7 days"
|
||||||
|
today: "today"
|
||||||
|
all_time: "all time"
|
||||||
member_count:
|
member_count:
|
||||||
one: "%{formatted_number} Member"
|
one: "%{formatted_number} Member"
|
||||||
other: "%{formatted_number} Members"
|
other: "%{formatted_number} Members"
|
||||||
|
@ -355,6 +376,14 @@ en:
|
||||||
one: "%{formatted_number} Moderator"
|
one: "%{formatted_number} Moderator"
|
||||||
other: "%{formatted_number} Moderators"
|
other: "%{formatted_number} Moderators"
|
||||||
report_inappropriate_content: "If you come across any inappropriate content, don't hesitate to start a conversation with our moderators and admins. Remember to log in before reaching out."
|
report_inappropriate_content: "If you come across any inappropriate content, don't hesitate to start a conversation with our moderators and admins. Remember to log in before reaching out."
|
||||||
|
site_age:
|
||||||
|
less_than_one_month: "Created < 1 month ago"
|
||||||
|
month:
|
||||||
|
one: "Created %{count} month ago"
|
||||||
|
other: "Created %{count} months ago"
|
||||||
|
year:
|
||||||
|
one: "Created %{count} year ago"
|
||||||
|
other: "Created %{count} years ago"
|
||||||
|
|
||||||
bookmarked:
|
bookmarked:
|
||||||
title: "Bookmark"
|
title: "Bookmark"
|
||||||
|
|
|
@ -1034,6 +1034,24 @@ module Discourse
|
||||||
[SiteSetting.tos_topic_id, SiteSetting.guidelines_topic_id, SiteSetting.privacy_topic_id]
|
[SiteSetting.tos_topic_id, SiteSetting.guidelines_topic_id, SiteSetting.privacy_topic_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.site_creation_date
|
||||||
|
@creation_dates ||= {}
|
||||||
|
current_db = RailsMultisite::ConnectionManagement.current_db
|
||||||
|
@creation_dates[current_db] ||= begin
|
||||||
|
result = DB.query_single <<~SQL
|
||||||
|
SELECT created_at
|
||||||
|
FROM schema_migration_details
|
||||||
|
ORDER BY created_at
|
||||||
|
LIMIT 1
|
||||||
|
SQL
|
||||||
|
result.first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.clear_site_creation_date_cache
|
||||||
|
@creation_dates = {}
|
||||||
|
end
|
||||||
|
|
||||||
cattr_accessor :last_ar_cache_reset
|
cattr_accessor :last_ar_cache_reset
|
||||||
|
|
||||||
def self.reset_active_record_cache_if_needed(e)
|
def self.reset_active_record_cache_if_needed(e)
|
||||||
|
|
|
@ -201,6 +201,7 @@ module SvgSprite
|
||||||
reply
|
reply
|
||||||
robot
|
robot
|
||||||
rocket
|
rocket
|
||||||
|
scroll
|
||||||
search
|
search
|
||||||
search-plus
|
search-plus
|
||||||
search-minus
|
search-minus
|
||||||
|
|
|
@ -63,5 +63,113 @@ describe "About page", type: :system do
|
||||||
expect(about_page).to have_admins_count(1, "1")
|
expect(about_page).to have_admins_count(1, "1")
|
||||||
expect(about_page).to have_moderators_count(1, "1")
|
expect(about_page).to have_moderators_count(1, "1")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "displayed site age" do
|
||||||
|
it "says less than 1 month if the site is less than 1 month old" do
|
||||||
|
Discourse.stubs(:site_creation_date).returns(1.week.ago)
|
||||||
|
|
||||||
|
about_page.visit
|
||||||
|
|
||||||
|
expect(about_page).to have_site_created_less_than_1_month_ago
|
||||||
|
end
|
||||||
|
|
||||||
|
it "says how many months old the site is if the site is less than 1 year old" do
|
||||||
|
Discourse.stubs(:site_creation_date).returns(2.months.ago)
|
||||||
|
|
||||||
|
about_page.visit
|
||||||
|
|
||||||
|
expect(about_page).to have_site_created_in_months_ago(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "says how many years old the site is if the site is more than 1 year old" do
|
||||||
|
Discourse.stubs(:site_creation_date).returns(5.years.ago)
|
||||||
|
|
||||||
|
about_page.visit
|
||||||
|
|
||||||
|
expect(about_page).to have_site_created_in_years_ago(5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "the site activity section" do
|
||||||
|
describe "topics" do
|
||||||
|
before do
|
||||||
|
Fabricate(:topic, created_at: 2.days.ago)
|
||||||
|
Fabricate(:topic, created_at: 3.days.ago)
|
||||||
|
Fabricate(:topic, created_at: 8.days.ago)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the count of topics created in the last 7 days" do
|
||||||
|
about_page.visit
|
||||||
|
expect(about_page.site_activities.topics).to have_count(2, "2")
|
||||||
|
expect(about_page.site_activities.topics).to have_7_days_period
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "posts" do
|
||||||
|
before do
|
||||||
|
Fabricate(:post, created_at: 2.days.ago)
|
||||||
|
Fabricate(:post, created_at: 1.hour.ago)
|
||||||
|
Fabricate(:post, created_at: 3.hours.ago)
|
||||||
|
Fabricate(:post, created_at: 23.hours.ago)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the count of topics created in the last day" do
|
||||||
|
about_page.visit
|
||||||
|
expect(about_page.site_activities.posts).to have_count(3, "3")
|
||||||
|
expect(about_page.site_activities.posts).to have_1_day_period
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "active users" do
|
||||||
|
before do
|
||||||
|
User.update_all(last_seen_at: 1.month.ago)
|
||||||
|
|
||||||
|
Fabricate(:user, last_seen_at: 1.hour.ago)
|
||||||
|
Fabricate(:user, last_seen_at: 1.day.ago)
|
||||||
|
Fabricate(:user, last_seen_at: 3.days.ago)
|
||||||
|
Fabricate(:user, last_seen_at: 6.days.ago)
|
||||||
|
Fabricate(:user, last_seen_at: 8.days.ago)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the count of active users in the last 7 days" do
|
||||||
|
about_page.visit
|
||||||
|
expect(about_page.site_activities.active_users).to have_count(5, "5") # 4 fabricated above + 1 for the current user
|
||||||
|
expect(about_page.site_activities.active_users).to have_7_days_period
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "sign ups" do
|
||||||
|
before do
|
||||||
|
User.update_all(created_at: 1.month.ago)
|
||||||
|
|
||||||
|
Fabricate(:user, created_at: 3.hours.ago)
|
||||||
|
Fabricate(:user, created_at: 3.days.ago)
|
||||||
|
Fabricate(:user, created_at: 8.days.ago)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the count of signups in the last 7 days" do
|
||||||
|
about_page.visit
|
||||||
|
expect(about_page.site_activities.sign_ups).to have_count(2, "2")
|
||||||
|
expect(about_page.site_activities.sign_ups).to have_7_days_period
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "likes" do
|
||||||
|
before do
|
||||||
|
UserAction.destroy_all
|
||||||
|
|
||||||
|
Fabricate(:user_action, created_at: 1.hour.ago, action_type: UserAction::LIKE)
|
||||||
|
Fabricate(:user_action, created_at: 1.day.ago, action_type: UserAction::LIKE)
|
||||||
|
Fabricate(:user_action, created_at: 1.month.ago, action_type: UserAction::LIKE)
|
||||||
|
Fabricate(:user_action, created_at: 10.years.ago, action_type: UserAction::LIKE)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the count of likes of all time" do
|
||||||
|
about_page.visit
|
||||||
|
expect(about_page.site_activities.likes).to have_count(4, "4")
|
||||||
|
expect(about_page.site_activities.likes).to have_all_time_period
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module PageObjects
|
||||||
|
module Components
|
||||||
|
class AboutPageSiteActivity < PageObjects::Components::Base
|
||||||
|
attr_reader :container
|
||||||
|
|
||||||
|
def initialize(container)
|
||||||
|
@container = container
|
||||||
|
end
|
||||||
|
|
||||||
|
def topics
|
||||||
|
AboutPageSiteActivityItem.new(
|
||||||
|
container.find(".about__activities-item.topics"),
|
||||||
|
translation_key: "about.activities.topics",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def posts
|
||||||
|
AboutPageSiteActivityItem.new(
|
||||||
|
container.find(".about__activities-item.posts"),
|
||||||
|
translation_key: "about.activities.posts",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def active_users
|
||||||
|
AboutPageSiteActivityItem.new(
|
||||||
|
container.find(".about__activities-item.active-users"),
|
||||||
|
translation_key: "about.activities.active_users",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign_ups
|
||||||
|
AboutPageSiteActivityItem.new(
|
||||||
|
container.find(".about__activities-item.sign-ups"),
|
||||||
|
translation_key: "about.activities.sign_ups",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def likes
|
||||||
|
AboutPageSiteActivityItem.new(
|
||||||
|
container.find(".about__activities-item.likes"),
|
||||||
|
translation_key: "about.activities.likes",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module PageObjects
|
||||||
|
module Components
|
||||||
|
class AboutPageSiteActivityItem < PageObjects::Components::Base
|
||||||
|
attr_reader :container
|
||||||
|
|
||||||
|
def initialize(container, translation_key:)
|
||||||
|
@container = container
|
||||||
|
@translation_key = translation_key
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_count?(count, formatted_number)
|
||||||
|
container.find(".about__activities-item-count").has_text?(
|
||||||
|
I18n.t("js.#{@translation_key}", count: count, formatted_number:),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_1_day_period?
|
||||||
|
period_element.has_text?(I18n.t("js.about.activities.periods.today"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_7_days_period?
|
||||||
|
period_element.has_text?(I18n.t("js.about.activities.periods.last_7_days"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_all_time_period?
|
||||||
|
period_element.has_text?(I18n.t("js.about.activities.periods.all_time"))
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def period_element
|
||||||
|
container.find(".about__activities-item-period")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -33,6 +33,28 @@ module PageObjects
|
||||||
element = find(".about__stats-item.moderators span")
|
element = find(".about__stats-item.moderators span")
|
||||||
element.has_text?(I18n.t("js.about.moderator_count", count:, formatted_number:))
|
element.has_text?(I18n.t("js.about.moderator_count", count:, formatted_number:))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_site_created_less_than_1_month_ago?
|
||||||
|
site_age_stat_element.has_text?(I18n.t("js.about.site_age.less_than_one_month"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_site_created_in_months_ago?(months)
|
||||||
|
site_age_stat_element.has_text?(I18n.t("js.about.site_age.month", count: months))
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_site_created_in_years_ago?(years)
|
||||||
|
site_age_stat_element.has_text?(I18n.t("js.about.site_age.year", count: years))
|
||||||
|
end
|
||||||
|
|
||||||
|
def site_activities
|
||||||
|
PageObjects::Components::AboutPageSiteActivity.new(find(".about__activities"))
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def site_age_stat_element
|
||||||
|
find(".about__stats-item.site-creation-date span")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue
Block a user