1
0
Fork 0
mirror of https://framagit.org/framasoft/mobilizon.git synced 2024-12-21 15:32:32 +00:00

Merge branch 'bugs' into 'master'

Add dir="auto" to most user generated content

See merge request framasoft/mobilizon!1100
This commit is contained in:
Thomas Citharel 2021-11-07 17:40:13 +00:00
commit 7a30c92651
29 changed files with 154 additions and 77 deletions

View file

@ -1,27 +1,25 @@
<template>
<div>
<div class="media" style="align-items: top">
<div class="media-left">
<figure class="image is-32x32" v-if="actor.avatar">
<img class="is-rounded" :src="actor.avatar.url" alt="" />
</figure>
<b-icon v-else size="is-medium" icon="account-circle" />
</div>
<div class="media" style="align-items: top" dir="auto">
<div class="media-left">
<figure class="image is-32x32" v-if="actor.avatar">
<img class="is-rounded" :src="actor.avatar.url" alt="" />
</figure>
<b-icon v-else size="is-medium" icon="account-circle" />
</div>
<div class="media-content">
<p>
{{ actor.name || `@${usernameWithDomain(actor)}` }}
</p>
<p class="has-text-grey-dark" v-if="actor.name">
@{{ usernameWithDomain(actor) }}
</p>
<div
v-if="full"
class="summary"
:class="{ limit: limit }"
v-html="actor.summary"
/>
</div>
<div class="media-content">
<p>
{{ actor.name || `@${usernameWithDomain(actor)}` }}
</p>
<p class="has-text-grey-dark" v-if="actor.name">
@{{ usernameWithDomain(actor) }}
</p>
<div
v-if="full"
class="summary"
:class="{ limit: limit }"
v-html="actor.summary"
/>
</div>
</div>
</template>
@ -54,6 +52,14 @@ export default class ActorCard extends Vue {
<style lang="scss">
@use "@/styles/_mixins" as *;
.media {
.media-left {
margin-right: initial;
@include margin-right(1rem);
}
}
.tooltip {
display: block !important;
z-index: 10000;

View file

@ -1,5 +1,5 @@
<template>
<address>
<address dir="auto">
<b-icon
v-if="showIcon"
:icon="address.poiInfos.poiIcon.icon"

View file

@ -7,7 +7,7 @@
}"
class="comment-element"
>
<article class="media" :id="commentId">
<article class="media" :id="commentId" dir="auto">
<popover-actor-card
:actor="comment.actor"
:inline="true"
@ -32,11 +32,11 @@
</div>
<div class="media-content">
<div class="content">
<span class="first-line" v-if="!comment.deletedAt">
<span class="first-line" v-if="!comment.deletedAt" dir="auto">
<strong :class="{ organizer: commentFromOrganizer }">{{
comment.actor.name
}}</strong>
<small>{{ usernameWithDomain(comment.actor) }}</small>
<small>@{{ usernameWithDomain(comment.actor) }}</small>
</span>
<a v-else class="comment-link" :href="commentURL">
<span>{{ $t("[deleted]") }}</span>
@ -63,7 +63,7 @@
</button>
</span>
<br />
<div v-if="!comment.deletedAt" v-html="comment.text" />
<div v-if="!comment.deletedAt" v-html="comment.text" dir="auto" />
<div v-else>{{ $t("[This comment has been deleted]") }}</div>
<div class="load-replies" v-if="comment.totalReplies">
<p v-if="!showReplies" @click="fetchReplies">
@ -446,9 +446,12 @@ a.comment-link {
.media .media-content {
overflow-x: initial;
.content .editor-line {
display: flex;
align-items: center;
.content {
text-align: start;
.editor-line {
display: flex;
align-items: center;
}
}
.icons {

View file

@ -59,7 +59,7 @@
>
{{ $t("Loading comments…") }}
</p>
<transition-group name="comment-empty-list" mode="out-in" v-else>
<transition-group tag="div" name="comment-empty-list" v-else>
<transition-group
key="list"
name="comment-list"

View file

@ -10,7 +10,7 @@
<b-icon v-else size="is-large" icon="account-circle" />
</div>
<div class="body">
<div class="meta">
<div class="meta" dir="auto">
<span
class="first-line name"
v-if="comment.actor && !comment.deletedAt"
@ -64,7 +64,11 @@
>
</div>
</div>
<div v-if="!editMode && !comment.deletedAt" class="text-wrapper">
<div
v-if="!editMode && !comment.deletedAt"
class="text-wrapper"
dir="auto"
>
<div class="description-content" v-html="comment.text"></div>
<p
v-if="

View file

@ -1,6 +1,7 @@
<template>
<router-link
class="discussion-minimalist-card-wrapper"
dir="auto"
:to="{
name: RouteName.DISCUSSION,
params: { slug: discussion.slug, id: discussion.id },
@ -37,6 +38,7 @@
</div>
<div
class="ellipsis has-text-grey-dark"
dir="auto"
v-if="!discussion.lastComment.deletedAt"
>
{{ htmlTextEllipsis }}

View file

@ -211,6 +211,7 @@ import ListItem from "@tiptap/extension-list-item";
import Underline from "@tiptap/extension-underline";
import Link from "@tiptap/extension-link";
import CharacterCount from "@tiptap/extension-character-count";
import { AutoDir } from "./Editor/Autodir";
@Component({
components: { EditorContent, BubbleMenu },
@ -274,6 +275,7 @@ export default class EditorComponent extends Vue {
ListItem,
Mention.configure(MentionOptions),
CustomImage,
AutoDir,
Underline,
Link.configure({
HTMLAttributes: { target: "_blank", rel: "noopener noreferrer ugc" },

View file

@ -0,0 +1,30 @@
import { Extension } from "@tiptap/core";
/**
* Allows to set dir="auto" on top nodes
* Taken from https://github.com/ueberdosis/tiptap/issues/1621#issuecomment-918990408
*/
export const AutoDir = Extension.create({
name: "AutoDir",
addGlobalAttributes() {
return [
{
types: [
"heading",
"paragraph",
"bulletList",
"orderedList",
"blockquote",
],
attributes: {
autoDir: {
renderHTML: () => ({
dir: "auto",
}),
parseHTML: (element) => element.dir || "auto",
},
},
},
];
},
});

View file

@ -27,6 +27,7 @@ const debouncedFetchItems = pDebounce(fetchItems, 200);
const mentionOptions: Partial<any> = {
HTMLAttributes: {
class: "mention",
dir: "ltr",
},
suggestion: {
items: async (query: string): Promise<IPerson[]> => {

View file

@ -12,6 +12,7 @@
expanded
@select="updateSelected"
v-bind="$attrs"
dir="auto"
>
<template #default="{ option }">
<b-icon :icon="option.poiInfos.poiIcon.icon" />

View file

@ -24,7 +24,7 @@
v-for="tag in (event.tags || []).slice(0, 3)"
:key="tag.slug"
>
<b-tag type="is-light">{{ tag.title }}</b-tag>
<b-tag type="is-light" dir="auto">{{ tag.title }}</b-tag>
</router-link>
</div>
</figure>
@ -39,9 +39,11 @@
/>
</div>
<div class="media-content">
<h3 class="event-title" :title="event.title">{{ event.title }}</h3>
<h3 class="event-title" :title="event.title" dir="auto">
{{ event.title }}
</h3>
<div class="content-end">
<div class="event-organizer">
<div class="event-organizer" dir="auto">
<figure
class="image is-24x24"
v-if="organizer(event) && organizer(event).avatar"
@ -58,12 +60,14 @@
</span>
</div>
<inline-address
dir="auto"
v-if="event.physicalAddress"
class="event-subtitle"
:physical-address="event.physicalAddress"
/>
<div
class="event-subtitle"
dir="auto"
v-else-if="event.options && event.options.isOnline"
>
<b-icon icon="video" />

View file

@ -44,6 +44,7 @@ div.eventMetadataBlock {
.content-wrapper {
overflow: hidden;
width: 100%;
&.padding-left {
padding: 0 20px;

View file

@ -1,6 +1,6 @@
<template>
<article class="box mb-5 mt-4">
<div class="identity-header">
<div class="identity-header" dir="auto">
<figure class="image is-24x24" v-if="participation.actor.avatar">
<img
class="is-rounded"
@ -43,7 +43,7 @@
</router-link>
</div>
</div>
<div class="list-card-content">
<div class="list-card-content" dir="auto">
<div class="title-wrapper">
<router-link
:to="{

View file

@ -23,13 +23,15 @@
<b-icon v-else size="is-large" icon="account-group" />
</div>
<div class="media-content">
<h3 class="is-size-5 group-title">{{ displayName(group) }}</h3>
<h3 class="is-size-5 group-title" dir="auto">
{{ displayName(group) }}
</h3>
<span class="is-6 has-text-grey-dark group-federated-username">
{{ `@${usernameWithDomain(group)}` }}
</span>
</div>
</div>
<div class="content mb-2" v-html="group.summary" />
<div class="content mb-2" dir="auto" v-html="group.summary" />
<div class="card-custom-footer">
<inline-address
class="has-text-grey-dark"

View file

@ -1,13 +1,13 @@
<template>
<div class="card">
<div class="identity-header">
<div class="identity-header" dir="auto">
<figure class="image is-24x24" v-if="member.actor.avatar">
<img class="is-rounded" :src="member.actor.avatar.url" alt="" />
</figure>
<b-icon v-else icon="account-circle" />
{{ displayNameAndUsername(member.actor) }}
</div>
<div class="card-content">
<div class="card-content" dir="auto">
<div>
<div class="media">
<div class="media-left">
@ -16,7 +16,7 @@
</figure>
<b-icon v-else size="is-large" icon="account-group" />
</div>
<div class="media-content">
<div class="media-content" dir="auto">
<router-link
:to="{
name: RouteName.GROUP,
@ -27,10 +27,7 @@
>
<h2>{{ member.parent.name }}</h2>
<p class="is-6 has-text-grey-dark">
<span v-if="member.parent.domain">{{
`@${member.parent.preferredUsername}@${member.parent.domain}`
}}</span>
<span v-else>{{ `@${member.parent.preferredUsername}` }}</span>
<span>{{ `@${usernameWithDomain(member.parent)}` }}</span>
<b-taglist>
<b-tag
type="is-info"

View file

@ -18,6 +18,7 @@
class="absolute top-0 left-0 transition-opacity duration-500"
:class="{ isLoaded: isLoaded ? 'opacity-100' : 'opacity-0', rounded }"
alt=""
src=""
/>
</div>
</div>

View file

@ -1,5 +1,5 @@
<template>
<div class="resource-wrapper">
<div class="resource-wrapper" dir="auto">
<router-link
:to="{
name: RouteName.RESOURCE_FOLDER,

View file

@ -1,5 +1,5 @@
<template>
<div class="resource-wrapper">
<div class="resource-wrapper" dir="auto">
<a :href="resource.resourceUrl" target="_blank">
<div class="preview">
<div

View file

@ -1232,5 +1232,6 @@
"This profile is from another instance, the informations shown here may be incomplete.": "This profile is from another instance, the informations shown here may be incomplete.",
"View full profile": "View full profile",
"Any type": "Any type",
"In person": "In person"
"In person": "In person",
"In the past": "In the past"
}

View file

@ -1336,5 +1336,6 @@
"This profile is from another instance, the informations shown here may be incomplete.": "Ce profil provient d'une autre instance, les informations montrées ici peuvent être incomplètes.",
"View full profile": "Voir le profil complet",
"Any type": "N'importe quel type",
"In person": "En personne"
"In person": "En personne",
"In the past": "Dans le passé"
}

View file

@ -47,6 +47,7 @@
v-model="identity.name"
@input="autoUpdateUsername($event)"
id="identity-display-name"
dir="auto"
/>
</b-field>
@ -64,6 +65,7 @@
required
v-model="identity.preferredUsername"
:disabled="isUpdate"
dir="auto"
:use-html5-validation="!isUpdate"
pattern="[a-z0-9_]+"
id="identity-username"
@ -82,6 +84,7 @@
>
<b-input
type="textarea"
dir="auto"
aria-required="false"
v-model="identity.summary"
id="identity-summary"
@ -190,7 +193,8 @@
</div>
</template>
<style scoped type="scss">
<style scoped lang="scss">
@use "@/styles/_mixins" as *;
h1 {
display: flex;
justify-content: center;

View file

@ -45,7 +45,7 @@
{{ error }}
</b-message>
<section>
<div class="discussion-title">
<div class="discussion-title" dir="auto">
<h1 class="title" v-if="discussion.title && !editTitleMode">
{{ discussion.title }}
</h1>

View file

@ -65,7 +65,7 @@
{{ $t("There's no discussions yet") }}
</empty-content>
</section>
<section class="section" v-else>
<section class="section" v-else-if="!$apollo.loading">
<empty-content icon="chat">
{{ $t("Only group members can access discussions") }}
<template #desc>

View file

@ -10,9 +10,11 @@
<section class="intro">
<div class="columns">
<div class="column">
<h1 class="title" style="margin: 0">{{ event.title }}</h1>
<h1 class="title" style="margin: 0" dir="auto">
{{ event.title }}
</h1>
<div class="organizer">
<span v-if="event.organizerActor && !event.attributedTo">
<div v-if="event.organizerActor && !event.attributedTo">
<popover-actor-card
:actor="event.organizerActor"
:inline="true"
@ -25,7 +27,7 @@
}}
</span>
</popover-actor-card>
</span>
</div>
<span
v-else-if="
event.attributedTo &&
@ -70,7 +72,11 @@
</i18n>
</span>
</div>
<p class="tags" v-if="event.tags && event.tags.length > 0">
<p
class="tags"
v-if="event.tags && event.tags.length > 0"
dir="auto"
>
<router-link
v-for="tag in event.tags"
:key="tag.title"
@ -316,6 +322,7 @@
</p>
<div v-else>
<div
dir="auto"
class="description-content"
ref="eventDescriptionElement"
v-html="event.description"

View file

@ -5,7 +5,7 @@
<lazy-image-wrapper :picture="post.picture" />
</div>
<div class="heading-section">
<div class="heading-wrapper">
<div class="heading-wrapper" dir="auto">
<div class="title-metadata">
<div class="title-wrapper">
<b-tag
@ -165,8 +165,8 @@
}}
</b-message>
<section v-html="post.body" class="content" />
<section class="tags">
<section v-html="post.body" dir="auto" class="content" />
<section class="tags" dir="auto">
<router-link
v-for="tag in post.tags"
:key="tag.title"

View file

@ -213,7 +213,7 @@ import debounce from "lodash/debounce";
interface ISearchTimeOption {
label: string;
start?: Date;
start?: Date | null;
end?: Date | null;
}
@ -292,6 +292,11 @@ export default class Search extends Vue {
location: IAddress = new Address();
dateOptions: Record<string, ISearchTimeOption> = {
past: {
label: this.$t("In the past") as string,
start: null,
end: new Date(),
},
today: {
label: this.$t("Today") as string,
start: new Date(),
@ -346,7 +351,7 @@ export default class Search extends Vue {
data(): Record<string, unknown> {
return {
debouncedUpdateSearchQuery: debounce(this.updateSearchQuery, 200),
debouncedUpdateSearchQuery: debounce(this.updateSearchQuery, 500),
};
}
@ -525,7 +530,7 @@ export default class Search extends Vue {
}
};
get start(): Date | undefined {
get start(): Date | undefined | null {
if (this.dateOptions[this.when]) {
return this.dateOptions[this.when].start;
}

View file

@ -24,7 +24,7 @@ exports[`CommentTree renders a comment tree with comments 1`] = `
</div>
</article>
</form>
<transition-group-stub name="comment-empty-list" mode="out-in">
<transition-group-stub tag="div" name="comment-empty-list">
<transition-group-stub tag="ul" name="comment-list" class="comment-list">
<comment-stub comment="[object Object]" event="[object Object]" class="root-comment"></comment-stub>
<comment-stub comment="[object Object]" event="[object Object]" class="root-comment"></comment-stub>
@ -66,7 +66,7 @@ exports[`CommentTree renders an empty comment tree 1`] = `
</div>
</article>
</form>
<transition-group-stub name="comment-empty-list" mode="out-in">
<transition-group-stub tag="div" name="comment-empty-list">
<div class="no-comments"><span>No comments yet</span></div>
</transition-group-stub>
</div>

View file

@ -1258,23 +1258,27 @@ defmodule Mobilizon.Events do
defp events_for_begins_on(query, args) do
begins_on = Map.get(args, :begins_on, DateTime.utc_now())
query
|> where([q], q.begins_on >= ^begins_on)
if is_nil(begins_on) do
query
else
where(query, [q], q.begins_on >= ^begins_on)
end
end
@spec events_for_ends_on(Ecto.Queryable.t(), map()) :: Ecto.Query.t()
defp events_for_ends_on(query, args) do
ends_on = Map.get(args, :ends_on)
if is_nil(ends_on),
do: query,
else:
where(
query,
[q],
(is_nil(q.ends_on) and q.begins_on <= ^ends_on) or
q.ends_on <= ^ends_on
)
if is_nil(ends_on) do
query
else
where(
query,
[q],
(is_nil(q.ends_on) and q.begins_on <= ^ends_on) or
q.ends_on <= ^ends_on
)
end
end
@spec events_for_tags(Ecto.Queryable.t(), map()) :: Ecto.Query.t()

View file

@ -71,6 +71,7 @@ defmodule Mobilizon.Service.Formatter.DefaultScrubbler do
])
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "mention"])
Meta.allow_tag_with_this_attribute_values(:span, "dir", ["ltr", "rtl", "auto"])
Meta.allow_tag_with_these_attributes(:span, ["data-user"])
Meta.allow_tag_with_these_attributes(:h1, [])