CollectionCompose.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <template>
  2. <div class="container">
  3. <div v-if="loaded" class="row">
  4. <div class="col-12 col-md-6 offset-md-3 pt-5">
  5. <div class="text-center pb-4">
  6. <h1>Create Collection</h1>
  7. </div>
  8. </div>
  9. <div class="col-12 col-md-4 pt-3">
  10. <div class="card rounded-0 shadow-none border " style="min-height: 440px;">
  11. <div class="card-body">
  12. <div>
  13. <form>
  14. <div class="form-group">
  15. <label for="title" class="font-weight-bold text-muted">Title</label>
  16. <input type="text" class="form-control" id="title" placeholder="Collection Title" v-model="collection.title" maxlength="50">
  17. <div class="text-right small text-muted">
  18. <span>{{collection.title ? collection.title.length : 0}}/50</span>
  19. </div>
  20. </div>
  21. <div class="form-group">
  22. <label for="description" class="font-weight-bold text-muted">Description</label>
  23. <textarea class="form-control" id="description" placeholder="Example description here" v-model="collection.description" rows="3" maxlength="500">
  24. </textarea>
  25. <div class="text-right small text-muted">
  26. <span>{{collection.description ? collection.description.length : 0}}/500</span>
  27. </div>
  28. </div>
  29. <div class="form-group">
  30. <label for="visibility" class="font-weight-bold text-muted">Visibility</label>
  31. <select class="custom-select" v-model="collection.visibility">
  32. <option value="public">Public</option>
  33. <option value="private">Followers Only</option>
  34. <option value="draft">Draft</option>
  35. </select>
  36. </div>
  37. </form>
  38. <hr>
  39. <p>
  40. <button v-if="posts.length > 0 && collection.visibility != 'draft'" type="button" class="btn btn-primary font-weight-bold btn-block" @click="publish">Publish</button>
  41. <button v-else type="button" class="btn btn-primary font-weight-bold btn-block disabled" disabled>Publish</button>
  42. </p>
  43. <p>
  44. <button type="button" class="btn btn-outline-primary font-weight-bold btn-block" @click="save">Save</button>
  45. </p>
  46. <p class="mb-0">
  47. <button type="button" class="btn btn-outline-secondary font-weight-bold btn-block" @click="deleteCollection">Delete</button>
  48. </p>
  49. </div>
  50. </div>
  51. </div>
  52. </div>
  53. <div class="col-12 col-md-8 pt-3">
  54. <div>
  55. <ul class="nav nav-tabs">
  56. <li class="nav-item">
  57. <a :class="[tab == 'add' ? 'nav-link font-weight-bold bg-white active' : 'nav-link font-weight-bold text-muted']" href="#" @click.prevent="tab = 'add'">Add Posts</a>
  58. </li>
  59. <li class="nav-item">
  60. <a :class="[tab == 'all' ? 'nav-link font-weight-bold bg-white active' : 'nav-link font-weight-bold text-muted']" href="#" @click.prevent="tab = 'all'">Preview</a>
  61. </li>
  62. </ul>
  63. </div>
  64. <div class="card rounded-0 shadow-none border border-top-0">
  65. <div class="card-body" style="min-height: 460px;">
  66. <div v-if="tab == 'all'" class="row">
  67. <div class="col-4 p-1" v-for="(s, index) in posts">
  68. <a class="card info-overlay card-md-border-0" :href="s.url">
  69. <div class="square">
  70. <span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
  71. <span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
  72. <span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
  73. <div class="square-content" v-bind:style="previewBackground(s)">
  74. </div>
  75. <div class="info-overlay-text">
  76. <h5 class="text-white m-auto font-weight-bold">
  77. <span>
  78. <span class="far fa-heart fa-lg p-2 d-flex-inline"></span>
  79. <span class="d-flex-inline">{{s.favourites_count}}</span>
  80. </span>
  81. <span>
  82. <span class="fas fa-retweet fa-lg p-2 d-flex-inline"></span>
  83. <span class="d-flex-inline">{{s.reblogs_count}}</span>
  84. </span>
  85. </h5>
  86. </div>
  87. </div>
  88. </a>
  89. </div>
  90. </div>
  91. <div v-if="tab == 'add'">
  92. <div class="form-group">
  93. <label for="title" class="font-weight-bold text-muted">Add Post by URL</label>
  94. <input type="text" class="form-control" placeholder="https://pixelfed.dev/p/admin/1" v-model="id">
  95. <p class="help-text small text-muted">Only local, public posts can be added</p>
  96. </div>
  97. <div class="form-group pt-4">
  98. <label for="title" class="font-weight-bold text-muted">Add Recent Post</label>
  99. <div style="max-height: 360px; overflow-y: auto">
  100. <div v-for="(s, index) in recentPosts" :class="[selectedPost == s.id ? 'box-shadow border border-warning d-inline-block m-1':'d-inline-block m-1']" @click="selectPost(s)">
  101. <div class="cursor-pointer" :style="'width: 175px; height: 175px; ' + previewBackground(s)"></div>
  102. </div>
  103. </div>
  104. </div>
  105. <hr>
  106. <button type="button" class="btn btn-primary font-weight-bold btn-block" @click="addId">Add Post</button>
  107. </div>
  108. <div v-if="tab == 'order'">
  109. </div>
  110. </div>
  111. </div>
  112. </div>
  113. </div>
  114. </div>
  115. </template>
  116. <script type="text/javascript">
  117. export default {
  118. props: ['collection-id', 'profile-id'],
  119. data() {
  120. return {
  121. config: window.App.config,
  122. loaded: false,
  123. limit: 8,
  124. step: 1,
  125. title: '',
  126. description: '',
  127. collection: {
  128. title: '',
  129. description: '',
  130. visibility: 'draft'
  131. },
  132. id: '',
  133. posts: [],
  134. tab: 'add',
  135. tabs: [
  136. 'all',
  137. 'add',
  138. 'order'
  139. ],
  140. recentPosts: [],
  141. selectedPost: '',
  142. }
  143. },
  144. beforeMount() {
  145. axios.get('/api/local/collection/' + this.collectionId)
  146. .then(res => {
  147. this.collection = res.data;
  148. });
  149. },
  150. mounted() {
  151. this.fetchRecentPosts();
  152. this.fetchItems();
  153. axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
  154. window._sharedData.curUser = res.data;
  155. window.App.util.navatar();
  156. });
  157. },
  158. methods: {
  159. addToIds(id) {
  160. axios.post('/api/local/collection/item', {
  161. collection_id: this.collectionId,
  162. post_id: id
  163. }).then(res => {
  164. this.fetchItems();
  165. this.fetchRecentPosts();
  166. this.tab = 'all';
  167. this.id = '';
  168. }).catch(err => {
  169. swal('Invalid URL', 'The post you entered was invalid', 'error');
  170. this.id = '';
  171. })
  172. },
  173. fetchItems() {
  174. axios.get('/api/local/collection/items/' + this.collectionId)
  175. .then(res => {
  176. this.posts = res.data;
  177. this.loaded = true;
  178. });
  179. },
  180. addId() {
  181. let max = this.config.uploader.max_collection_length;
  182. if(this.posts.length >= max) {
  183. swal('Error', 'You can only add ' + max + ' posts per collection', 'error');
  184. return;
  185. }
  186. let url = this.id;
  187. let origin = window.location.origin;
  188. let split = url.split('/');
  189. if(url.slice(0, origin.length) !== origin) {
  190. swal('Invalid URL', 'You can only add posts from this instance', 'error');
  191. this.id = '';
  192. }
  193. if(url.includes('/i/web/post/') || url.includes('/p/')) {
  194. let id = split[split.length - 1];
  195. console.log('adding ' + id);
  196. this.addToIds(id);
  197. return;
  198. } else {
  199. swal('Invalid URL', 'Invalid URL', 'error');
  200. this.id = '';
  201. }
  202. return;
  203. },
  204. previewUrl(status) {
  205. return status.sensitive ? '/storage/no-preview.png?v=' + new Date().getTime() : status.media_attachments[0].preview_url;
  206. },
  207. previewBackground(status) {
  208. let preview = this.previewUrl(status);
  209. return 'background-image: url(' + preview + ');background-size:cover;';
  210. },
  211. fetchRecentPosts() {
  212. axios.get('/api/v1/accounts/' + this.profileId + '/statuses', {
  213. params: {
  214. only_media: true,
  215. min_id: 1,
  216. limit: 40
  217. }
  218. }).then(res => {
  219. this.recentPosts = res.data.filter(s => {
  220. let ids = this.posts.map(s => {
  221. return s.id;
  222. });
  223. return s.visibility == 'public' && s.sensitive == false && ids.indexOf(s.id) == -1;
  224. });
  225. });
  226. },
  227. selectPost(status) {
  228. this.selectedPost = status.id;
  229. this.id = status.url;
  230. },
  231. publish() {
  232. if(this.posts.length == 0) {
  233. swal('Error', 'You cannot publish an empty collection');
  234. return;
  235. }
  236. axios.post('/api/local/collection/' + this.collectionId + '/publish', {
  237. title: this.collection.title,
  238. description: this.collection.description,
  239. visibility: this.collection.visibility
  240. })
  241. .then(res => {
  242. window.location.href = res.data.url;
  243. }).catch(err => {
  244. swal('Something went wrong', 'There was a problem with your request, please try again later.', 'error');
  245. });
  246. },
  247. save() {
  248. axios.post('/api/local/collection/' + this.collectionId, {
  249. title: this.collection.title,
  250. description: this.collection.description,
  251. visibility: this.collection.visibility
  252. })
  253. .then(res => {
  254. swal('Saved!', 'You have successfully saved this collection.', 'success');
  255. });
  256. },
  257. deleteCollection() {
  258. let confirm = window.confirm('Are you sure you want to delete this collection?');
  259. if(!confirm) {
  260. return;
  261. }
  262. axios.delete('/api/local/collection/' + this.collectionId)
  263. .then(res => {
  264. window.location.href = '/';
  265. });
  266. }
  267. }
  268. }
  269. </script>