Daniel Supernault před 4 roky
rodič
revize
e9ef0c887a

+ 74 - 1
app/Http/Controllers/Api/BaseApiController.php

@@ -15,7 +15,8 @@ use App\{
     Media,
     Notification,
     Profile,
-    Status
+    Status,
+    StatusArchived
 };
 use App\Transformer\Api\{
     AccountTransformer,
@@ -39,6 +40,7 @@ use App\Jobs\VideoPipeline\{
 use App\Services\NotificationService;
 use App\Services\MediaPathService;
 use App\Services\MediaBlocklistService;
+use App\Services\StatusService;
 
 class BaseApiController extends Controller
 {
@@ -286,4 +288,75 @@ class BaseApiController extends Controller
 
         return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
     }
+
+    public function archive(Request $request, $id)
+    {
+        abort_if(!$request->user(), 403);
+
+        $status = Status::whereNull('in_reply_to_id')
+            ->whereNull('reblog_of_id')
+            ->whereProfileId($request->user()->profile_id)
+            ->findOrFail($id);
+
+        if($status->scope === 'archived') {
+            return [200];
+        }
+
+        $archive = new StatusArchived;
+        $archive->status_id = $status->id;
+        $archive->profile_id = $status->profile_id;
+        $archive->original_scope = $status->scope;
+        $archive->save();
+
+        $status->scope = 'archived';
+        $status->visibility = 'draft';
+        $status->save();
+
+        StatusService::del($status->id);
+
+        // invalidate caches
+
+        return [200];
+    }
+
+    public function unarchive(Request $request, $id)
+    {
+        abort_if(!$request->user(), 403);
+
+        $status = Status::whereNull('in_reply_to_id')
+            ->whereNull('reblog_of_id')
+            ->whereProfileId($request->user()->profile_id)
+            ->findOrFail($id);
+
+        if($status->scope !== 'archived') {
+            return [200];
+        }
+
+        $archive = StatusArchived::whereStatusId($status->id)
+            ->whereProfileId($status->profile_id)
+            ->firstOrFail();
+
+        $status->scope = $archive->original_scope;
+        $status->visibility = $archive->original_scope;
+        $status->save();
+
+        $archive->delete();
+
+        return [200];
+    }
+
+    public function archivedPosts(Request $request)
+    {
+        abort_if(!$request->user(), 403);
+
+        $statuses = Status::whereProfileId($request->user()->profile_id)
+            ->whereScope('archived')
+            ->orderByDesc('id')
+            ->simplePaginate(10);
+
+        $fractal = new Fractal\Manager();
+        $fractal->setSerializer(new ArraySerializer());
+        $resource = new Fractal\Resource\Collection($statuses, new StatusStatelessTransformer());
+        return $fractal->createData($resource)->toArray();
+    }
 }

+ 25 - 0
resources/assets/js/components/PostComponent.vue

@@ -616,6 +616,8 @@
 			<div v-if="status && user.id != status.account.id && !relationship.blocking && !user.is_admin" class="list-group-item rounded cursor-pointer text-danger" @click="blockProfile()">Block</div>
 			<div v-if="status && user.id != status.account.id && relationship.blocking && !user.is_admin" class="list-group-item rounded cursor-pointer text-danger" @click="unblockProfile()">Unblock</div>
 			<a v-if="user && user.id != status.account.id && !user.is_admin" class="list-group-item rounded cursor-pointer text-danger text-decoration-none" :href="reportUrl()">Report</a>
+			<div v-if="status && user.id == status.account.id && status.visibility != 'archived'" class="list-group-item rounded cursor-pointer text-danger" @click="archivePost(status)">Archive</div>
+			<div v-if="status && user.id == status.account.id && status.visibility == 'archived'" class="list-group-item rounded cursor-pointer text-danger" @click="unarchivePost(status)">Unarchive</div>
 			<div v-if="status && (user.is_admin || user.id == status.account.id)" class="list-group-item rounded cursor-pointer text-danger" @click="deletePost(ctxMenuStatus)">Delete</div>
 			<div class="list-group-item rounded cursor-pointer text-lighter" @click="closeCtxMenu()">Cancel</div>
 		</div>
@@ -1757,6 +1759,29 @@ export default {
 				});
 			},
 
+			archivePost(status) {
+				if(window.confirm('Are you sure you want to archive this post?') == false) {
+					return;
+				}
+
+				axios.post('/api/pixelfed/v2/status/' + status.id + '/archive')
+				.then(res => {
+					this.$refs.ctxModal.hide();
+					window.location.href = '/';
+				});
+			},
+
+			unarchivePost(status) {
+				if(window.confirm('Are you sure you want to unarchive this post?') == false) {
+					return;
+				}
+
+				axios.post('/api/pixelfed/v2/status/' + status.id + '/unarchive')
+				.then(res => {
+					this.$refs.ctxModal.hide();
+				});
+			}
+
 		},
 }
 </script>

+ 141 - 59
resources/assets/js/components/Profile.vue

@@ -181,64 +181,68 @@
 					<li v-if="owner" class="nav-item border-top">
 						<a :class="this.mode == 'bookmarks' ? 'nav-link text-dark' : 'nav-link'" href="#" v-on:click.prevent="switchMode('bookmarks')"><i class="fas fa-bookmark"></i> <span class="d-none d-md-inline-block small pl-1">SAVED</span></a>
 					</li>
+					<li v-if="owner" class="nav-item border-top">
+						<a :class="this.mode == 'archives' ? 'nav-link text-dark' : 'nav-link'" href="#" v-on:click.prevent="switchMode('archives')"><i class="far fa-folder-open"></i> <span class="d-none d-md-inline-block small pl-1">ARCHIVES</span></a>
+					</li>
 				</ul>
 			</div>
 
 			<div class="container px-0">
 				<div class="profile-timeline mt-md-4">
-					<div class="row" v-if="mode == 'grid'">
-						<div class="col-4 p-1 p-md-3" v-for="(s, index) in timeline" :key="'tlob:'+index">
-							<a class="card info-overlay card-md-border-0" :href="statusUrl(s)" v-once>
-								<div class="square">
-									<div v-if="s.sensitive" class="square-content">
-										<div class="info-overlay-text-label">
+					<div v-if="mode == 'grid'">
+						<div class="row">
+							<div class="col-4 p-1 p-md-3" v-for="(s, index) in timeline" :key="'tlob:'+index">
+								<a class="card info-overlay card-md-border-0" :href="statusUrl(s)" v-once>
+									<div class="square">
+										<div v-if="s.sensitive" class="square-content">
+											<div class="info-overlay-text-label">
+												<h5 class="text-white m-auto font-weight-bold">
+													<span>
+														<span class="far fa-eye-slash fa-lg p-2 d-flex-inline"></span>
+													</span>
+												</h5>
+											</div>
+											<blur-hash-canvas
+												width="32"
+												height="32"
+												:hash="s.media_attachments[0].blurhash"
+												/>
+										</div>
+										<div v-else class="square-content">
+											<blur-hash-image
+												width="32"
+												height="32"
+												:hash="s.media_attachments[0].blurhash"
+												:src="s.media_attachments[0].preview_url"
+												/>
+										</div>
+										<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
+										<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
+										<span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
+										<div class="info-overlay-text">
 											<h5 class="text-white m-auto font-weight-bold">
 												<span>
-													<span class="far fa-eye-slash fa-lg p-2 d-flex-inline"></span>
+													<span class="far fa-comment fa-lg p-2 d-flex-inline"></span>
+													<span class="d-flex-inline">{{formatCount(s.reply_count)}}</span>
 												</span>
 											</h5>
 										</div>
-										<blur-hash-canvas
-											width="32"
-											height="32"
-											:hash="s.media_attachments[0].blurhash"
-											/>
-									</div>
-									<div v-else class="square-content">
-
-										<blur-hash-image
-											width="32"
-											height="32"
-											:hash="s.media_attachments[0].blurhash"
-											:src="s.media_attachments[0].preview_url"
-											/>
-									</div>
-									<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
-									<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
-									<span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
-									<div class="info-overlay-text">
-										<h5 class="text-white m-auto font-weight-bold">
-											<span>
-												<span class="far fa-comment fa-lg p-2 d-flex-inline"></span>
-												<span class="d-flex-inline">{{formatCount(s.reply_count)}}</span>
-											</span>
-										</h5>
 									</div>
+								</a>
+							</div>
+							<div v-if="timeline.length == 0" class="col-12">
+								<div class="py-5 text-center text-muted">
+									<p><i class="fas fa-camera-retro fa-2x"></i></p>
+									<p class="h2 font-weight-light pt-3">No posts yet</p>
 								</div>
-							</a>
-						</div>
-						<div v-if="timeline.length == 0" class="col-12">
-							<div class="py-5 text-center text-muted">
-								<p><i class="fas fa-camera-retro fa-2x"></i></p>
-								<p class="h2 font-weight-light pt-3">No posts yet</p>
 							</div>
 						</div>
-					</div>
-					<div v-if="timeline.length && mode == 'grid'">
-						<infinite-loading @infinite="infiniteTimeline">
-							<div slot="no-more"></div>
-							<div slot="no-results"></div>
-						</infinite-loading>
+						<div v-if="timeline.length">
+							<infinite-loading @infinite="infiniteTimeline">
+								<div slot="no-more"></div>
+								<div slot="no-results"></div>
+							</infinite-loading>
+						</div>
 					</div>
 					<div v-if="mode == 'bookmarks'">
 						<div v-if="bookmarksLoading">
@@ -280,8 +284,9 @@
 							</div>
 						</div>
 					</div>
+
 					<div v-if="mode == 'collections'">
-						<div v-if="collections.length" class="row">
+						<div v-if="collections.length && collectionsLoaded" class="row">
 							<div class="col-4 p-1 p-sm-2 p-md-3" v-for="(c, index) in collections">
 								<a class="card info-overlay card-md-border-0" :href="c.url">
 									<div class="square">
@@ -298,6 +303,28 @@
 							</div>
 						</div>
 					</div>
+
+					<div v-if="mode == 'archives'">
+						<div v-if="archives.length" class="col-12 col-md-8 offset-md-2 px-0 mb-sm-3 timeline mt-5">
+							<div class="alert alert-info">
+								<p class="mb-0">Posts you archive can only be seen by you.</p>
+								<p class="mb-0">For more information see the <a href="/site/kb/sharing-media">Sharing Media</a> help center page.</p>
+							</div>
+
+							<div v-for="(status, index) in archives">
+								<status-card
+									:class="{ 'border-top': index === 0 }"
+									:status="status"
+									:reaction-bar="false"
+								/>
+							</div>
+
+							<infinite-loading @infinite="archivesInfiniteLoader">
+								<div slot="no-more"></div>
+								<div slot="no-results"></div>
+							</infinite-loading>
+						</div>
+					</div>
 				</div>
 			</div>
 		</div>
@@ -663,6 +690,7 @@
 </style>
 <script type="text/javascript">
 	import VueMasonry from 'vue-masonry-css'
+	import StatusCard from './partials/StatusCard.vue';
 
 	export default {
 		props: [
@@ -671,6 +699,11 @@
 			'profile-settings',
 			'profile-username'
 		],
+
+		components: {
+			StatusCard,
+		},
+
 		data() {
 			return {
 				ids: [],
@@ -684,7 +717,7 @@
 				owner: false,
 				layout: this.profileLayout,
 				mode: 'grid',
-				modes: ['grid', 'collections', 'bookmarks'],
+				modes: ['grid', 'collections', 'bookmarks', 'archives'],
 				modalStatus: false,
 				relationship: {},
 				followers: [],
@@ -700,6 +733,7 @@
 				bookmarks: [],
 				bookmarksPage: 2,
 				collections: [],
+				collectionsLoaded: false,
 				collectionsPage: 2,
 				isMobile: false,
 				ctxEmbedPayload: null,
@@ -709,6 +743,8 @@
 				followingModalSearchCache: null,
 				followingModalTab: 'following',
 				bookmarksLoading: true,
+				archives: [],
+				archivesPage: 2
 			}
 		},
 		beforeMount() {
@@ -734,6 +770,39 @@
 				}
 			}
 
+			if(u.has('m') && this.modes.includes(u.get('m'))) {
+				this.mode = u.get('m');
+
+				if(this.mode == 'bookmarks') {
+					axios.get('/api/local/bookmarks')
+					.then(res => {
+						this.bookmarks = res.data;
+						this.bookmarksLoading = false;
+					}).catch(err => {
+						this.mode = 'grid';
+					});
+				}
+
+				if(this.mode == 'collections') {
+					axios.get('/api/local/profile/collections/' + this.profileId)
+					.then(res => {
+						this.collections = res.data
+						this.collectionsLoaded = true;
+					}).catch(err => {
+						this.mode = 'grid';
+					});
+				}
+
+				if(this.mode == 'archives') {
+					axios.get('/api/pixelfed/v2/statuses/archives')
+					.then(res => {
+						this.archives = res.data;
+					}).catch(err => {
+						this.mode = 'grid';
+					});
+				}
+			}
+
 		},
 
 		mounted() {
@@ -858,19 +927,15 @@
 			},
 
 			switchMode(mode) {
-				this.mode = _.indexOf(this.modes, mode) ? mode : 'grid';
-				if(this.mode == 'bookmarks' && this.bookmarks.length == 0) {
-					axios.get('/api/local/bookmarks')
-					.then(res => {
-						this.bookmarks = res.data;
-						this.bookmarksLoading = false;
-					});
-				}
-				if(this.mode == 'collections' && this.collections.length == 0) {
-					axios.get('/api/local/profile/collections/' + this.profileId)
-					.then(res => {
-						this.collections = res.data
-					});
+				if(mode == 'grid') {
+					this.mode = mode;
+				} else if(mode == 'bookmarks' && this.bookmarks.length) {
+					this.mode = 'bookmarks';
+				} else if(mode == 'collections' && this.collections.length) {
+					this.mode = 'collections';
+				} else {
+					window.location.href = '/' + this.profileUsername + '?m=' + mode;
+					return;
 				}
 			},
 
@@ -1362,6 +1427,23 @@
 			joinedAtFormat(created) {
 				let d = new Date(created);
 				return d.toDateString();
+			},
+
+			archivesInfiniteLoader($state) {
+				axios.get('/api/pixelfed/v2/statuses/archives', {
+					params: {
+						page: this.archivesPage
+					}
+				}).then(res => {
+					if(res.data.length) {
+						this.archives.push(...res.data);
+						this.archivesPage++;
+						$state.loaded();
+					} else {
+						$state.complete();
+					}
+
+				});
 			}
 		}
 	}

+ 30 - 5
resources/assets/js/components/partials/ContextMenu.vue

@@ -11,14 +11,16 @@
 			<div class="list-group text-center">
 				<!-- <div v-if="status && status.account.id != profile.id && ctxMenuRelationship && ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuUnfollow()">Unfollow</div>
 				<div v-if="status && status.account.id != profile.id && ctxMenuRelationship && !ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-primary" @click="ctxMenuFollow()">Follow</div> -->
-				<div class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToPost()">View Post</div>
-				<div class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToProfile()">View Profile</div>
+				<div v-if="status.visibility !== 'archived'" class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToPost()">View Post</div>
+				<div v-if="status.visibility !== 'archived'" class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToProfile()">View Profile</div>
 				<!-- <div v-if="status && status.local == true && !status.in_reply_to_id" class="list-group-item rounded cursor-pointer" @click="ctxMenuEmbed()">Embed</div>
 				<div class="list-group-item rounded cursor-pointer" @click="ctxMenuCopyLink()">Copy Link</div> -->
-				<div class="list-group-item rounded cursor-pointer" @click="ctxMenuShare()">Share</div>
-				<div v-if="status && profile && profile.is_admin == true" class="list-group-item rounded cursor-pointer" @click="ctxModMenuShow()">Moderation Tools</div>
+				<div v-if="status.visibility !== 'archived'" class="list-group-item rounded cursor-pointer" @click="ctxMenuShare()">Share</div>
+				<div v-if="status && profile && profile.is_admin == true && status.visibility !== 'archived'" class="list-group-item rounded cursor-pointer" @click="ctxModMenuShow()">Moderation Tools</div>
 				<div v-if="status && status.account.id != profile.id" class="list-group-item rounded cursor-pointer text-danger" @click="ctxMenuReportPost()">Report</div>
-				<div v-if="status && (profile.is_admin || profile.id == status.account.id)" class="list-group-item rounded cursor-pointer text-danger" @click="deletePost(status)">Delete</div>
+				<div v-if="status && profile.id == status.account.id && status.visibility !== 'archived'" class="list-group-item rounded cursor-pointer text-danger" @click="archivePost(status)">Archive</div>
+				<div v-if="status && profile.id == status.account.id && status.visibility == 'archived'" class="list-group-item rounded cursor-pointer text-danger" @click="unarchivePost(status)">Unarchive</div>
+				<div v-if="status && (profile.is_admin || profile.id == status.account.id) && status.visibility !== 'archived'" class="list-group-item rounded cursor-pointer text-danger" @click="deletePost(status)">Delete</div>
 				<div class="list-group-item rounded cursor-pointer text-lighter" @click="closeCtxMenu()">Cancel</div>
 			</div>
 		</b-modal>
@@ -680,6 +682,29 @@
 			ownerOrAdmin(status) {
 				return this.owner(status) || this.admin();
 			},
+
+			archivePost(status) {
+				if(window.confirm('Are you sure you want to archive this post?') == false) {
+					return;
+				}
+
+				axios.post('/api/pixelfed/v2/status/' + status.id + '/archive')
+				.then(res => {
+					this.$emit('status-delete', status.id);
+					this.closeModals();
+				});
+			},
+
+			unarchivePost(status) {
+				if(window.confirm('Are you sure you want to unarchive this post?') == false) {
+					return;
+				}
+
+				axios.post('/api/pixelfed/v2/status/' + status.id + '/unarchive')
+				.then(res => {
+					this.closeModals();
+				});
+			}
 		}
 	}
 </script>

+ 9 - 2
resources/assets/js/components/partials/StatusCard.vue

@@ -77,7 +77,10 @@
 			<div class="postPresenterContainer" style="background: #000;">
 
 				<div v-if="status.pf_type === 'photo'" class="w-100">
-					<photo-presenter :status="status" v-on:lightbox="lightbox" v-on:togglecw="status.sensitive = false"></photo-presenter>
+					<photo-presenter
+						:status="status"
+						v-on:lightbox="lightbox"
+						v-on:togglecw="status.sensitive = false"/>
 				</div>
 
 				<div v-else-if="status.pf_type === 'video'" class="w-100">
@@ -149,9 +152,13 @@
 				</div>
 				<div class="timestamp mt-2">
 					<p class="small mb-0">
-						<a :href="statusUrl(status)" class="text-muted text-uppercase">
+						<a v-if="status.visibility != 'archived'" :href="statusUrl(status)" class="text-muted text-uppercase">
 							<timeago :datetime="status.created_at" :auto-update="60" :converter-options="{includeSeconds:true}" :title="timestampFormat(status.created_at)" v-b-tooltip.hover.bottom></timeago>
 						</a>
+						<span v-else class="text-muted text-uppercase">
+							Posted <timeago :datetime="status.created_at" :auto-update="60" :converter-options="{includeSeconds:true}" :title="timestampFormat(status.created_at)" v-b-tooltip.hover.bottom></timeago>
+						</span>
+
 						<span v-if="recommended">
 							<span class="px-1">&middot;</span>
 							<span class="text-muted">Based on popular and trending content</span>

+ 64 - 13
resources/views/site/help/sharing-media.blade.php

@@ -32,7 +32,7 @@
 			</div>
 		</div>
 	</p>
-	<p>	
+	<p>
 		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse2" role="button" aria-expanded="false" aria-controls="collapse2">
 			<i class="fas fa-chevron-down mr-2"></i>
 			How do I share a post with multiple photos or videos?
@@ -43,7 +43,7 @@
 			</div>
 		</div>
 	</p>
-	<p>	
+	<p>
 		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse3" role="button" aria-expanded="false" aria-controls="collapse3">
 			<i class="fas fa-chevron-down mr-2"></i>
 			How do I add a caption before sharing my photos or videos on Pixelfed?
@@ -54,7 +54,7 @@
 			</div>
 		</div>
 	</p>
-	<p>	
+	<p>
 		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse4" role="button" aria-expanded="false" aria-controls="collapse4">
 			<i class="fas fa-chevron-down mr-2"></i>
 			How do I add a filter to my photos?
@@ -74,7 +74,7 @@
 			</div>
 		</div>
 	</p>
-	<p>	
+	<p>
 		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse5" role="button" aria-expanded="false" aria-controls="collapse5">
 			<i class="fas fa-chevron-down mr-2"></i>
 			How do I add a description to each photo or video for the visually impaired?
@@ -94,8 +94,8 @@
 				<p class="small text-muted"><i class="fas fa-info-circle mr-1"></i> Image descriptions are federated to instances where supported.</p>
 			</div>
 		</div>
-	</p>	
-	<p>	
+	</p>
+	<p>
 		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse6" role="button" aria-expanded="false" aria-controls="collapse6">
 			<i class="fas fa-chevron-down mr-2"></i>
 			What types of photos or videos can I upload?
@@ -104,14 +104,14 @@
 			<div>
 				You can upload the following media types:
 				<ul>
-					@foreach(explode(',', config('pixelfed.media_types')) as $type)
+					@foreach(explode(',', config_cache('pixelfed.media_types')) as $type)
 					<li class="font-weight-bold">{{$type}}</li>
 					@endforeach
 				</ul>
 			</div>
 		</div>
 	</p>
-	{{-- <p>	
+	{{-- <p>
 		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse7" role="button" aria-expanded="false" aria-controls="collapse7">
 			<i class="fas fa-chevron-down mr-2"></i>
 			What is the limit for photo and video file sizes?
@@ -122,7 +122,7 @@
 			</div>
 		</div>
 	</p> --}}
-	{{-- <p>	
+	{{-- <p>
 		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse8" role="button" aria-expanded="false" aria-controls="collapse8">
 			<i class="fas fa-chevron-down mr-2"></i>
 			When I share a photo, what's the image resolution?
@@ -133,7 +133,7 @@
 			</div>
 		</div>
 	</p> --}}
-	{{-- <p>	
+	{{-- <p>
 		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse9" role="button" aria-expanded="false" aria-controls="collapse9">
 			<i class="fas fa-chevron-down mr-2"></i>
 			Can I edit my post captions, photos or videos after sharing them?
@@ -144,7 +144,7 @@
 			</div>
 		</div>
 	</p> --}}
-	<p>	
+	<p>
 		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse10" role="button" aria-expanded="false" aria-controls="collapse10">
 			<i class="fas fa-chevron-down mr-2"></i>
 			How can I disable comments/replies on my post?
@@ -159,7 +159,7 @@
 			</div>
 		</div>
 	</p>
-	<p>	
+	<p>
 		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse11" role="button" aria-expanded="false" aria-controls="collapse11">
 			<i class="fas fa-chevron-down mr-2"></i>
 			How many people can I tag or mention in my comments or posts?
@@ -171,4 +171,55 @@
 		</div>
 	</p>
 
-@endsection
+	<p>
+		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse12" role="button" aria-expanded="false" aria-controls="collapse11">
+			<i class="fas fa-chevron-down mr-2"></i>
+			What does archive mean?
+		</a>
+		<div class="collapse" id="collapse12">
+			<div>
+				You can archive your posts which prevents anyone from interacting or viewing it.
+				<br />
+				<strong class="text-danger">Archived posts cannot be deleted or otherwise interacted with. You may not recieve interactions (comments, likes, shares) from other servers while a post is archived.</strong>
+				<br />
+			</div>
+		</div>
+	</p>
+
+	<p>
+		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse13" role="button" aria-expanded="false" aria-controls="collapse11">
+			<i class="fas fa-chevron-down mr-2"></i>
+			How can I archive my posts?
+		</a>
+		<div class="collapse" id="collapse13">
+			<div>
+				To archive your posts:
+				<ul>
+					<li>Navigate to the post</li>
+					<li>Open the menu, click the <i class="fas fa-ellipsis-v text-muted mx-2 cursor-pointer"></i> or <i class="fas fa-ellipsis-h text-muted mx-2 cursor-pointer"></i> button</li>
+					<li>Click on <span class="small font-weight-bold cursor-pointer">Archive</span></li>
+				</ul>
+			</div>
+		</div>
+	</p>
+
+	<p>
+		<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse14" role="button" aria-expanded="false" aria-controls="collapse11">
+			<i class="fas fa-chevron-down mr-2"></i>
+			How do I unarchive my posts?
+		</a>
+		<div class="collapse" id="collapse14">
+			<div>
+				To unarchive your posts:
+				<ul>
+					<li>Navigate to your profile</li>
+					<li>Click on the <strong>ARCHIVES</strong> tab</li>
+					<li>Scroll to the post you want to unarchive</li>
+					<li>Open the menu, click the <i class="fas fa-ellipsis-v text-muted mx-2 cursor-pointer"></i> or <i class="fas fa-ellipsis-h text-muted mx-2 cursor-pointer"></i> button</li>
+					<li>Click on <span class="small font-weight-bold cursor-pointer">Unarchive</span></li>
+				</ul>
+			</div>
+		</div>
+	</p>
+
+@endsection

+ 3 - 0
routes/web.php

@@ -200,6 +200,9 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
 				Route::get('discover/posts/places', 'DiscoverController@trendingPlaces');
 				Route::get('seasonal/yir', 'SeasonalController@getData');
 				Route::post('seasonal/yir', 'SeasonalController@store');
+				Route::post('status/{id}/archive', 'ApiController@archive');
+				Route::post('status/{id}/unarchive', 'ApiController@unarchive');
+				Route::get('statuses/archives', 'ApiController@archivedPosts');
 			});
 		});