Merge branch 'resource-pagination' into 'master'
Add pagination to resources Closes #727 See merge request framasoft/mobilizon!939
This commit is contained in:
commit
6dc048f292
|
@ -162,7 +162,7 @@ a {
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
padding: 10px 8px 8px;
|
padding: 8px;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
|
|
@ -20,21 +20,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
<div class="title-wrapper">
|
||||||
<img
|
<img
|
||||||
class="favicon"
|
class="favicon"
|
||||||
v-if="resource.metadata && resource.metadata.faviconUrl"
|
v-if="resource.metadata && resource.metadata.faviconUrl"
|
||||||
:src="resource.metadata.faviconUrl"
|
:src="resource.metadata.faviconUrl"
|
||||||
/>
|
/>
|
||||||
<h3>{{ resource.title }}</h3>
|
<h3>{{ resource.title }}</h3>
|
||||||
<span class="host" v-if="inline">{{
|
</div>
|
||||||
resource.updatedAt | formatDateTimeString
|
<div class="metadata-wrapper">
|
||||||
}}</span>
|
<span class="host" v-if="!inline || preview">{{ urlHostname }}</span>
|
||||||
<span class="host" v-else>{{ urlHostname }}</span>
|
<span
|
||||||
|
class="published-at is-hidden-mobile"
|
||||||
|
v-if="resource.updatedAt || resource.publishedAt"
|
||||||
|
>{{
|
||||||
|
(resource.updatedAt || resource.publishedAt)
|
||||||
|
| formatDateTimeString
|
||||||
|
}}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<resource-dropdown
|
<resource-dropdown
|
||||||
class="actions"
|
class="actions"
|
||||||
v-if="!inline"
|
v-if="!inline || !preview"
|
||||||
@delete="$emit('delete', resource.id)"
|
@delete="$emit('delete', resource.id)"
|
||||||
@move="$emit('move', resource)"
|
@move="$emit('move', resource)"
|
||||||
@rename="$emit('rename', resource)"
|
@rename="$emit('rename', resource)"
|
||||||
|
@ -53,6 +62,7 @@ export default class ResourceItem extends Vue {
|
||||||
@Prop({ required: true, type: Object }) resource!: IResource;
|
@Prop({ required: true, type: Object }) resource!: IResource;
|
||||||
|
|
||||||
@Prop({ required: false, default: false }) inline!: boolean;
|
@Prop({ required: false, default: false }) inline!: boolean;
|
||||||
|
@Prop({ required: false, default: false }) preview!: boolean;
|
||||||
|
|
||||||
list = [];
|
list = [];
|
||||||
|
|
||||||
|
@ -74,11 +84,12 @@ export default class ResourceItem extends Vue {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
display: block;
|
display: block;
|
||||||
margin: auto 1rem auto 2rem;
|
margin: auto 1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,10 +122,15 @@ a {
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
padding: 10px 8px 8px;
|
padding: 8px;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
max-width: calc(100vw - 122px);
|
||||||
|
}
|
||||||
|
|
||||||
img.favicon {
|
img.favicon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
|
@ -134,14 +150,30 @@ a {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.host {
|
.metadata-wrapper {
|
||||||
display: block;
|
max-width: calc(100vw - 122px);
|
||||||
margin-top: 5px;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
span {
|
||||||
|
&:last-child::before {
|
||||||
|
content: "⋅";
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
&:first-child::before {
|
||||||
|
content: "";
|
||||||
|
padding: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.host,
|
||||||
|
&.published-at {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -58,18 +58,34 @@
|
||||||
>
|
>
|
||||||
{{ $t("No resources in this folder") }}
|
{{ $t("No resources in this folder") }}
|
||||||
</p>
|
</p>
|
||||||
|
<b-pagination
|
||||||
|
v-if="resource.children.total > RESOURCES_PER_PAGE"
|
||||||
|
:total="resource.children.total"
|
||||||
|
v-model="page"
|
||||||
|
size="is-small"
|
||||||
|
:per-page="RESOURCES_PER_PAGE"
|
||||||
|
:aria-next-label="$t('Next page')"
|
||||||
|
:aria-previous-label="$t('Previous page')"
|
||||||
|
:aria-page-label="$t('Page')"
|
||||||
|
:aria-current-label="$t('Current page')"
|
||||||
|
/>
|
||||||
</article>
|
</article>
|
||||||
|
<div class="buttons">
|
||||||
|
<b-button type="is-text" @click="$emit('close-move-modal')">{{
|
||||||
|
$t("Cancel")
|
||||||
|
}}</b-button>
|
||||||
<b-button
|
<b-button
|
||||||
type="is-primary"
|
type="is-primary"
|
||||||
@click="updateResource"
|
@click="updateResource"
|
||||||
:disabled="moveDisabled"
|
:disabled="moveDisabled"
|
||||||
>{{
|
><template v-if="resource.path === '/'">
|
||||||
$t("Move resource to {folder}", { folder: resource.title })
|
{{ $t("Move resource to the root folder") }}
|
||||||
}}</b-button
|
</template>
|
||||||
|
<template v-else
|
||||||
|
>{{ $t("Move resource to {folder}", { folder: resource.title }) }}
|
||||||
|
</template></b-button
|
||||||
>
|
>
|
||||||
<b-button type="is-text" @click="$emit('close-move-modal')">{{
|
</div>
|
||||||
$t("Cancel")
|
|
||||||
}}</b-button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -86,6 +102,8 @@ import { IResource } from "../../types/resource";
|
||||||
return {
|
return {
|
||||||
path: this.resource.path,
|
path: this.resource.path,
|
||||||
username: this.username,
|
username: this.username,
|
||||||
|
page: this.page,
|
||||||
|
limit: this.RESOURCES_PER_PAGE,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { path: "/", username: this.username };
|
return { path: "/", username: this.username };
|
||||||
|
@ -103,6 +121,10 @@ export default class ResourceSelector extends Vue {
|
||||||
|
|
||||||
resource: IResource | undefined = this.initialResource.parent;
|
resource: IResource | undefined = this.initialResource.parent;
|
||||||
|
|
||||||
|
RESOURCES_PER_PAGE = 10;
|
||||||
|
|
||||||
|
page = 1;
|
||||||
|
|
||||||
goDown(element: IResource): void {
|
goDown(element: IResource): void {
|
||||||
if (element.type === "folder" && element.id !== this.initialResource.id) {
|
if (element.type === "folder" && element.id !== this.initialResource.id) {
|
||||||
this.resource = element;
|
this.resource = element;
|
||||||
|
@ -150,4 +172,11 @@ export default class ResourceSelector extends Vue {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.buttons {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.pagination {
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -11,7 +11,12 @@ export const RESOURCE_METADATA_BASIC_FIELDS_FRAGMENT = gql`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const GET_RESOURCE = gql`
|
export const GET_RESOURCE = gql`
|
||||||
query GetResource($path: String!, $username: String!) {
|
query GetResource(
|
||||||
|
$path: String!
|
||||||
|
$username: String!
|
||||||
|
$page: Int
|
||||||
|
$limit: Int
|
||||||
|
) {
|
||||||
resource(path: $path, username: $username) {
|
resource(path: $path, username: $username) {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
|
@ -38,7 +43,7 @@ export const GET_RESOURCE = gql`
|
||||||
name
|
name
|
||||||
domain
|
domain
|
||||||
}
|
}
|
||||||
children {
|
children(page: $page, limit: $limit) {
|
||||||
total
|
total
|
||||||
elements {
|
elements {
|
||||||
id
|
id
|
||||||
|
@ -53,6 +58,9 @@ export const GET_RESOURCE = gql`
|
||||||
path
|
path
|
||||||
type
|
type
|
||||||
}
|
}
|
||||||
|
publishedAt
|
||||||
|
updatedAt
|
||||||
|
insertedAt
|
||||||
metadata {
|
metadata {
|
||||||
...ResourceMetadataBasicFields
|
...ResourceMetadataBasicFields
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,7 +275,7 @@ export default class Participants extends Vue {
|
||||||
}
|
}
|
||||||
|
|
||||||
set page(page: number) {
|
set page(page: number) {
|
||||||
this.pushRouter(RouteName.RELAY_FOLLOWINGS, {
|
this.pushRouter(RouteName.PARTICIPATIONS, {
|
||||||
page: page.toString(),
|
page: page.toString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,6 +145,17 @@
|
||||||
<p>{{ $t("No resources in this folder") }}</p>
|
<p>{{ $t("No resources in this folder") }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<b-pagination
|
||||||
|
v-if="resource.children.total > RESOURCES_PER_PAGE"
|
||||||
|
:total="resource.children.total"
|
||||||
|
v-model="page"
|
||||||
|
:per-page="RESOURCES_PER_PAGE"
|
||||||
|
:aria-next-label="$t('Next page')"
|
||||||
|
:aria-previous-label="$t('Previous page')"
|
||||||
|
:aria-page-label="$t('Page')"
|
||||||
|
:aria-current-label="$t('Current page')"
|
||||||
|
>
|
||||||
|
</b-pagination>
|
||||||
<b-modal :active.sync="renameModal" has-modal-card>
|
<b-modal :active.sync="renameModal" has-modal-card>
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<section class="modal-card-body">
|
<section class="modal-card-body">
|
||||||
|
@ -187,7 +198,11 @@
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
<b-modal :active.sync="createLinkResourceModal" has-modal-card>
|
<b-modal
|
||||||
|
:active.sync="createLinkResourceModal"
|
||||||
|
has-modal-card
|
||||||
|
class="link-resource-modal"
|
||||||
|
>
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<section class="modal-card-body">
|
<section class="modal-card-body">
|
||||||
<b-message type="is-danger" v-if="modalError">
|
<b-message type="is-danger" v-if="modalError">
|
||||||
|
@ -204,7 +219,7 @@
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<div class="new-resource-preview" v-if="newResource.title">
|
<div class="new-resource-preview" v-if="newResource.title">
|
||||||
<resource-item :resource="newResource" />
|
<resource-item :resource="newResource" :preview="true" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<b-field :label="$t('Title')">
|
<b-field :label="$t('Title')">
|
||||||
|
@ -250,6 +265,8 @@ import { IConfig } from "../../types/config.model";
|
||||||
import ResourceMixin from "../../mixins/resource";
|
import ResourceMixin from "../../mixins/resource";
|
||||||
import ResourceSelector from "../../components/Resource/ResourceSelector.vue";
|
import ResourceSelector from "../../components/Resource/ResourceSelector.vue";
|
||||||
import { ApolloCache, FetchResult } from "@apollo/client/core";
|
import { ApolloCache, FetchResult } from "@apollo/client/core";
|
||||||
|
import VueRouter from "vue-router";
|
||||||
|
const { isNavigationFailure, NavigationFailureType } = VueRouter;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { FolderItem, ResourceItem, Draggable, ResourceSelector },
|
components: { FolderItem, ResourceItem, Draggable, ResourceSelector },
|
||||||
|
@ -265,6 +282,8 @@ import { ApolloCache, FetchResult } from "@apollo/client/core";
|
||||||
return {
|
return {
|
||||||
path,
|
path,
|
||||||
username: this.$route.params.preferredUsername,
|
username: this.$route.params.preferredUsername,
|
||||||
|
page: this.page,
|
||||||
|
limit: this.RESOURCES_PER_PAGE,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
error({ graphQLErrors }) {
|
error({ graphQLErrors }) {
|
||||||
|
@ -303,6 +322,8 @@ export default class Resources extends Mixins(ResourceMixin) {
|
||||||
|
|
||||||
usernameWithDomain = usernameWithDomain;
|
usernameWithDomain = usernameWithDomain;
|
||||||
|
|
||||||
|
RESOURCES_PER_PAGE = 10;
|
||||||
|
|
||||||
newResource: IResource = {
|
newResource: IResource = {
|
||||||
title: "",
|
title: "",
|
||||||
summary: "",
|
summary: "",
|
||||||
|
@ -344,6 +365,16 @@ export default class Resources extends Mixins(ResourceMixin) {
|
||||||
|
|
||||||
mapServiceTypeToIcon = mapServiceTypeToIcon;
|
mapServiceTypeToIcon = mapServiceTypeToIcon;
|
||||||
|
|
||||||
|
get page(): number {
|
||||||
|
return parseInt((this.$route.query.page as string) || "1", 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
set page(page: number) {
|
||||||
|
this.pushRouter({
|
||||||
|
page: page.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get actualPath(): string {
|
get actualPath(): string {
|
||||||
const path = Array.isArray(this.$route.params.path)
|
const path = Array.isArray(this.$route.params.path)
|
||||||
? this.$route.params.path.join("/")
|
? this.$route.params.path.join("/")
|
||||||
|
@ -641,16 +672,51 @@ export default class Resources extends Mixins(ResourceMixin) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Watch("page")
|
||||||
|
loadMoreResources(): void {
|
||||||
|
this.$apollo.queries.resource.fetchMore({
|
||||||
|
// New variables
|
||||||
|
variables: {
|
||||||
|
page: this.page,
|
||||||
|
limit: this.RESOURCES_PER_PAGE,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
handleErrors(errors: any[]): void {
|
handleErrors(errors: any[]): void {
|
||||||
if (errors.some((error) => error.status_code === 404)) {
|
if (errors.some((error) => error.status_code === 404)) {
|
||||||
this.$router.replace({ name: RouteName.PAGE_NOT_FOUND });
|
this.$router.replace({ name: RouteName.PAGE_NOT_FOUND });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async pushRouter(args: Record<string, string>): Promise<void> {
|
||||||
|
try {
|
||||||
|
const path = this.filteredPath.toString();
|
||||||
|
const routeName =
|
||||||
|
path === ""
|
||||||
|
? RouteName.RESOURCE_FOLDER_ROOT
|
||||||
|
: RouteName.RESOURCE_FOLDER;
|
||||||
|
|
||||||
|
await this.$router.push({
|
||||||
|
name: routeName,
|
||||||
|
params: { path },
|
||||||
|
query: { ...this.$route.query, ...args },
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (isNavigationFailure(e, NavigationFailureType.redirected)) {
|
||||||
|
throw Error(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.container.section {
|
.container.section {
|
||||||
background: $white;
|
background: $white;
|
||||||
|
|
||||||
|
& > nav.pagination {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.breadcrumb ul {
|
nav.breadcrumb ul {
|
||||||
|
@ -675,6 +741,10 @@ nav.breadcrumb ul {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
::v-deep .b-checkbox.checkbox {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
|
||||||
|
@ -693,11 +763,16 @@ nav.breadcrumb ul {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: #444b5d;
|
color: #444b5d;
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
|
||||||
.resource-checkbox {
|
.resource-checkbox {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
padding: 0 3px 0 10px;
|
padding-left: 10px;
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
|
|
||||||
|
::v-deep .b-checkbox.checkbox {
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .resource-checkbox,
|
&:hover .resource-checkbox,
|
||||||
|
|
|
@ -50,7 +50,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
|
||||||
|
|
||||||
def find_resources_for_parent(
|
def find_resources_for_parent(
|
||||||
%Resource{actor_id: group_id} = parent,
|
%Resource{actor_id: group_id} = parent,
|
||||||
_args,
|
%{page: page, limit: limit},
|
||||||
%{
|
%{
|
||||||
context: %{
|
context: %{
|
||||||
current_user: %User{} = user
|
current_user: %User{} = user
|
||||||
|
@ -59,7 +59,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
|
||||||
) do
|
) do
|
||||||
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
|
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
|
||||||
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
||||||
%Page{} = page <- Resources.get_resources_for_folder(parent) do
|
%Page{} = page <- Resources.get_resources_for_folder(parent, page, limit) do
|
||||||
{:ok, page}
|
{:ok, page}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,6 +20,7 @@ defmodule Mobilizon.GraphQL.Schema.ResourceType do
|
||||||
field(:actor, :actor, description: "The resource's owner")
|
field(:actor, :actor, description: "The resource's owner")
|
||||||
field(:inserted_at, :naive_datetime, description: "The resource's creation date")
|
field(:inserted_at, :naive_datetime, description: "The resource's creation date")
|
||||||
field(:updated_at, :naive_datetime, description: "The resource's last update date")
|
field(:updated_at, :naive_datetime, description: "The resource's last update date")
|
||||||
|
field(:published_at, :naive_datetime, description: "The resource's publication date")
|
||||||
field(:type, :string, description: "The resource's type (if it's a folder)")
|
field(:type, :string, description: "The resource's type (if it's a folder)")
|
||||||
field(:path, :string, description: "The resource's path")
|
field(:path, :string, description: "The resource's path")
|
||||||
|
|
||||||
|
@ -27,6 +28,14 @@ defmodule Mobilizon.GraphQL.Schema.ResourceType do
|
||||||
|
|
||||||
field :children, :paginated_resource_list do
|
field :children, :paginated_resource_list do
|
||||||
description("Children resources in folder")
|
description("Children resources in folder")
|
||||||
|
|
||||||
|
arg(:page, :integer,
|
||||||
|
default_value: 1,
|
||||||
|
description: "The page in the paginated resource list"
|
||||||
|
)
|
||||||
|
|
||||||
|
arg(:limit, :integer, default_value: 10, description: "The limit of resources per page")
|
||||||
|
|
||||||
resolve(&Resource.find_resources_for_parent/3)
|
resolve(&Resource.find_resources_for_parent/3)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -47,7 +47,7 @@ defmodule Mobilizon.Resources do
|
||||||
) do
|
) do
|
||||||
Resource
|
Resource
|
||||||
|> where([r], r.actor_id == ^group_id and is_nil(r.parent_id))
|
|> where([r], r.actor_id == ^group_id and is_nil(r.parent_id))
|
||||||
|> order_by(asc: :type)
|
|> order_by(asc: :type, asc: :title)
|
||||||
|> preload([r], [:actor, :creator])
|
|> preload([r], [:actor, :creator])
|
||||||
|> Page.build_page(page, limit)
|
|> Page.build_page(page, limit)
|
||||||
end
|
end
|
||||||
|
@ -55,7 +55,7 @@ defmodule Mobilizon.Resources do
|
||||||
def get_resources_for_folder(%Resource{id: resource_id}, page, limit) do
|
def get_resources_for_folder(%Resource{id: resource_id}, page, limit) do
|
||||||
Resource
|
Resource
|
||||||
|> where([r], r.parent_id == ^resource_id)
|
|> where([r], r.parent_id == ^resource_id)
|
||||||
|> order_by(asc: :type)
|
|> order_by(asc: :type, asc: :title)
|
||||||
|> preload([r], [:actor, :creator])
|
|> preload([r], [:actor, :creator])
|
||||||
|> Page.build_page(page, limit)
|
|> Page.build_page(page, limit)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue