|
@@ -0,0 +1,606 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="h-100 pf-import">
|
|
|
|
+ <div v-if="!loaded" class="d-flex justify-content-center align-items-center h-100">
|
|
|
|
+ <b-spinner />
|
|
|
|
+ </div>
|
|
|
|
+ <template v-else>
|
|
|
|
+ <input type="file" name="file" class="d-none" ref="zipInput" @change="zipInputChanged" />
|
|
|
|
+ <template v-if="page === 1">
|
|
|
|
+ <div class="title">
|
|
|
|
+ <h3 class="font-weight-bold">Import</h3>
|
|
|
|
+ </div>
|
|
|
|
+ <hr>
|
|
|
|
+ <section>
|
|
|
|
+ <p class="lead">Account Import allows you to import your data from a supported service.</p>
|
|
|
|
+ </section>
|
|
|
|
+ <section class="mt-4">
|
|
|
|
+ <ul class="list-group">
|
|
|
|
+ <li class="list-group-item d-flex justify-content-between flex-column" style="gap:1rem">
|
|
|
|
+ <div class="d-flex justify-content-between align-items-center" style="gap: 1rem;">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="font-weight-bold mb-1">Import from Instagram</p>
|
|
|
|
+ <p v-if="showDisabledWarning" class="small mb-0">This feature has been disabled by the administrators.</p>
|
|
|
|
+ <p v-else-if="showNotAllowedWarning" class="small mb-0">You have not been permitted to use this feature, or have reached the maximum limits. For more info, view the <a href="/site/kb/import" class="font-weight-bold">Import Help Center</a> page.</p>
|
|
|
|
+ <p v-else class="small mb-0">Upload the JSON export from Instagram in .zip format.<br />For more information click <a href="/site/kb/import">here</a>.</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div v-if="!showDisabledWarning && !showNotAllowedWarning">
|
|
|
|
+ <button
|
|
|
|
+ v-if="step === 1 || invalidArchive"
|
|
|
|
+ type="button"
|
|
|
|
+ class="font-weight-bold btn btn-primary rounded-pill px-4 btn-lg"
|
|
|
|
+ @click="selectArchive()"
|
|
|
|
+ :disabled="showDisabledWarning">
|
|
|
|
+ Import
|
|
|
|
+ </button>
|
|
|
|
+
|
|
|
|
+ <template v-else-if="step === 2">
|
|
|
|
+ <div class="d-flex justify-content-center align-items-center flex-column">
|
|
|
|
+ <b-spinner v-if="showUploadLoader" small />
|
|
|
|
+ <button v-else type="button" class="font-weight-bold btn btn-outline-primary btn-sm btn-block" @click="reviewImports()">Review Imports</button>
|
|
|
|
+ <p v-if="zipName" class="small font-weight-bold mt-2 mb-0">{{ zipName }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </li>
|
|
|
|
+ </ul>
|
|
|
|
+ <ul class="list-group mt-3">
|
|
|
|
+
|
|
|
|
+ <li v-if="processingCount" class="list-group-item d-flex justify-content-between flex-column" style="gap:1rem">
|
|
|
|
+ <div class="d-flex justify-content-between align-items-center">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="font-weight-bold mb-1">Processing Imported Posts</p>
|
|
|
|
+ <p class="small mb-0">These are posts that are in the process of being imported.</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <span class="btn btn-danger rounded-pill py-0 font-weight-bold" disabled>{{ processingCount }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </li>
|
|
|
|
+
|
|
|
|
+ <li v-if="finishedCount" class="list-group-item d-flex justify-content-between flex-column" style="gap:1rem">
|
|
|
|
+ <div class="d-flex justify-content-between align-items-center">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="font-weight-bold mb-1">Imported Posts</p>
|
|
|
|
+ <p class="small mb-0">These are posts that have been successfully imported.</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <button
|
|
|
|
+ type="button"
|
|
|
|
+ class="font-weight-bold btn btn-primary btn-sm rounded-pill px-4 btn-block"
|
|
|
|
+ @click="handleReviewPosts()"
|
|
|
|
+ :disabled="!finishedCount">
|
|
|
|
+ Review {{ finishedCount }} Posts
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </li>
|
|
|
|
+ </ul>
|
|
|
|
+
|
|
|
|
+ </section>
|
|
|
|
+ </template>
|
|
|
|
+
|
|
|
|
+ <template v-else-if="page === 2">
|
|
|
|
+ <div class="d-flex justify-content-between align-items-center">
|
|
|
|
+
|
|
|
|
+ <div class="title">
|
|
|
|
+ <h3 class="font-weight-bold">Import from Instagram</h3>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <button
|
|
|
|
+ class="btn btn-primary font-weight-bold rounded-pill px-4"
|
|
|
|
+ :class="{ disabled: !selectedMedia || !selectedMedia.length }"
|
|
|
|
+ :disabled="!selectedMedia || !selectedMedia.length || importButtonLoading"
|
|
|
|
+ @click="handleImport()"
|
|
|
|
+ >
|
|
|
|
+ <b-spinner v-if="importButtonLoading" small />
|
|
|
|
+ <span v-else>Import</span>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <hr>
|
|
|
|
+ <section>
|
|
|
|
+ <div class="d-flex justify-content-between align-items-center mb-3">
|
|
|
|
+ <div v-if="!selectedMedia || !selectedMedia.length">
|
|
|
|
+ <p class="lead mb-0">Review posts you'd like to import.</p>
|
|
|
|
+ <p class="small text-muted mb-0">Tap on posts to include them in your import.</p>
|
|
|
|
+ </div>
|
|
|
|
+ <p v-else class="lead mb-0"><span class="font-weight-bold">{{ selectedPostsCounter }}</span> posts selected for import</p>
|
|
|
|
+ </div>
|
|
|
|
+ </section>
|
|
|
|
+ <section class="row mb-n5 media-selector" style="max-height: 600px;overflow-y: auto;">
|
|
|
|
+ <div v-for="media in postMeta" class="col-12 col-md-4">
|
|
|
|
+ <div
|
|
|
|
+ class="square cursor-pointer"
|
|
|
|
+ @click="toggleSelectedPost(media)">
|
|
|
|
+ <div
|
|
|
|
+ v-if="media.media[0].uri.endsWith('.mp4')"
|
|
|
|
+ :class="{ selected: selectedMedia.indexOf(media.media[0].uri) != -1 }"
|
|
|
|
+ class="info-overlay-text-label rounded">
|
|
|
|
+ <h5 class="text-white m-auto font-weight-bold">
|
|
|
|
+ <span>
|
|
|
|
+ <span class="far fa-video fa-2x p-2 d-flex-inline"></span>
|
|
|
|
+ </span>
|
|
|
|
+ </h5>
|
|
|
|
+ </div>
|
|
|
|
+ <div
|
|
|
|
+ v-else
|
|
|
|
+ class="square-content"
|
|
|
|
+ :class="{ selected: selectedMedia.indexOf(media.media[0].uri) != -1 }"
|
|
|
|
+ :style="{ borderRadius: '5px', backgroundImage: 'url(' + getFileNameUrl(media.media[0].uri) + ')'}">
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="d-flex mt-1 justify-content-between align-items-center">
|
|
|
|
+ <p class="small"><i class="far fa-clock"></i> {{ formatDate(media.media[0].creation_timestamp) }}</p>
|
|
|
|
+ <p class="small font-weight-bold"><a href="#" @click.prevent="showDetailsModal(media)"><i class="far fa-info-circle"></i> Details</a></p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </section>
|
|
|
|
+ </template>
|
|
|
|
+
|
|
|
|
+ <template v-else-if="page === 'reviewImports'">
|
|
|
|
+ <div class="d-flex justify-content-between align-items-center">
|
|
|
|
+
|
|
|
|
+ <div class="title">
|
|
|
|
+ <h3 class="font-weight-bold">Posts Imported from Instagram</h3>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <hr>
|
|
|
|
+ <section class="row mb-n5 media-selector" style="max-height: 600px;overflow-y: auto;">
|
|
|
|
+ <div v-for="media in importedPosts.data" class="col-12 col-md-4">
|
|
|
|
+ <div
|
|
|
|
+ class="square cursor-pointer">
|
|
|
|
+ <div
|
|
|
|
+ v-if="media.media_attachments[0].url.endsWith('.mp4')"
|
|
|
|
+ class="info-overlay-text-label rounded">
|
|
|
|
+ <h5 class="text-white m-auto font-weight-bold">
|
|
|
|
+ <span>
|
|
|
|
+ <span class="far fa-video fa-2x p-2 d-flex-inline"></span>
|
|
|
|
+ </span>
|
|
|
|
+ </h5>
|
|
|
|
+ </div>
|
|
|
|
+ <div
|
|
|
|
+ v-else
|
|
|
|
+ class="square-content"
|
|
|
|
+ :style="{ borderRadius: '5px', backgroundImage: 'url(' + media.media_attachments[0].url + ')'}">
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="d-flex mt-1 justify-content-between align-items-center">
|
|
|
|
+ <p class="small"><i class="far fa-clock"></i> {{ formatDate(media.created_at, false) }}</p>
|
|
|
|
+ <p class="small font-weight-bold"><a :href="media.url"><i class="far fa-info-circle"></i> View</a></p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="col-12 my-3">
|
|
|
|
+ <button
|
|
|
|
+ v-if="importedPosts.meta && importedPosts.meta.next_cursor"
|
|
|
|
+ class="btn btn-primary btn-block font-weight-bold"
|
|
|
|
+ @click="loadMorePosts()">
|
|
|
|
+ Load more
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </section>
|
|
|
|
+ </template>
|
|
|
|
+ </template>
|
|
|
|
+
|
|
|
|
+ <b-modal
|
|
|
|
+ id="detailsModal"
|
|
|
|
+ title="Post Details"
|
|
|
|
+ v-model="detailsModalShow"
|
|
|
|
+ :ok-only="true"
|
|
|
|
+ ok-title="Close"
|
|
|
|
+ centered>
|
|
|
|
+ <div class="">
|
|
|
|
+ <div v-for="(media, idx) in modalData.media" class="mb-3">
|
|
|
|
+ <div class="list-group">
|
|
|
|
+ <div class="list-group-item d-flex justify-content-between align-items-center">
|
|
|
|
+ <p class="text-center font-weight-bold mb-0">Media #{{idx + 1}}</p>
|
|
|
|
+ <img :src="getFileNameUrl(media.uri)" width="30" height="30" style="object-fit: cover; border-radius: 5px;">
|
|
|
|
+ </div>
|
|
|
|
+ <div class="list-group-item">
|
|
|
|
+ <p class="small text-muted">Caption</p>
|
|
|
|
+ <p class="mb-0 small read-more" style="font-size: 12px;overflow-y: hidden;">{{ media.title ? media.title : modalData.title }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="list-group-item">
|
|
|
|
+ <div class="d-flex justify-content-between align-items-center">
|
|
|
|
+ <p class="small mb-0 text-muted">Timestamp</p>
|
|
|
|
+ <p class="font-weight-bold mb-0">{{ formatDate(media.creation_timestamp) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </b-modal>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script type="text/javascript">
|
|
|
|
+ import * as zip from "@zip.js/zip.js";
|
|
|
|
+
|
|
|
|
+ export default {
|
|
|
|
+ data() {
|
|
|
|
+ return {
|
|
|
|
+ page: 1,
|
|
|
|
+ step: 1,
|
|
|
|
+ toggleLimit: 100,
|
|
|
|
+ config: {},
|
|
|
|
+ showDisabledWarning: false,
|
|
|
|
+ showNotAllowedWarning: false,
|
|
|
|
+ invalidArchive: false,
|
|
|
|
+ loaded: false,
|
|
|
|
+ existing: [],
|
|
|
|
+ zipName: undefined,
|
|
|
|
+ zipFiles: [],
|
|
|
|
+ postMeta: [],
|
|
|
|
+ imageCache: [],
|
|
|
|
+ includeArchives: false,
|
|
|
|
+ selectedMedia: [],
|
|
|
|
+ selectedPostsCounter: 0,
|
|
|
|
+ detailsModalShow: false,
|
|
|
|
+ modalData: {},
|
|
|
|
+ importedPosts: [],
|
|
|
|
+ finishedCount: undefined,
|
|
|
|
+ processingCount: undefined,
|
|
|
|
+ showUploadLoader: false,
|
|
|
|
+ importButtonLoading: false,
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ mounted() {
|
|
|
|
+ this.fetchConfig();
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ methods: {
|
|
|
|
+ fetchConfig() {
|
|
|
|
+ axios.get('/api/local/import/ig/config')
|
|
|
|
+ .then(res => {
|
|
|
|
+ this.config = res.data;
|
|
|
|
+
|
|
|
|
+ if(res.data.enabled == false) {
|
|
|
|
+ this.showDisabledWarning = true;
|
|
|
|
+ this.loaded = true;
|
|
|
|
+ } else if(res.data.allowed == false) {
|
|
|
|
+ this.showNotAllowedWarning = true;
|
|
|
|
+ this.loaded = true;
|
|
|
|
+ } else {
|
|
|
|
+ this.fetchExisting();
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ fetchExisting() {
|
|
|
|
+ axios.post('/api/local/import/ig/existing')
|
|
|
|
+ .then(res => {
|
|
|
|
+ this.existing = res.data;
|
|
|
|
+ })
|
|
|
|
+ .finally(() => {
|
|
|
|
+ this.fetchProcessing();
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ fetchProcessing() {
|
|
|
|
+ axios.post('/api/local/import/ig/processing')
|
|
|
|
+ .then(res => {
|
|
|
|
+ this.processingCount = res.data.processing_count;
|
|
|
|
+ this.finishedCount = res.data.finished_count;
|
|
|
|
+ })
|
|
|
|
+ .finally(() => {
|
|
|
|
+ this.loaded = true;
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ selectArchive() {
|
|
|
|
+ event.currentTarget.blur();
|
|
|
|
+ swal({
|
|
|
|
+ title: 'Upload Archive',
|
|
|
|
+ icon: 'success',
|
|
|
|
+ text: 'The .zip archive is probably named something like username_20230606.zip, and was downloaded from the Instagram.com website.',
|
|
|
|
+ buttons: {
|
|
|
|
+ cancel: "Cancel",
|
|
|
|
+ danger: {
|
|
|
|
+ text: "Upload zip archive",
|
|
|
|
+ value: "upload"
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ .then(res => {
|
|
|
|
+ this.$refs.zipInput.click();
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ zipInputChanged(event) {
|
|
|
|
+ this.step = 2;
|
|
|
|
+ this.zipName = event.target.files[0].name;
|
|
|
|
+ this.showUploadLoader = true;
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ this.reviewImports();
|
|
|
|
+ }, 1000);
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ this.showUploadLoader = false;
|
|
|
|
+ }, 3000);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ reviewImports() {
|
|
|
|
+ this.invalidArchive = false;
|
|
|
|
+ this.checkZip();
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ model(file, options = {}) {
|
|
|
|
+ return (new zip.ZipReader(new zip.BlobReader(file))).getEntries(options);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ formatDate(ts, unixt = true) {
|
|
|
|
+ let date = unixt ? new Date(ts * 1000) : new Date(ts);
|
|
|
|
+ return date.toLocaleDateString()
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ getFileNameUrl(filename) {
|
|
|
|
+ return this.imageCache.filter(e => e.filename === filename).map(e => e.blob);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ showDetailsModal(entry) {
|
|
|
|
+ this.modalData = entry;
|
|
|
|
+ this.detailsModalShow = true;
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ pixelfed.readmore();
|
|
|
|
+ }, 500);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ filterPostMeta(media) {
|
|
|
|
+ let json = JSON.parse(media);
|
|
|
|
+ let res = json.filter(j => {
|
|
|
|
+ let ids = j.media.map(m => m.uri).filter(m => {
|
|
|
|
+ if(this.config.allow_video_posts == true) {
|
|
|
|
+ return m.endsWith('.png') || m.endsWith('.jpg') || m.endsWith('.mp4');
|
|
|
|
+ } else {
|
|
|
|
+ return m.endsWith('.png') || m.endsWith('.jpg');
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ return ids.length;
|
|
|
|
+ }).filter(j => {
|
|
|
|
+ let ids = j.media.map(m => m.uri);
|
|
|
|
+ return !this.existing.includes(ids[0]);
|
|
|
|
+ })
|
|
|
|
+ this.postMeta = res;
|
|
|
|
+ return res;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ async checkZip() {
|
|
|
|
+ let file = this.$refs.zipInput.files[0];
|
|
|
|
+ let entries = await this.model(file);
|
|
|
|
+ if (entries && entries.length) {
|
|
|
|
+ let files = await entries.filter(e => e.filename === 'content/posts_1.json');
|
|
|
|
+
|
|
|
|
+ if(!files || !files.length) {
|
|
|
|
+ this.contactModal(
|
|
|
|
+ 'Invalid import archive',
|
|
|
|
+ "The .zip archive you uploaded is corrupted, or is invalid. We cannot process your import at this time.\n\nIf this issue persists, please contact an administrator.",
|
|
|
|
+ 'error'
|
|
|
|
+ )
|
|
|
|
+ this.invalidArchive = true;
|
|
|
|
+ return;
|
|
|
|
+ } else {
|
|
|
|
+ this.readZip();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ async readZip() {
|
|
|
|
+ let file = this.$refs.zipInput.files[0];
|
|
|
|
+ let entries = await this.model(file);
|
|
|
|
+ if (entries && entries.length) {
|
|
|
|
+ this.zipFiles = entries;
|
|
|
|
+ let media = await entries.filter(e => e.filename === 'content/posts_1.json')[0].getData(new zip.TextWriter());
|
|
|
|
+ this.filterPostMeta(media);
|
|
|
|
+
|
|
|
|
+ let imgs = await Promise.all(entries.filter(entry => {
|
|
|
|
+ return entry.filename.startsWith('media/posts/') && (entry.filename.endsWith('.png') || entry.filename.endsWith('.jpg') || entry.filename.endsWith('.mp4'));
|
|
|
|
+ })
|
|
|
|
+ .map(async entry => {
|
|
|
|
+ if(
|
|
|
|
+ entry.filename.startsWith('media/posts/') &&
|
|
|
|
+ (
|
|
|
|
+ entry.filename.endsWith('.png') ||
|
|
|
|
+ entry.filename.endsWith('.jpg') ||
|
|
|
|
+ entry.filename.endsWith('.mp4')
|
|
|
|
+ )
|
|
|
|
+ ) {
|
|
|
|
+ let types = {
|
|
|
|
+ 'png': 'image/png',
|
|
|
|
+ 'jpg': 'image/jpeg',
|
|
|
|
+ 'jpeg': 'image/jpeg',
|
|
|
|
+ 'mp4': 'video/mp4'
|
|
|
|
+ }
|
|
|
|
+ let type = types[entry.filename.split('/').pop().split('.').pop()];
|
|
|
|
+ let blob = await entry.getData(new zip.BlobWriter(type));
|
|
|
|
+ let url = URL.createObjectURL(blob);
|
|
|
|
+ return {
|
|
|
|
+ filename: entry.filename,
|
|
|
|
+ blob: url,
|
|
|
|
+ file: blob
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ }));
|
|
|
|
+ this.imageCache = imgs.flat(2);
|
|
|
|
+ }
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ this.page = 2;
|
|
|
|
+ }, 500);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ toggleLimitReached() {
|
|
|
|
+ this.contactModal(
|
|
|
|
+ 'Limit reached',
|
|
|
|
+ "You can only import " + this.toggleLimit + " posts at a time.\nYou can import more posts after you finish importing these posts.",
|
|
|
|
+ 'error'
|
|
|
|
+ )
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ toggleSelectedPost(media) {
|
|
|
|
+ let filename;
|
|
|
|
+ let self = this;
|
|
|
|
+ if(media.media.length === 1) {
|
|
|
|
+ filename = media.media[0].uri
|
|
|
|
+ if(this.selectedMedia.indexOf(filename) == -1) {
|
|
|
|
+ if(this.selectedPostsCounter >= this.toggleLimit) {
|
|
|
|
+ this.toggleLimitReached();
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ this.selectedMedia.push(filename);
|
|
|
|
+ this.selectedPostsCounter++;
|
|
|
|
+ } else {
|
|
|
|
+ let idx = this.selectedMedia.indexOf(filename);
|
|
|
|
+ this.selectedMedia.splice(idx, 1);
|
|
|
|
+ this.selectedPostsCounter--;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ filename = media.media[0].uri
|
|
|
|
+ if(this.selectedMedia.indexOf(filename) == -1) {
|
|
|
|
+ if(this.selectedPostsCounter >= this.toggleLimit) {
|
|
|
|
+ this.toggleLimitReached();
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ this.selectedPostsCounter++;
|
|
|
|
+ } else {
|
|
|
|
+ this.selectedPostsCounter--;
|
|
|
|
+ }
|
|
|
|
+ media.media.forEach(function(m) {
|
|
|
|
+ filename = m.uri
|
|
|
|
+ if(self.selectedMedia.indexOf(filename) == -1) {
|
|
|
|
+ self.selectedMedia.push(filename);
|
|
|
|
+ } else {
|
|
|
|
+ let idx = self.selectedMedia.indexOf(filename);
|
|
|
|
+ self.selectedMedia.splice(idx, 1);
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ sliceIntoChunks(arr, chunkSize) {
|
|
|
|
+ const res = [];
|
|
|
|
+ for (let i = 0; i < arr.length; i += chunkSize) {
|
|
|
|
+ const chunk = arr.slice(i, i + chunkSize);
|
|
|
|
+ res.push(chunk);
|
|
|
|
+ }
|
|
|
|
+ return res;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ handleImport() {
|
|
|
|
+ swal('Importing...', "Please wait while we upload your imported posts.\n Keep this page open and do not navigate away.", 'success');
|
|
|
|
+ this.importButtonLoading = true;
|
|
|
|
+ let ic = this.imageCache.filter(e => {
|
|
|
|
+ return this.selectedMedia.indexOf(e.filename) != -1;
|
|
|
|
+ })
|
|
|
|
+ let chunks = this.sliceIntoChunks(ic, 10);
|
|
|
|
+ chunks.forEach(c => {
|
|
|
|
+ let formData = new FormData();
|
|
|
|
+ c.map((e, idx) => {
|
|
|
|
+ let file = new File([e.file], e.filename);
|
|
|
|
+ formData.append('file['+ idx +']', file, e.filename.split('/').pop());
|
|
|
|
+ })
|
|
|
|
+ axios.post(
|
|
|
|
+ '/api/local/import/ig/media',
|
|
|
|
+ formData,
|
|
|
|
+ {
|
|
|
|
+ headers: {
|
|
|
|
+ 'Content-Type': `multipart/form-data`,
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ .catch(err => {
|
|
|
|
+ this.contactModal(
|
|
|
|
+ 'Error',
|
|
|
|
+ err.response.data.message,
|
|
|
|
+ 'error'
|
|
|
|
+ )
|
|
|
|
+ });
|
|
|
|
+ })
|
|
|
|
+ axios.post('/api/local/import/ig', {
|
|
|
|
+ files: this.postMeta.filter(e => this.selectedMedia.includes(e.media[0].uri)).map(e => {
|
|
|
|
+ if(e.hasOwnProperty('title')) {
|
|
|
|
+ return {
|
|
|
|
+ title: e.title,
|
|
|
|
+ 'creation_timestamp': e.creation_timestamp,
|
|
|
|
+ uri: e.uri,
|
|
|
|
+ media: e.media
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ return {
|
|
|
|
+ title: null,
|
|
|
|
+ 'creation_timestamp': null,
|
|
|
|
+ uri: null,
|
|
|
|
+ media: e.media
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }).then(res => {
|
|
|
|
+ if(res) {
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ window.location.reload()
|
|
|
|
+ }, 5000);
|
|
|
|
+ }
|
|
|
|
+ }).catch(err => {
|
|
|
|
+ this.contactModal(
|
|
|
|
+ 'Error',
|
|
|
|
+ err.response.data.error,
|
|
|
|
+ 'error'
|
|
|
|
+ )
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ handleReviewPosts() {
|
|
|
|
+ this.page = 'reviewImports';
|
|
|
|
+
|
|
|
|
+ axios.post('/api/local/import/ig/posts')
|
|
|
|
+ .then(res => {
|
|
|
|
+ this.importedPosts = res.data;
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ loadMorePosts() {
|
|
|
|
+ event.currentTarget.blur();
|
|
|
|
+
|
|
|
|
+ axios.post('/api/local/import/ig/posts', {
|
|
|
|
+ cursor: this.importedPosts.meta.next_cursor
|
|
|
|
+ })
|
|
|
|
+ .then(res => {
|
|
|
|
+ let data = res.data;
|
|
|
|
+ data.data = [...this.importedPosts.data, ...res.data.data];
|
|
|
|
+ this.importedPosts = data;
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ contactModal(title = 'Error', text, icon, closeButton = 'Close') {
|
|
|
|
+ swal({
|
|
|
|
+ title: title,
|
|
|
|
+ text: text,
|
|
|
|
+ icon: icon,
|
|
|
|
+ dangerMode: true,
|
|
|
|
+ buttons: {
|
|
|
|
+ ok: closeButton,
|
|
|
|
+ danger: {
|
|
|
|
+ text: 'Contact Support',
|
|
|
|
+ value: 'contact'
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ .then(res => {
|
|
|
|
+ if(res === 'contact') {
|
|
|
|
+ window.location.href = '/site/contact'
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
+ .pf-import {
|
|
|
|
+ .media-selector {
|
|
|
|
+ .selected {
|
|
|
|
+ border: 5px solid red;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+</style>
|