even merge (#528)

even merge
This commit is contained in:
Liyas Thomas
2020-01-27 05:36:22 +05:30
committed by GitHub
33 changed files with 2533 additions and 307 deletions

View File

@@ -15,6 +15,8 @@ language: node_js
node_js: node_js:
- "12" - "12"
os: linux
addons: addons:
apt: apt:
packages: packages:

View File

@@ -3,10 +3,10 @@
<br> <br>
<br> <br>
<p> <p>
API request builder - A free, fast, and beautiful alternative to Postman <b>A free, fast & beautiful API request builder</b>
</p> </p>
<p> <p>
Helps you create your requests faster, saving you precious time on your development - <a href="https://postwoman.launchaco.com">Subscribe</a> <i>Web alternative to Postman - Helps you create requests faster, saving precious time on development - <a href="https://postwoman.launchaco.com">Subscribe</a></i>
</p> </p>
<p> <p>
@@ -69,8 +69,8 @@ _Customized themes are synced with local session storage_
- Instant loading with [Service Workers](https://developers.google.com/web/fundamentals/primers/service-workers) - Instant loading with [Service Workers](https://developers.google.com/web/fundamentals/primers/service-workers)
- Offline support - Offline support
- Low RAM/memory and CPU usage - Low RAM/memory and CPU usage
- [Add to Home Screen](https://developers.google.com/web/fundamentals/app-install-banners) (button in footer) - Add to Home Screen
- [Desktop PWA](https://developers.google.com/web/progressive-web-apps/desktop) support (button in footer) - Desktop PWA
- ([full features](https://developers.google.com/web/progressive-web-apps)) - ([full features](https://developers.google.com/web/progressive-web-apps))
🚀 **Request**: Retrieve response from endpoint instantly. 🚀 **Request**: Retrieve response from endpoint instantly.
@@ -190,38 +190,36 @@ _Requests with Pre-Request Scripts are indicated in History entries_
🌎 **i18n β**: Experience the app in your own language. 🌎 **i18n β**: Experience the app in your own language.
1. Scroll down to the footer 1. Scroll down to the footer
2. Click "Choose Language" button 2. Click "Choose Language" icon button
3. Select your language from the menu 3. Select your language from the menu
_Keep in mind translations aren't available for all source and target language combinations_ _Keep in mind: Translations aren't available for all source and target language combinations_
**To provide a localized experience for users around the world, you can add you own translations.** **To provide a localized experience for users around the world, you can add you own translations.**
- Add a new locale in `lang/`
Example: `lang/es-ES.js`
- Mention `code`, `name`, `iso` and `file` in `nuxt.config.js`
Example:
```
i18n: {
locales: [{
code: 'es',
name: 'Español',
iso: 'es-ES',
file: 'es-ES.js'
}]
}
```
_**All `i18n` contributions are welcome to `i18n` [branch](https://github.com/liyasthomas/postwoman/tree/i18n) only!**_ _**All `i18n` contributions are welcome to `i18n` [branch](https://github.com/liyasthomas/postwoman/tree/i18n) only!**_
📦 **Add-ons**: Official add-ons for Postwoman. 📦 **Add-ons**: Official add-ons for Postwoman.
- **[Postwoman Proxy β](https://github.com/postwoman-io/postwoman-proxy)** - A simple proxy server created for Postwoman - **[Proxy β](https://github.com/postwoman-io/postwoman-proxy)** - A simple proxy server created for Postwoman
- **[Postwoman CLI β](https://github.com/postwoman-io/postwoman-cli)** - A CLI solution for Postwoman - **[CLI β](https://github.com/postwoman-io/postwoman-cli)** - A CLI solution for Postwoman
- **Browser Extensions** - Browser extensions that simplifies access to Postwoman
_Add-ons are developed and maintained under **[Official Postwoman Organization](https://github.com/postwoman-io)**_ [![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_16x16.png) **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/postwoman) ([GitHub](https://github.com/AndrewBastin/postwoman-firefox)) &nbsp;|&nbsp; [![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_16x16.png) **Chrome**](https://chrome.google.com/webstore/detail/postwoman-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld) ([GitHub](https://github.com/AndrewBastin/postwoman-chrome))
>**Extensions fixes `CORS` issues.**
_Add-ons are developed and maintained under **[Official Postwoman Organization](https://github.com/postwoman-io)**._
☁️ **Auth + Sync**: Sign in and sync in real-time.
**Sign in with:**
- Google
- GitHub
**Sync:**
- History
- Collections
**To find out more, please check out [Postwoman Wiki](https://github.com/liyasthomas/postwoman/wiki).** **To find out more, please check out [Postwoman Wiki](https://github.com/liyasthomas/postwoman/wiki).**

View File

@@ -27,6 +27,7 @@
-webkit-font-feature-settings: "liga"; -webkit-font-feature-settings: "liga";
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
font-feature-settings: "liga"; font-feature-settings: "liga";
border-radius: 50%;
} }
/* poppins-500 - latin */ /* poppins-500 - latin */

View File

@@ -592,7 +592,6 @@ pre {
select { select {
height: 37px; height: 37px;
background-color: var(--bg-dark-color);
cursor: pointer; cursor: pointer;
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
@@ -698,6 +697,7 @@ ol li {
.show-on-large-screen { .show-on-large-screen {
display: inline-flex; display: inline-flex;
flex: 1;
} }
.info-response { .info-response {
@@ -852,10 +852,7 @@ input[type="radio"]:checked + label + .tab {
} }
@media (max-width: $responsiveWidth) { @media (max-width: $responsiveWidth) {
.content { .content,
flex-flow: column;
}
.columns { .columns {
flex-flow: column; flex-flow: column;
} }

View File

@@ -53,7 +53,7 @@ try {
// Write version data into a file // Write version data into a file
fs.writeFileSync( fs.writeFileSync(
PW_BUILD_DATA_DIR + "/version.json", `${PW_BUILD_DATA_DIR}/version.json`,
JSON.stringify(version) JSON.stringify(version)
); );
})(); })();

View File

@@ -17,7 +17,11 @@
<div slot="body"> <div slot="body">
<ul> <ul>
<li> <li>
<input type="text" v-model="name" :placeholder="$t('my_new_collection')" /> <input
type="text"
v-model="name"
:placeholder="$t('my_new_collection')"
/>
</li> </li>
</ul> </ul>
</div> </div>
@@ -52,6 +56,10 @@ export default {
}, },
methods: { methods: {
addNewCollection() { addNewCollection() {
if (!this.$data.name) {
this.$toast.info("Please provide a valid name for the collection");
return;
}
this.$store.commit("postwoman/addNewCollection", { this.$store.commit("postwoman/addNewCollection", {
name: this.$data.name name: this.$data.name
}); });

View File

@@ -17,7 +17,11 @@
<div slot="body"> <div slot="body">
<ul> <ul>
<li> <li>
<input type="text" v-model="name" :placeholder="$t('my_new_folder')" /> <input
type="text"
v-model="name"
:placeholder="$t('my_new_folder')"
/>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -59,6 +59,10 @@ export default {
}, },
methods: { methods: {
saveCollection() { saveCollection() {
if (!this.$data.name) {
this.$toast.info("Please provide a valid name for the collection");
return;
}
const collectionUpdated = { const collectionUpdated = {
...this.$props.editingCollection, ...this.$props.editingCollection,
name: this.$data.name name: this.$data.name

View File

@@ -11,15 +11,23 @@
</button> </button>
</div> </div>
</div> </div>
</li>
</ul>
</div>
<div slot="body">
<textarea v-model="collectionJson" rows="8"></textarea>
</div>
<div slot="footer">
<div class="flex-wrap"> <div class="flex-wrap">
<span> <span
v-tooltip="{
content: !fb.currentUser
? $t('login_first')
: $t('replace_current')
}"
>
<button
:disabled="!fb.currentUser"
class="icon"
@click="syncCollections"
>
<i class="material-icons">folder_shared</i>
<span>{{ $t("import_from_sync") }}</span>
</button>
</span>
<button <button
class="icon" class="icon"
@click="openDialogChooseFileToReplaceWith" @click="openDialogChooseFileToReplaceWith"
@@ -32,6 +40,7 @@
@change="replaceWithJSON" @change="replaceWithJSON"
style="display: none;" style="display: none;"
ref="inputChooseFileToReplaceWith" ref="inputChooseFileToReplaceWith"
accept="application/json"
/> />
</button> </button>
<button <button
@@ -39,18 +48,24 @@
@click="openDialogChooseFileToImportFrom" @click="openDialogChooseFileToImportFrom"
v-tooltip="$t('preserve_current')" v-tooltip="$t('preserve_current')"
> >
<i class="material-icons">folder_shared</i> <i class="material-icons">folder_special</i>
<span>{{ $t("import_json") }}</span> <span>{{ $t("import_json") }}</span>
<input <input
type="file" type="file"
@change="importFromJSON" @change="importFromJSON"
style="display: none;" style="display: none;"
ref="inputChooseFileToImportFrom" ref="inputChooseFileToImportFrom"
accept="application/json"
/> />
</button> </button>
</span>
<span></span>
</div> </div>
</li>
</ul>
</div>
<div slot="body">
<textarea v-model="collectionJson" rows="8"></textarea>
</div>
<div slot="footer">
<div class="flex-wrap"> <div class="flex-wrap">
<span></span> <span></span>
<span> <span>
@@ -71,7 +86,14 @@
</template> </template>
<script> <script>
import { fb } from "../../functions/fb";
export default { export default {
data() {
return {
fb
};
},
props: { props: {
show: Boolean show: Boolean
}, },
@@ -101,6 +123,7 @@ export default {
this.$store.commit("postwoman/replaceCollections", collections); this.$store.commit("postwoman/replaceCollections", collections);
}; };
reader.readAsText(this.$refs.inputChooseFileToReplaceWith.files[0]); reader.readAsText(this.$refs.inputChooseFileToReplaceWith.files[0]);
this.fileImported();
}, },
importFromJSON() { importFromJSON() {
let reader = new FileReader(); let reader = new FileReader();
@@ -110,6 +133,7 @@ export default {
this.$store.commit("postwoman/importCollections", collections); this.$store.commit("postwoman/importCollections", collections);
}; };
reader.readAsText(this.$refs.inputChooseFileToImportFrom.files[0]); reader.readAsText(this.$refs.inputChooseFileToImportFrom.files[0]);
this.fileImported();
}, },
exportJSON() { exportJSON() {
let text = this.collectionJson; let text = this.collectionJson;
@@ -125,6 +149,18 @@ export default {
document.body.appendChild(anchor); document.body.appendChild(anchor);
anchor.click(); anchor.click();
document.body.removeChild(anchor); document.body.removeChild(anchor);
this.$toast.success(this.$t("download_started"), {
icon: "done"
});
},
syncCollections() {
this.$store.commit("postwoman/replaceCollections", fb.currentCollections);
this.fileImported();
},
fileImported() {
this.$toast.info(this.$t("file_imported"), {
icon: "folder_shared"
});
} }
} }
}; };

View File

@@ -47,12 +47,8 @@ TODO:
</button> </button>
</div> </div>
<div> <div>
<button <button class="icon" @click="displayModalImportExport(true)">
class="icon" {{ $t("import_export") }}
@click="displayModalImportExport(true)"
v-tooltip="$t('import_export')"
>
<i class="material-icons">import_export</i>
</button> </button>
<!-- <a <!-- <a
href="https://github.com/liyasthomas/postwoman/wiki/Collections" href="https://github.com/liyasthomas/postwoman/wiki/Collections"
@@ -90,12 +86,18 @@ TODO:
</li> </li>
</ul> </ul>
</virtual-list> </virtual-list>
<nuxt-link :to="localePath('doc')" :aria-label="$t('documentation')">
<button class="icon">
<i class="material-icons">books</i>
<span>{{ $t("generate_docs") }}</span>
</button>
</nuxt-link>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.virtual-list { .virtual-list {
max-height: calc(100vh - 232px); max-height: calc(100vh - 276px);
} }
ul { ul {
@@ -106,6 +108,7 @@ ul {
<script> <script>
import collection from "./collection"; import collection from "./collection";
import { fb } from "../../functions/fb";
export default { export default {
components: { components: {
@@ -170,11 +173,13 @@ export default {
this.$data.editingCollection = collection; this.$data.editingCollection = collection;
this.$data.editingCollectionIndex = collectionIndex; this.$data.editingCollectionIndex = collectionIndex;
this.displayModalEdit(true); this.displayModalEdit(true);
this.syncCollections();
}, },
addFolder(collection, collectionIndex) { addFolder(collection, collectionIndex) {
this.$data.editingCollection = collection; this.$data.editingCollection = collection;
this.$data.editingCollectionIndex = collectionIndex; this.$data.editingCollectionIndex = collectionIndex;
this.displayModalAddFolder(true); this.displayModalAddFolder(true);
this.syncCollections();
}, },
editFolder(payload) { editFolder(payload) {
const { collectionIndex, folder, folderIndex } = payload; const { collectionIndex, folder, folderIndex } = payload;
@@ -183,6 +188,7 @@ export default {
this.$data.editingFolder = folder; this.$data.editingFolder = folder;
this.$data.editingFolderIndex = folderIndex; this.$data.editingFolderIndex = folderIndex;
this.displayModalEditFolder(true); this.displayModalEditFolder(true);
this.syncCollections();
}, },
editRequest(payload) { editRequest(payload) {
const { request, collectionIndex, folderIndex, requestIndex } = payload; const { request, collectionIndex, folderIndex, requestIndex } = payload;
@@ -191,6 +197,7 @@ export default {
this.$data.editingRequest = request; this.$data.editingRequest = request;
this.$data.editingRequestIndex = requestIndex; this.$data.editingRequestIndex = requestIndex;
this.displayModalEditRequest(true); this.displayModalEditRequest(true);
this.syncCollections();
}, },
resetSelectedData() { resetSelectedData() {
this.$data.editingCollection = undefined; this.$data.editingCollection = undefined;
@@ -199,6 +206,15 @@ export default {
this.$data.editingFolderIndex = undefined; this.$data.editingFolderIndex = undefined;
this.$data.editingRequest = undefined; this.$data.editingRequest = undefined;
this.$data.editingRequestIndex = undefined; this.$data.editingRequestIndex = undefined;
},
syncCollections() {
if (fb.currentUser !== null) {
if (fb.currentSettings[0].value) {
fb.writeCollections(
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections))
);
}
}
} }
} }
}; };

View File

@@ -1,7 +1,11 @@
<template> <template>
<div class="flex-wrap"> <div class="flex-wrap">
<div> <div>
<button class="icon" @click="selectRequest()" v-tooltip="$t('use_request')"> <button
class="icon"
@click="selectRequest()"
v-tooltip="$t('use_request')"
>
<i class="material-icons">insert_drive_file</i> <i class="material-icons">insert_drive_file</i>
<span>{{ request.name }}</span> <span>{{ request.name }}</span>
</button> </button>

View File

@@ -0,0 +1,85 @@
<template>
<virtual-list
v-if="fb.currentFeeds.length !== 0"
class="virtual-list"
:class="{ filled: fb.currentFeeds.length }"
:size="56"
:remain="Math.min(8, fb.currentFeeds.length)"
>
<ul v-for="feed in fb.currentFeeds" :key="feed.id">
<div class="show-on-large-screen">
<li>
<input
:aria-label="$t('label')"
type="text"
readonly
:value="feed.label"
:placeholder="$t('no_label')"
class="bg-color"
/>
</li>
<button class="icon" @click="saveFeed(feed)">
<i class="material-icons">get_app</i>
</button>
<button class="icon" @click="deleteFeed(feed)">
<i class="material-icons">delete</i>
</button>
</div>
</ul>
</virtual-list>
<ul v-else>
<li>
<label class="info">{{ $t("empty") }}</label>
</li>
</ul>
</template>
<style scoped lang="scss">
.virtual-list {
max-height: calc(100vh - 288px);
}
.bg-color {
background-color: transparent;
}
</style>
<script>
import { fb } from "../../functions/fb";
export default {
components: {
VirtualList: () => import("vue-virtual-scroll-list")
},
data() {
return {
fb
};
},
methods: {
deleteFeed(feed) {
fb.deleteFeed(feed.id);
this.$toast.error(this.$t("deleted"), {
icon: "delete"
});
},
saveFeed(feed) {
const dataToWrite = JSON.stringify(feed.message, null, 2);
const file = new Blob([dataToWrite], { type: "application/json" });
const a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = (feed.label + " on " + Date()).replace(/\./g, "[dot]");
document.body.appendChild(a);
a.click();
this.$toast.success(this.$t("download_started"), {
icon: "done"
});
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 1000);
}
}
};
</script>

View File

@@ -0,0 +1,55 @@
<template>
<div>
<ul>
<li>
<input
:aria-label="$t('label')"
type="text"
autofocus
v-model="message"
:placeholder="$t('paste_a_collection')"
/>
</li>
</ul>
<ul>
<li>
<input
:aria-label="$t('label')"
type="text"
autofocus
v-model="label"
:placeholder="$t('label')"
/>
</li>
<button
class="icon"
:disabled="!(this.message && this.label)"
value="Save"
@click="formPost"
>
<i class="material-icons">add</i>
<span>Add</span>
</button>
</ul>
</div>
</template>
<script>
import { fb } from "../../functions/fb";
export default {
data() {
return {
message: null,
label: null
};
},
methods: {
formPost() {
fb.writeFeeds(this.message, this.label);
this.message = null;
this.label = null;
}
}
};
</script>

View File

@@ -0,0 +1,115 @@
<template>
<v-popover>
<button class="icon" v-tooltip="$t('login_with')">
<i class="material-icons">vpn_key</i>
</button>
<template slot="popover">
<div>
<button class="icon" @click="signInWithGoogle" v-close-popover>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="material-icons"
>
<path
d="M12.24 10.285V14.4h6.806c-.275 1.765-2.056 5.174-6.806 5.174-4.095 0-7.439-3.389-7.439-7.574s3.345-7.574 7.439-7.574c2.33 0 3.891.989 4.785 1.849l3.254-3.138C18.189 1.186 15.479 0 12.24 0c-6.635 0-12 5.365-12 12s5.365 12 12 12c6.926 0 11.52-4.869 11.52-11.726 0-.788-.085-1.39-.189-1.989H12.24z"
/>
</svg>
<span>Google</span>
</button>
</div>
<div>
<button class="icon" @click="signInWithGithub" v-close-popover>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="material-icons"
>
<path
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
/>
</svg>
<span>GitHub</span>
</button>
</div>
</template>
</v-popover>
</template>
<script>
import firebase from "firebase/app";
import { fb } from "../../functions/fb";
export default {
data() {
return {
fb
};
},
methods: {
signInWithGoogle() {
const provider = new firebase.auth.GoogleAuthProvider();
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
icon: "sync",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
onClick: (e, toastObject) => {
fb.writeSettings("syncHistory", false);
fb.writeSettings("syncCollections", true);
this.$router.push({ path: "/settings" });
toastObject.remove();
}
}
});
}
})
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
},
signInWithGithub() {
const provider = new firebase.auth.GithubAuthProvider();
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
icon: "sync",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
onClick: (e, toastObject) => {
fb.writeSettings("syncHistory", false);
fb.writeSettings("syncCollections", true);
this.$router.push({ path: "/settings" });
toastObject.remove();
}
}
});
}
})
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
}
}
};
</script>

View File

@@ -0,0 +1,135 @@
<template>
<pre ref="editor"></pre>
</template>
<script>
const DEFAULT_THEME = "twilight";
import ace from "ace-builds";
import * as gql from "graphql";
import "ace-builds/webpack-resolver";
import debounce from "../../functions/utils/debounce";
export default {
props: {
value: {
type: String,
default: ""
},
theme: {
type: String,
required: false
},
lang: {
type: String,
default: "json"
},
options: {
type: Object,
default: {}
}
},
data() {
return {
editor: null,
cacheValue: "",
validationSchema: null
};
},
watch: {
value(value) {
if (value !== this.cacheValue) {
this.editor.session.setValue(value, 1);
this.cacheValue = value;
}
},
theme() {
this.editor.setTheme("ace/theme/" + this.defineTheme());
},
lang(value) {
this.editor.getSession().setMode("ace/mode/" + value);
},
options(value) {
this.editor.setOptions(value);
}
},
mounted() {
const editor = ace.edit(this.$refs.editor, {
theme: "ace/theme/" + this.defineTheme(),
mode: "ace/mode/" + this.lang,
...this.options
});
if (this.value) editor.setValue(this.value, 1);
this.editor = editor;
this.cacheValue = this.value;
editor.on("change", () => {
const content = editor.getValue();
this.$emit("input", content);
this.parseContents(content);
this.cacheValue = content;
});
this.parseContents(this.value);
},
methods: {
defineTheme() {
if (this.theme) {
return this.theme;
} else {
return (
this.$store.state.postwoman.settings.THEME_ACE_EDITOR || DEFAULT_THEME
);
}
},
setValidationSchema(schema) {
this.validationSchema = schema;
this.parseContents(this.cacheValue);
},
parseContents: debounce(function(content) {
if (content !== "") {
try {
const doc = gql.parse(content);
if (this.validationSchema) {
this.editor.session.setAnnotations(
gql.validate(this.validationSchema, doc).map(err => {
return {
row: err.locations[0].line - 1,
column: err.locations[0].column - 1,
text: err.message,
type: "error"
};
})
);
}
} catch (e) {
this.editor.session.setAnnotations([
{
row: e.locations[0].line - 1,
column: e.locations[0].column - 1,
text: e.message,
type: "error"
}
]);
}
} else {
this.editor.session.setAnnotations([]);
}
}, 2000)
},
beforeDestroy() {
this.editor.destroy();
this.editor.container.remove();
}
};
</script>

View File

@@ -1,10 +1,5 @@
<template> <template>
<pw-section <pw-section class="green" icon="history" :label="$t('history')" ref="history">
class="green"
icon="history"
:label="$t('history')"
ref="history"
>
<ul> <ul>
<li id="filter-history"> <li id="filter-history">
<input <input
@@ -26,8 +21,10 @@
<button <button
class="icon" class="icon"
:class="{ stared: entry.star }" :class="{ stared: entry.star }"
@click="toggleStar(index)" @click="toggleStar(entry)"
v-tooltip="{ content: !entry.star ? $t('add_star') : $t('remove_star') }" v-tooltip="{
content: !entry.star ? $t('add_star') : $t('remove_star')
}"
> >
<i class="material-icons"> <i class="material-icons">
{{ entry.star ? "star" : "star_border" }} {{ entry.star ? "star" : "star_border" }}
@@ -238,7 +235,9 @@
</v-popover> </v-popover>
</div> </div>
<div class="flex-wrap" v-else> <div class="flex-wrap" v-else>
<label for="clear-history-button" class="info">{{ $t("are_you_sure") }}</label> <label for="clear-history-button" class="info">
{{ $t("are_you_sure") }}
</label>
<div> <div>
<button <button
class="icon" class="icon"
@@ -332,6 +331,7 @@ ol li {
<script> <script>
import { findStatusGroup } from "../pages/index"; import { findStatusGroup } from "../pages/index";
import { fb } from "../functions/fb";
const updateOnLocalStorage = (propertyName, property) => const updateOnLocalStorage = (propertyName, property) =>
window.localStorage.setItem(propertyName, JSON.stringify(property)); window.localStorage.setItem(propertyName, JSON.stringify(property));
@@ -342,11 +342,11 @@ export default {
VirtualList: () => import("vue-virtual-scroll-list") VirtualList: () => import("vue-virtual-scroll-list")
}, },
data() { data() {
const localStorageHistory = JSON.parse(
window.localStorage.getItem("history")
);
return { return {
history: localStorageHistory || [], history:
fb.currentUser !== null
? fb.currentHistory
: JSON.parse(window.localStorage.getItem("history")) || [],
filterText: "", filterText: "",
showFilter: false, showFilter: false,
isClearingHistory: false, isClearingHistory: false,
@@ -360,6 +360,10 @@ export default {
}, },
computed: { computed: {
filteredHistory() { filteredHistory() {
this.history =
fb.currentUser !== null
? fb.currentHistory
: JSON.parse(window.localStorage.getItem("history")) || [];
return this.history.filter(entry => { return this.history.filter(entry => {
const filterText = this.filterText.toLowerCase(); const filterText = this.filterText.toLowerCase();
return Object.keys(entry).some(key => { return Object.keys(entry).some(key => {
@@ -372,6 +376,9 @@ export default {
}, },
methods: { methods: {
clearHistory() { clearHistory() {
if (fb.currentUser !== null) {
fb.clearHistory();
}
this.history = []; this.history = [];
this.filterText = ""; this.filterText = "";
this.disableHistoryClearing(); this.disableHistoryClearing();
@@ -392,6 +399,9 @@ export default {
); );
}, },
deleteHistory(entry) { deleteHistory(entry) {
if (fb.currentUser !== null) {
fb.deleteHistory(entry);
}
this.history.splice(this.history.indexOf(entry), 1); this.history.splice(this.history.indexOf(entry), 1);
if (this.history.length === 0) { if (this.history.length === 0) {
this.filterText = ""; this.filterText = "";
@@ -498,8 +508,11 @@ export default {
toggleCollapse() { toggleCollapse() {
this.showMore = !this.showMore; this.showMore = !this.showMore;
}, },
toggleStar(index) { toggleStar(entry) {
this.history[index]["star"] = !this.history[index]["star"]; if (fb.currentUser !== null) {
fb.toggleStar(entry, !entry.star);
}
entry.star = !entry.star;
updateOnLocalStorage("history", this.history); updateOnLocalStorage("history", this.history);
} }
} }

View File

@@ -1,7 +1,14 @@
service cloud.firestore { service cloud.firestore {
match /databases/{database}/documents { match /databases/{database}/documents {
match /{document=**} { match /{document=**} {
allow read, write; allow read, write: if request.auth.uid != null;
}
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
} }
} }
} }

192
functions/fb.js Normal file
View File

@@ -0,0 +1,192 @@
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/auth";
// Initialize Firebase, copied from cloud console
const firebaseConfig = {
apiKey: "AIzaSyCMsFreESs58-hRxTtiqQrIcimh4i1wbsM",
authDomain: "postwoman-api.firebaseapp.com",
databaseURL: "https://postwoman-api.firebaseio.com",
projectId: "postwoman-api",
storageBucket: "postwoman-api.appspot.com",
messagingSenderId: "421993993223",
appId: "1:421993993223:web:ec0baa8ee8c02ffa1fc6a2",
measurementId: "G-ERJ6025CEB"
};
firebase.initializeApp(firebaseConfig);
// a reference to the users collection
const usersCollection = firebase.firestore().collection("users");
// the shared state object that any vue component
// can get access to
export const fb = {
currentUser: {},
currentFeeds: [],
currentSettings: [],
currentHistory: [],
currentCollections: [],
writeFeeds: async (message, label) => {
const dt = {
createdOn: new Date(),
author: fb.currentUser.uid,
author_name: fb.currentUser.displayName,
author_image: fb.currentUser.photoURL,
message,
label
};
usersCollection
.doc(fb.currentUser.uid)
.collection("feeds")
.add(dt)
.catch(e => console.error("error inserting", dt, e));
},
deleteFeed: id => {
usersCollection
.doc(fb.currentUser.uid)
.collection("feeds")
.doc(id)
.delete()
.catch(e => console.error("error deleting", id, e));
},
writeSettings: async (setting, value) => {
const st = {
updatedOn: new Date(),
author: fb.currentUser.uid,
author_name: fb.currentUser.displayName,
author_image: fb.currentUser.photoURL,
name: setting,
value
};
usersCollection
.doc(fb.currentUser.uid)
.collection("settings")
.doc(setting)
.set(st)
.catch(e => console.error("error updating", st, e));
},
writeHistory: async entry => {
const hs = entry;
usersCollection
.doc(fb.currentUser.uid)
.collection("history")
.add(hs)
.catch(e => console.error("error inserting", hs, e));
},
deleteHistory: entry => {
usersCollection
.doc(fb.currentUser.uid)
.collection("history")
.doc(entry.id)
.delete()
.catch(e => console.error("error deleting", entry, e));
},
clearHistory: () => {
usersCollection
.doc(fb.currentUser.uid)
.collection("history")
.get()
.then(({ docs }) => {
docs.forEach(e => fb.deleteHistory(e));
});
},
toggleStar: (entry, value) => {
usersCollection
.doc(fb.currentUser.uid)
.collection("history")
.doc(entry.id)
.update({ star: value })
.catch(e => console.error("error deleting", entry, e));
},
writeCollections: async collection => {
const cl = {
updatedOn: new Date(),
author: fb.currentUser.uid,
author_name: fb.currentUser.displayName,
author_image: fb.currentUser.photoURL,
collection: collection
};
usersCollection
.doc(fb.currentUser.uid)
.collection("collections")
.doc("sync")
.set(cl)
.catch(e => console.error("error updating", cl, e));
}
};
// When a user logs in or out, save that in the store
firebase.auth().onAuthStateChanged(user => {
if (user) {
fb.currentUser = user;
fb.currentUser.providerData.forEach(profile => {
let us = {
updatedOn: new Date(),
provider: profile.providerId,
name: profile.displayName,
email: profile.email,
photoUrl: profile.photoURL,
uid: profile.uid
};
usersCollection
.doc(fb.currentUser.uid)
.set(us)
.catch(e => console.error("error updating", us, e));
});
usersCollection
.doc(fb.currentUser.uid)
.collection("feeds")
.orderBy("createdOn", "desc")
.onSnapshot(feedsRef => {
const feeds = [];
feedsRef.forEach(doc => {
const feed = doc.data();
feed.id = doc.id;
feeds.push(feed);
});
fb.currentFeeds = feeds;
});
usersCollection
.doc(fb.currentUser.uid)
.collection("settings")
.onSnapshot(settingsRef => {
const settings = [];
settingsRef.forEach(doc => {
const setting = doc.data();
setting.id = doc.id;
settings.push(setting);
});
fb.currentSettings = settings;
});
usersCollection
.doc(fb.currentUser.uid)
.collection("history")
.onSnapshot(historyRef => {
const history = [];
historyRef.forEach(doc => {
const entry = doc.data();
entry.id = doc.id;
history.push(entry);
});
fb.currentHistory = history;
});
usersCollection
.doc(fb.currentUser.uid)
.collection("collections")
.onSnapshot(collectionsRef => {
const collections = [];
collectionsRef.forEach(doc => {
const collection = doc.data();
collection.id = doc.id;
collections.push(collection);
});
fb.currentCollections = collections[0].collection;
});
} else {
fb.currentUser = null;
}
});

24
functions/network.js Normal file
View File

@@ -0,0 +1,24 @@
import AxiosStrategy from "./strategies/AxiosStrategy";
import FirefoxStrategy from "./strategies/FirefoxStrategy";
import ChromeStrategy, { hasChromeExtensionInstalled } from "./strategies/ChromeStrategy";
const runAppropriateStrategy = (req, store) => {
// Chrome Provides a chrome object for scripts to access
// Check its availability to say whether you are in Google Chrome
if (window.chrome && hasChromeExtensionInstalled()) {
return ChromeStrategy(req, store);
}
// The firefox plugin injects a function to send requests through it
// If that is available, then we can use the FirefoxStrategy
if (window.firefoxExtSendRequest) {
return FirefoxStrategy(req, store);
}
return AxiosStrategy(req, store);
}
const sendNetworkRequest = (req, store) =>
runAppropriateStrategy(req, store)
.finally(() => window.$nuxt.$loading.finish());
export { sendNetworkRequest };

View File

@@ -0,0 +1,23 @@
import axios from "axios";
const axiosWithProxy = async (req, { state }) => {
const { data } = await axios.post(
state.postwoman.settings.PROXY_URL || "https://postwoman.apollotv.xyz/",
req
);
return data;
};
const axiosWithoutProxy = async (req, _store) => {
const res = await axios(req);
return res;
};
const axiosStrategy = (req, store) => {
if (store.state.postwoman.settings.PROXY_ENABLED) {
return axiosWithProxy(req, store);
}
return axiosWithoutProxy(req, store);
};
export default axiosStrategy;

View File

@@ -0,0 +1,56 @@
const EXTENSION_ID = "amknoiejhlmhancpahfcfcfhllgkpbld";
// Check if the Chrome Extension is present
// The Chrome extension injects an empty span to help detection.
// Also check for the presence of window.chrome object to confirm smooth operations
export const hasChromeExtensionInstalled = () => {
return document.getElementById("chromePWExtensionDetect") !== null;
}
const chromeWithoutProxy = (req, _store) => new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
EXTENSION_ID, {
messageType: "send-req",
data: {
config: req
}
}, (message) => {
if (message.data.error) {
reject(message.data.error);
} else {
resolve(message.data.response);
}
}
);
});
const chromeWithProxy = (req, { state }) => new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
EXTENSION_ID, {
messageType: "send-req",
data: {
config: {
method: "post",
url: state.postwoman.settings.PROXY_URL || "https://postwoman.apollotv.xyz/",
data: req
}
}
}, (message) => {
if (message.data.error) {
reject(error);
} else {
resolve(message.data.response.data);
}
}
)
});
const chromeStrategy = (req, store) => {
if (store.state.postwoman.settings.PROXY_ENABLED) {
return chromeWithProxy(req, store);
} else {
return chromeWithoutProxy(req, store);
}
}
export default chromeStrategy;

View File

@@ -0,0 +1,50 @@
const firefoxWithProxy = (req, { state }) =>
new Promise((resolve, reject) => {
const eventListener = event => {
window.removeEventListener("firefoxExtSendRequestComplete", event);
if (event.detail.error) {
reject(JSON.parse(event.detail.error));
} else {
resolve(JSON.parse(event.detail.response).data);
}
};
window.addEventListener("firefoxExtSendRequestComplete", eventListener);
window.firefoxExtSendRequest({
method: "post",
url:
state.postwoman.settings.PROXY_URL || "https://postwoman.apollotv.xyz/",
data: req
});
});
const firefoxWithoutProxy = (req, _store) =>
new Promise((resolve, reject) => {
const eventListener = ({ detail }) => {
window.removeEventListener(
"firefoxExtSendRequestComplete",
eventListener
);
if (detail.error) {
reject(JSON.parse(detail.error));
} else {
resolve(JSON.parse(detail.response));
}
};
window.addEventListener("firefoxExtSendRequestComplete", eventListener);
window.firefoxExtSendRequest(req);
});
const firefoxStrategy = (req, store) => {
if (store.state.postwoman.settings.PROXY_ENABLED) {
return firefoxWithProxy(req, store);
}
return firefoxWithoutProxy(req, store);
};
export default firefoxStrategy;

View File

@@ -0,0 +1,15 @@
// Debounce is a higher order function which makes its enclosed function be executed
// only if the function wasn't called again till 'delay' time has passed, this helps reduce impact of heavy working
// functions which might be called frequently
// NOTE : Don't use lambda functions as this doesn't get bound properly in them, use the 'function (args) {}' format
const debounce = (func, delay) => {
let inDebounce;
return function() {
const context = this;
const args = arguments;
clearTimeout(inDebounce);
inDebounce = setTimeout(() => func.apply(context, args), delay);
};
};
export default debounce;

View File

@@ -154,7 +154,8 @@ export default {
disconnected_from: "Disconnected from {name}", disconnected_from: "Disconnected from {name}",
something_went_wrong: "Something went wrong!", something_went_wrong: "Something went wrong!",
error_occurred: "An error has occurred.", error_occurred: "An error has occurred.",
browser_support_sse: "This browser doesn't seems to have Server Sent Events support.", browser_support_sse:
"This browser doesn't seems to have Server Sent Events support.",
log: "Log", log: "Log",
no_url: "No URL", no_url: "No URL",
run_query: "Run Query", run_query: "Run Query",
@@ -207,7 +208,8 @@ export default {
value_count: "value {count}", value_count: "value {count}",
send_request_first: "Send a request first", send_request_first: "Send a request first",
generate_docs: "Generate Documentation", generate_docs: "Generate Documentation",
generate_docs_message: "Import any Postwoman Collection to Generate Documentation on-the-go.", generate_docs_message:
"Import any Postwoman Collection to Generate Documentation on-the-go.",
generate_docs_first: "Generate documentation first", generate_docs_first: "Generate documentation first",
docs_generated: "Documentation generated", docs_generated: "Documentation generated",
import_collections: "Import collections", import_collections: "Import collections",
@@ -231,8 +233,10 @@ export default {
enable_proxy: "Try enabling Proxy", enable_proxy: "Try enabling Proxy",
complete_config_urls: "Please complete configuration urls.", complete_config_urls: "Please complete configuration urls.",
token_request_saved: "Token request saved", token_request_saved: "Token request saved",
donate_info1: "If you have enjoyed the productivity of using Postwoman, consider donating as a sign of appreciation.", donate_info1:
donate_info2: "You can support Postwoman development via the following methods:", "If you have enjoyed the productivity of using Postwoman, consider donating as a sign of appreciation.",
donate_info2:
"You can support Postwoman development via the following methods:",
one_time_recurring: "One-time or recurring", one_time_recurring: "One-time or recurring",
one_time: "One-time", one_time: "One-time",
recurring: "Recurring", recurring: "Recurring",
@@ -241,5 +245,20 @@ export default {
go_home: "Go Home", go_home: "Go Home",
reload: "Reload", reload: "Reload",
enter_curl: "Enter cURL", enter_curl: "Enter cURL",
empty: "Empty" empty: "Empty",
extensions: "Extensions",
extensions_info1: "Browser extension that simplifies access to Postwoman",
extensions_info2: "Get Postwoman browser extension!",
installed: "Installed",
login_with: "Login with",
logged_out: "Logged out",
logout: "Logout",
account: "Account",
sync: "Sync",
syncHistory: "History",
syncCollections: "Collections",
turn_on: "Turn on",
login_first: "Login first",
paste_a_collection: "Paste a Collection",
import_from_sync: "Import from Sync"
}; };

View File

@@ -237,6 +237,11 @@
<div v-else-if="$route.path === '/settings'"> <div v-else-if="$route.path === '/settings'">
<nav class="secondary-nav"> <nav class="secondary-nav">
<ul> <ul>
<li>
<a href="#account" v-tooltip.right="$t('account')">
<i class="material-icons">person</i>
</a>
</li>
<li> <li>
<a href="#theme" v-tooltip.right="$t('theme')"> <a href="#theme" v-tooltip.right="$t('theme')">
<i class="material-icons">brush</i> <i class="material-icons">brush</i>
@@ -288,11 +293,61 @@
> >
<i class="material-icons">offline_bolt</i> <i class="material-icons">offline_bolt</i>
</button> </button>
<login v-if="!fb.currentUser" />
<span v-if="fb.currentUser">
<v-popover>
<button
class="icon"
v-tooltip="
(fb.currentUser.displayName ||
'<label><i>Name not found</i></label>') +
'<br>' +
(fb.currentUser.email ||
'<label><i>Email not found</i></label>')
"
>
<img
v-if="fb.currentUser.photoURL"
:src="fb.currentUser.photoURL"
class="material-icons"
/>
<i v-else class="material-icons">account_circle</i>
</button>
<template slot="popover">
<div>
<nuxt-link :to="localePath('settings')" v-close-popover>
<button class="icon">
<i class="material-icons">settings</i>
<span>
{{ $t("settings") }}
</span>
</button>
</nuxt-link>
</div>
<div>
<button class="icon" @click="logout" v-close-popover>
<i class="material-icons">exit_to_app</i>
<span>{{ $t("logout") }}</span>
</button>
</div>
</template>
</v-popover>
</span>
<v-popover> <v-popover>
<button class="icon" v-tooltip="$t('more')"> <button class="icon" v-tooltip="$t('more')">
<i class="material-icons">more_vert</i> <i class="material-icons">more_vert</i>
</button> </button>
<template slot="popover"> <template slot="popover">
<div>
<button
class="icon"
@click="showExtensions = true"
v-close-popover
>
<i class="material-icons">extension</i>
<span>{{ $t("extensions") }}</span>
</button>
</div>
<div> <div>
<button <button
class="icon" class="icon"
@@ -342,15 +397,7 @@
<div class="flex-wrap"> <div class="flex-wrap">
<span v-if="version.name" class="mono"> <span v-if="version.name" class="mono">
<a <a
href="https://liyasthomas.web.app" class="link"
target="_blank"
rel="noopener"
>
<button class="icon" v-tooltip="'Liyas Thomas'">
🦄
</button>
</a>
<a
:href=" :href="
'https://github.com/liyasthomas/postwoman/releases/tag/' + 'https://github.com/liyasthomas/postwoman/releases/tag/' +
version.name version.name
@@ -361,6 +408,14 @@
> >
{{ version.name }} {{ version.name }}
</a> </a>
<a
class="link"
href="https://www.netlify.com"
target="_blank"
rel="noopener"
>
Powered by Netlify
</a>
<!-- <span v-if="version.hash"> <!-- <span v-if="version.hash">
- -
<a <a
@@ -372,6 +427,15 @@
<!-- <span v-if="version.variant">({{version.variant}})</span> --> <!-- <span v-if="version.variant">({{version.variant}})</span> -->
</span> </span>
<span> <span>
<a
href="https://liyasthomas.web.app"
target="_blank"
rel="noopener"
>
<button class="icon" v-tooltip="'Liyas Thomas'">
🦄
</button>
</a>
<a <a
href="mailto:liyascthomas@gmail.com" href="mailto:liyascthomas@gmail.com"
target="_blank" target="_blank"
@@ -402,6 +466,86 @@
<aside class="nav-second"></aside> <aside class="nav-second"></aside>
</div> </div>
</div> </div>
<modal v-if="showExtensions" @close="showExtensions = false">
<div slot="header">
<ul>
<li>
<div class="flex-wrap">
<h3 class="title">{{ $t("extensions") }}</h3>
<div>
<button class="icon" @click="showExtensions = false">
<i class="material-icons">close</i>
</button>
</div>
</div>
</li>
</ul>
</div>
<div slot="body">
<p class="info">
{{ $t("extensions_info1") }}
</p>
<div>
<a
href="https://addons.mozilla.org/en-US/firefox/addon/postwoman"
target="_blank"
rel="noopener"
>
<button class="icon">
<svg
class="material-icons"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm8.003 8.657c-1.276-3.321-4.46-4.605-5.534-4.537 3.529 1.376 4.373 6.059 4.06 7.441-.307-1.621-1.286-3.017-1.872-3.385 3.417 8.005-4.835 10.465-7.353 7.687.649.168 1.931.085 2.891-.557.898-.602.983-.638 1.56-.683.686-.053-.041-1.406-1.539-1.177-.616.094-1.632.819-2.88.341-1.508-.576-1.46-2.634.096-2.015.337-.437.088-1.263.088-1.263.452-.414 1.022-.706 1.37-.911.228-.135.829-.507.795-1.23-.123-.096-.32-.219-.766-.193-1.736.11-1.852-.518-1.967-.808.078-.668.524-1.534 1.361-1.931-1.257-.193-2.28.397-2.789 1.154-.809-.174-1.305-.183-2.118-.031-.316-.24-.666-.67-.878-1.181 1.832-2.066 4.499-3.378 7.472-3.378 5.912 0 8.263 4.283 8.003 6.657z"
/>
</svg>
<span>Firefox</span>
<span
class="icon"
v-if="firefoxExtInstalled"
v-tooltip="$t('installed')"
>
<i class="material-icons">done</i>
</span>
</button>
</a>
</div>
<div>
<a
href="https://chrome.google.com/webstore/detail/postwoman-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld"
target="_blank"
rel="noopener"
>
<button class="icon">
<svg
class="material-icons"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
d="M2.897 4.181c2.43-2.828 5.763-4.181 9.072-4.181 4.288 0 8.535 2.273 10.717 6.554-2.722.001-6.984 0-9.293 0-1.674.001-2.755-.037-3.926.579-1.376.724-2.415 2.067-2.777 3.644l-3.793-6.596zm5.11 7.819c0 2.2 1.789 3.99 3.988 3.99s3.988-1.79 3.988-3.99-1.789-3.991-3.988-3.991-3.988 1.791-3.988 3.991zm5.536 5.223c-2.238.666-4.858-.073-6.293-2.549-1.095-1.891-3.989-6.933-5.305-9.225-1.33 2.04-1.945 4.294-1.945 6.507 0 5.448 3.726 10.65 9.673 11.818l3.87-6.551zm2.158-9.214c1.864 1.734 2.271 4.542 1.007 6.719-.951 1.641-3.988 6.766-5.46 9.248 7.189.443 12.752-5.36 12.752-11.972 0-1.313-.22-2.66-.69-3.995h-7.609z"
/>
</svg>
<span>Chrome</span>
<span
class="icon"
v-if="chromeExtInstalled"
v-tooltip="$t('installed')"
>
<i class="material-icons">done</i>
</span>
</button>
</a>
</div>
</div>
<div slot="footer"></div>
</modal>
<modal v-if="showShortcuts" @close="showShortcuts = false"> <modal v-if="showShortcuts" @close="showShortcuts = false">
<div slot="header"> <div slot="header">
<ul> <ul>
@@ -504,16 +648,30 @@
</div> </div>
</template> </template>
<style scoped lang="scss"></style> <style scoped lang="scss">
.link {
margin: 8px 16px;
}
@media (max-width: 768px) {
.link {
display: flex;
flex: 1;
}
}
</style>
<script> <script>
import intializePwa from "../assets/js/pwa"; import intializePwa from "../assets/js/pwa";
import * as version from "../.postwoman/version.json"; import * as version from "../.postwoman/version.json";
import { hasChromeExtensionInstalled } from "../functions/strategies/ChromeStrategy";
import firebase from "firebase/app";
import { fb } from "../functions/fb";
export default { export default {
components: { components: {
logo: () => import("../components/logo"), logo: () => import("../components/logo"),
modal: () => import("../components/modal") modal: () => import("../components/modal"),
login: () => import("../components/firebase/login")
}, },
methods: { methods: {
@@ -522,6 +680,21 @@ export default {
"nuxt-link-exact-active": this.$route.path === path, "nuxt-link-exact-active": this.$route.path === path,
"nuxt-link-active": this.$route.path === path "nuxt-link-active": this.$route.path === path
}; };
},
logout() {
fb.currentUser = null;
firebase
.auth()
.signOut()
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
this.$toast.info(this.$t("logged_out"), {
icon: "vpn_key"
});
} }
}, },
@@ -532,8 +705,12 @@ export default {
// prompt. // prompt.
showInstallPrompt: null, showInstallPrompt: null,
version: {}, version: {},
showExtensions: false,
showShortcuts: false, showShortcuts: false,
showSupport: false showSupport: false,
firefoxExtInstalled: window.firefoxExtSendRequest,
chromeExtInstalled: window.chrome && hasChromeExtensionInstalled(),
fb
}; };
}, },
@@ -591,6 +768,37 @@ export default {
] ]
}); });
} }
let showExtensionsToast =
localStorage.getItem("showExtensionsToast") === "yes";
if (
!this.firefoxExtInstalled &&
!this.chromeExtInstalled &&
!showExtensionsToast
) {
setTimeout(() => {
this.$toast.show(this.$t("extensions_info2"), {
icon: "extension",
duration: 5000,
theme: "toasted-primary",
action: [
{
text: this.$t("yes"),
onClick: (e, toastObject) => {
this.showExtensions = true;
localStorage.setItem("showExtensionsToast", "yes");
toastObject.goAway(0);
}
},
{
text: this.$t("no"),
onClick: (e, toastObject) => {
toastObject.goAway(0);
}
}
]
});
}, 15000);
}
})(); })();
window.addEventListener("scroll", event => { window.addEventListener("scroll", event => {
@@ -623,7 +831,7 @@ export default {
watch: { watch: {
$route() { $route() {
this.$toast.clear(); // this.$toast.clear();
} }
}, },

View File

@@ -1,6 +1,10 @@
<template> <template>
<div class="page page-error"> <div class="page page-error">
<img src="~static/icons/error.svg" :alt="$t('error')" class="error_banner" /> <img
src="~static/icons/error.svg"
:alt="$t('error')"
class="error_banner"
/>
<h2>{{ error.statusCode }}</h2> <h2>{{ error.statusCode }}</h2>
<h3>{{ error.message }}</h3> <h3>{{ error.message }}</h3>
<p> <p>

1104
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,27 +18,29 @@
"test": "start-server-and-test dev http://localhost:3000 e2e" "test": "start-server-and-test dev http://localhost:3000 e2e"
}, },
"dependencies": { "dependencies": {
"@nuxtjs/axios": "^5.9.2", "@nuxtjs/axios": "^5.9.3",
"@nuxtjs/google-analytics": "^2.2.3", "@nuxtjs/google-analytics": "^2.2.3",
"@nuxtjs/google-tag-manager": "^2.3.1", "@nuxtjs/google-tag-manager": "^2.3.1",
"@nuxtjs/pwa": "^3.0.0-beta.19", "@nuxtjs/pwa": "^3.0.0-beta.19",
"@nuxtjs/robots": "^2.4.2", "@nuxtjs/robots": "^2.4.2",
"@nuxtjs/sitemap": "^2.0.1", "@nuxtjs/sitemap": "^2.0.1",
"@nuxtjs/toast": "^3.3.0", "@nuxtjs/toast": "^3.3.0",
"ace-builds": "^1.4.7", "ace-builds": "^1.4.8",
"firebase": "^7.7.0",
"graphql": "^14.5.8", "graphql": "^14.5.8",
"nuxt": "^2.11.0", "nuxt": "^2.11.0",
"nuxt-i18n": "^6.4.1", "nuxt-i18n": "^6.5.0",
"v-tooltip": "^2.0.2", "v-tooltip": "^2.0.3",
"vue-virtual-scroll-list": "^1.4.4", "vue-virtual-scroll-list": "^1.4.4",
"vuefire": "^2.2.1",
"vuejs-auto-complete": "^0.9.0", "vuejs-auto-complete": "^0.9.0",
"vuex-persist": "^2.2.0", "vuex-persist": "^2.2.0",
"yargs-parser": "^16.1.0" "yargs-parser": "^16.1.0"
}, },
"devDependencies": { "devDependencies": {
"cypress": "^3.8.2", "cypress": "^3.8.3",
"node-sass": "^4.13.0", "node-sass": "^4.13.1",
"sass-loader": "^8.0.1", "sass-loader": "^8.0.2",
"start-server-and-test": "^1.10.6" "start-server-and-test": "^1.10.6"
} }
} }

View File

@@ -93,12 +93,14 @@
</p> </p>
<p class="doc-desc" v-if="request.path"> <p class="doc-desc" v-if="request.path">
<span> <span>
{{ $t("path") }}: <code>{{ request.path || $t("none") }}</code> {{ $t("path") }}:
<code>{{ request.path || $t("none") }}</code>
</span> </span>
</p> </p>
<p class="doc-desc" v-if="request.method"> <p class="doc-desc" v-if="request.method">
<span> <span>
{{ $t("method") }}: <code>{{ request.method || $t("none") }}</code> {{ $t("method") }}:
<code>{{ request.method || $t("none") }}</code>
</span> </span>
</p> </p>
<p class="doc-desc" v-if="request.auth"> <p class="doc-desc" v-if="request.auth">
@@ -109,7 +111,8 @@
</p> </p>
<p class="doc-desc" v-if="request.httpUser"> <p class="doc-desc" v-if="request.httpUser">
<span> <span>
{{ $t("username") }}: <code>{{ request.httpUser || $t("none") }}</code> {{ $t("username") }}:
<code>{{ request.httpUser || $t("none") }}</code>
</span> </span>
</p> </p>
<p class="doc-desc" v-if="request.httpPassword"> <p class="doc-desc" v-if="request.httpPassword">
@@ -120,16 +123,17 @@
</p> </p>
<p class="doc-desc" v-if="request.bearerToken"> <p class="doc-desc" v-if="request.bearerToken">
<span> <span>
{{ $t("token") }}: <code>{{ request.bearerToken || $t("none") }}</code> {{ $t("token") }}:
<code>{{ request.bearerToken || $t("none") }}</code>
</span> </span>
</p> </p>
<h4 v-if="request.headers.length > 0">{{ $t("headers") }}</h4> <h4 v-if="request.headers.length > 0">{{ $t("headers") }}</h4>
<span <span v-if="request.headers">
v-if="request.headers" <p
v-for="header in request.headers" v-for="header in request.headers"
:key="header.key" :key="header.key"
class="doc-desc"
> >
<p class="doc-desc">
<span> <span>
{{ header.key || $t("none") }}: {{ header.key || $t("none") }}:
<code>{{ header.value || $t("none") }}</code> <code>{{ header.value || $t("none") }}</code>
@@ -137,12 +141,12 @@
</p> </p>
</span> </span>
<h4 v-if="request.params.length > 0">{{ $t("parameters") }}</h4> <h4 v-if="request.params.length > 0">{{ $t("parameters") }}</h4>
<span <span v-if="request.params">
v-if="request.params" <p
v-for="parameter in request.params" v-for="parameter in request.params"
:key="parameter.key" :key="parameter.key"
class="doc-desc"
> >
<p class="doc-desc">
<span> <span>
{{ parameter.key || $t("none") }}: {{ parameter.key || $t("none") }}:
<code>{{ parameter.value || $t("none") }}</code> <code>{{ parameter.value || $t("none") }}</code>
@@ -150,12 +154,12 @@
</p> </p>
</span> </span>
<h4 v-if="request.bodyParam">{{ $t("payload") }}</h4> <h4 v-if="request.bodyParam">{{ $t("payload") }}</h4>
<span <span v-if="request.bodyParam">
v-if="request.bodyParam" <p
v-for="payload in request.bodyParam" v-for="payload in request.bodyParam"
:key="payload.key" :key="payload.key"
class="doc-desc"
> >
<p class="doc-desc">
<span> <span>
{{ payload.key || $t("none") }}: {{ payload.key || $t("none") }}:
<code>{{ payload.value || $t("none") }}</code> <code>{{ payload.value || $t("none") }}</code>
@@ -164,7 +168,8 @@
</span> </span>
<p class="doc-desc" v-if="request.rawParams"> <p class="doc-desc" v-if="request.rawParams">
<span> <span>
{{ $t("parameters") }}: <code>{{ request.rawParams || $t("none") }}</code> {{ $t("parameters") }}:
<code>{{ request.rawParams || $t("none") }}</code>
</span> </span>
</p> </p>
<p class="doc-desc" v-if="request.contentType"> <p class="doc-desc" v-if="request.contentType">
@@ -202,7 +207,8 @@
</p> </p>
<p class="doc-desc" v-if="request.method"> <p class="doc-desc" v-if="request.method">
<span> <span>
{{ $t("method") }}: <code>{{ request.method || $t("none") }}</code> {{ $t("method") }}:
<code>{{ request.method || $t("none") }}</code>
</span> </span>
</p> </p>
<p class="doc-desc" v-if="request.auth"> <p class="doc-desc" v-if="request.auth">
@@ -213,26 +219,29 @@
</p> </p>
<p class="doc-desc" v-if="request.httpUser"> <p class="doc-desc" v-if="request.httpUser">
<span> <span>
{{ $t("username") }}: <code>{{ request.httpUser || $t("none") }}</code> {{ $t("username") }}:
<code>{{ request.httpUser || $t("none") }}</code>
</span> </span>
</p> </p>
<p class="doc-desc" v-if="request.httpPassword"> <p class="doc-desc" v-if="request.httpPassword">
<span> <span>
{{ $t("password") }}: <code>{{ request.httpPassword || $t("none") }}</code> {{ $t("password") }}:
<code>{{ request.httpPassword || $t("none") }}</code>
</span> </span>
</p> </p>
<p class="doc-desc" v-if="request.bearerToken"> <p class="doc-desc" v-if="request.bearerToken">
<span> <span>
{{ $t("token") }}: <code>{{ request.bearerToken || $t("none") }}</code> {{ $t("token") }}:
<code>{{ request.bearerToken || $t("none") }}</code>
</span> </span>
</p> </p>
<h4 v-if="request.headers.length > 0">{{ $t("headers") }}</h4> <h4 v-if="request.headers.length > 0">{{ $t("headers") }}</h4>
<span <span v-if="request.headers">
v-if="request.headers" <p
v-for="header in request.headers" v-for="header in request.headers"
:key="header.key" :key="header.key"
class="doc-desc"
> >
<p class="doc-desc">
<span> <span>
{{ header.key || $t("none") }}: {{ header.key || $t("none") }}:
<code>{{ header.value || $t("none") }}</code> <code>{{ header.value || $t("none") }}</code>
@@ -240,12 +249,12 @@
</p> </p>
</span> </span>
<h4 v-if="request.params.length > 0">{{ $t("parameters") }}</h4> <h4 v-if="request.params.length > 0">{{ $t("parameters") }}</h4>
<span <span v-if="request.params">
v-if="request.params" <p
v-for="parameter in request.params" v-for="parameter in request.params"
:key="parameter.key" :key="parameter.key"
class="doc-desc"
> >
<p class="doc-desc">
<span> <span>
{{ parameter.key || $t("none") }}: {{ parameter.key || $t("none") }}:
<code>{{ parameter.value || $t("none") }}</code> <code>{{ parameter.value || $t("none") }}</code>
@@ -253,12 +262,12 @@
</p> </p>
</span> </span>
<h4 v-if="request.bodyParam">{{ $t("payload") }}</h4> <h4 v-if="request.bodyParam">{{ $t("payload") }}</h4>
<span <span v-if="request.bodyParam">
v-if="request.bodyParam" <p
v-for="payload in request.bodyParam" v-for="payload in request.bodyParam"
:key="payload.key" :key="payload.key"
class="doc-desc"
> >
<p class="doc-desc">
<span> <span>
{{ payload.key || $t("none") }}: {{ payload.key || $t("none") }}:
<code>{{ payload.value || $t("none") }}</code> <code>{{ payload.value || $t("none") }}</code>
@@ -267,7 +276,8 @@
</span> </span>
<p class="doc-desc" v-if="request.rawParams"> <p class="doc-desc" v-if="request.rawParams">
<span> <span>
{{ $t("parameters") }}: <code>{{ request.rawParams || $t("none") }}</code> {{ $t("parameters") }}:
<code>{{ request.rawParams || $t("none") }}</code>
</span> </span>
</p> </p>
<p class="doc-desc" v-if="request.contentType"> <p class="doc-desc" v-if="request.contentType">
@@ -305,9 +315,6 @@
} }
} }
.collection {
}
.folder { .folder {
border-left: 1px solid var(--brd-color); border-left: 1px solid var(--brd-color);
margin: 16px 0 0; margin: 16px 0 0;

View File

@@ -175,7 +175,8 @@
</button> </button>
</div> </div>
</div> </div>
<Editor <QueryEditor
ref="queryEditor"
v-model="gqlQueryString" v-model="gqlQueryString"
:options="{ :options="{
maxLines: responseBodyMaxLines, maxLines: responseBodyMaxLines,
@@ -391,6 +392,8 @@ import axios from "axios";
import * as gql from "graphql"; import * as gql from "graphql";
import textareaAutoHeight from "../directives/textareaAutoHeight"; import textareaAutoHeight from "../directives/textareaAutoHeight";
import AceEditor from "../components/ace-editor"; import AceEditor from "../components/ace-editor";
import QueryEditor from "../components/graphql/queryeditor";
import { sendNetworkRequest } from "../functions/network";
export default { export default {
directives: { directives: {
@@ -401,7 +404,8 @@ export default {
"gql-field": () => import("../components/graphql/field"), "gql-field": () => import("../components/graphql/field"),
"gql-type": () => import("../components/graphql/type"), "gql-type": () => import("../components/graphql/type"),
autocomplete: () => import("../components/autocomplete"), autocomplete: () => import("../components/autocomplete"),
Editor: AceEditor Editor: AceEditor,
QueryEditor: QueryEditor
}, },
data() { data() {
return { return {
@@ -542,6 +546,7 @@ export default {
responseBodyMaxLines: 16 responseBodyMaxLines: 16
}; };
}, },
computed: { computed: {
url: { url: {
get() { get() {
@@ -666,14 +671,9 @@ export default {
const gqlQueryString = this.gqlQueryString; const gqlQueryString = this.gqlQueryString;
this.variables.forEach(variable => { this.variables.forEach(variable => {
// todo: better variable type validation // todo: better variable type validation
const intRex = new RegExp(`\$${variable.key}\: Int`); if (gqlQueryString.indexOf(`\$${variable.key}: Int`) > -1) {
intRex.compile();
const floatRex = new RegExp(`\$${variable.key}\: Float`);
floatRex.compile();
if (intRex.test(gqlQueryString)) {
variables[variable.key] = parseInt(variable.value); variables[variable.key] = parseInt(variable.value);
} else if (floatRex.test(gqlQueryString)) { } else if (gqlQueryString.indexOf(`\$${variable.key}: Float`) > -1) {
variables[variable.key] = parseFloat(variable.value); variables[variable.key] = parseFloat(variable.value);
} else { } else {
variables[variable.key] = variable.value; variables[variable.key] = variable.value;
@@ -690,21 +690,7 @@ export default {
data: JSON.stringify({ query: gqlQueryString, variables }) data: JSON.stringify({ query: gqlQueryString, variables })
}; };
const reqConfig = this.$store.state.postwoman.settings.PROXY_ENABLED const data = await sendNetworkRequest(reqOptions, this.$store);
? {
method: "post",
url:
this.$store.state.postwoman.settings.PROXY_URL ||
`https://postwoman.apollotv.xyz/`,
data: reqOptions
}
: reqOptions;
const res = await axios(reqConfig);
const data = this.$store.state.postwoman.settings.PROXY_ENABLED
? res.data
: res;
this.responseString = JSON.stringify(data.data, null, 2); this.responseString = JSON.stringify(data.data, null, 2);
@@ -826,7 +812,7 @@ export default {
} }
} }
this.gqlTypes = types; this.gqlTypes = types;
this.$refs.queryEditor.setValidationSchema(schema);
this.$nuxt.$loading.finish(); this.$nuxt.$loading.finish();
const duration = Date.now() - startTime; const duration = Date.now() - startTime;
this.$toast.info(this.$t("finished_in", { duration }), { this.$toast.info(this.$t("finished_in", { duration }), {
@@ -834,7 +820,7 @@ export default {
}); });
} catch (error) { } catch (error) {
this.$nuxt.$loading.finish(); this.$nuxt.$loading.finish();
this.schemaString = `${error}. ${check_console_details}`; this.schemaString = `${error}. ${this.$t("check_console_details")}`;
this.$toast.error(`${error} ${this.$t("f12_details")}`, { this.$toast.error(`${error} ${this.$t("f12_details")}`, {
icon: "error" icon: "error"
}); });

View File

@@ -40,11 +40,7 @@
</ul> </ul>
</pw-section> </pw-section>
<pw-section <pw-section class="blue" :label="$t('request')" ref="request">
class="blue"
:label="$t('request')"
ref="request"
>
<ul> <ul>
<li> <li>
<label for="method">{{ $t("method") }}</label> <label for="method">{{ $t("method") }}</label>
@@ -842,10 +838,34 @@
<input id="collection-tab" type="radio" name="side" /> <input id="collection-tab" type="radio" name="side" />
<label for="collection-tab">{{ $t("collections") }}</label> <label for="collection-tab">{{ $t("collections") }}</label>
<div class="tab"> <div class="tab">
<pw-section class="yellow" :label="$t('collections')" ref="collections"> <pw-section
class="yellow"
:label="$t('collections')"
ref="collections"
>
<collections /> <collections />
</pw-section> </pw-section>
</div> </div>
<input id="sync-tab" type="radio" name="side" />
<label for="sync-tab">{{ $t("sync") }}</label>
<div class="tab">
<pw-section
v-if="fb.currentUser"
class="pink"
label="Sync"
ref="sync"
>
<inputform />
<ballsfeed />
</pw-section>
<pw-section v-else>
<ul>
<li>
<label>{{ $t("login_first") }}</label>
</li>
</ul>
</pw-section>
</div>
</section> </section>
</aside> </aside>
@@ -1135,6 +1155,8 @@ import getEnvironmentVariablesFromScript from "../functions/preRequest";
import parseTemplateString from "../functions/templating"; import parseTemplateString from "../functions/templating";
import AceEditor from "../components/ace-editor"; import AceEditor from "../components/ace-editor";
import { tokenRequest, oauthRedirect } from "../assets/js/oauth"; import { tokenRequest, oauthRedirect } from "../assets/js/oauth";
import { sendNetworkRequest } from "../functions/network";
import { fb } from "../functions/fb";
const statusCategories = [ const statusCategories = [
{ {
@@ -1199,7 +1221,9 @@ export default {
autocomplete: () => import("../components/autocomplete"), autocomplete: () => import("../components/autocomplete"),
collections: () => import("../components/collections"), collections: () => import("../components/collections"),
saveRequestAs: () => import("../components/collections/saveRequestAs"), saveRequestAs: () => import("../components/collections/saveRequestAs"),
Editor: AceEditor Editor: AceEditor,
inputform: () => import("../components/firebase/inputform"),
ballsfeed: () => import("../components/firebase/feeds")
}, },
data() { data() {
return { return {
@@ -1369,12 +1393,12 @@ export default {
], ],
showRequestModal: false, showRequestModal: false,
editRequest: {}, editRequest: {},
urlExcludes: {}, urlExcludes: {},
responseBodyText: "", responseBodyText: "",
responseBodyType: "text", responseBodyType: "text",
responseBodyMaxLines: 16, responseBodyMaxLines: 16,
activeSidebar: true activeSidebar: true,
fb
}; };
}, },
watch: { watch: {
@@ -1992,20 +2016,8 @@ export default {
if (typeof requestOptions.data === "string") { if (typeof requestOptions.data === "string") {
requestOptions.data = parseTemplateString(requestOptions.data); requestOptions.data = parseTemplateString(requestOptions.data);
} }
const config = this.$store.state.postwoman.settings.PROXY_ENABLED
? {
method: "POST",
url:
this.$store.state.postwoman.settings.PROXY_URL ||
"https://postwoman.apollotv.xyz/",
data: requestOptions
}
: requestOptions;
const response = await this.$axios(config); return await sendNetworkRequest(requestOptions, this.$store);
return this.$store.state.postwoman.settings.PROXY_ENABLED
? response.data
: response;
}, },
async sendRequest() { async sendRequest() {
this.$toast.clear(); this.$toast.clear();
@@ -2117,6 +2129,11 @@ export default {
star: false star: false
}; };
this.$refs.historyComponent.addEntry(entry); this.$refs.historyComponent.addEntry(entry);
if (fb.currentUser !== null) {
if (fb.currentSettings[1].value) {
fb.writeHistory(entry);
}
}
})(); })();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@@ -2138,6 +2155,11 @@ export default {
preRequestScript: this.preRequestScript preRequestScript: this.preRequestScript
}; };
this.$refs.historyComponent.addEntry(entry); this.$refs.historyComponent.addEntry(entry);
if (fb.currentUser !== null) {
if (fb.currentSettings[1].value) {
fb.writeHistory(entry);
}
}
return; return;
} else { } else {
this.response.status = error.message; this.response.status = error.message;
@@ -2150,7 +2172,7 @@ export default {
icon: "help", icon: "help",
duration: 8000, duration: 8000,
action: { action: {
text: "Settings", text: this.$t("yes"),
onClick: (e, toastObject) => { onClick: (e, toastObject) => {
this.$router.push({ path: "/settings" }); this.$router.push({ path: "/settings" });
} }

View File

@@ -1,5 +1,88 @@
<template> <template>
<div class="page"> <div class="page">
<pw-section class="green" :label="$t('account')" ref="account">
<ul>
<li>
<div v-if="fb.currentUser">
<button class="icon">
<img
v-if="fb.currentUser.photoURL"
:src="fb.currentUser.photoURL"
class="material-icons"
/>
<i v-else class="material-icons">account_circle</i>
<span>
{{ fb.currentUser.displayName || "Name not found" }}
</span>
</button>
<br />
<button class="icon">
<i class="material-icons">email</i>
<span>
{{ fb.currentUser.email || "Email not found" }}
</span>
</button>
<br />
<button class="icon" @click="logout">
<i class="material-icons">exit_to_app</i>
<span>{{ $t("logout") }}</span>
</button>
<br />
<p v-for="setting in fb.currentSettings" :key="setting.id">
<pw-toggle
:key="setting.name"
:on="setting.value"
@change="toggleSettings(setting.name, setting.value)"
>
{{ $t(setting.name) + " " + $t("sync") }}
{{ setting.value ? $t("enabled") : $t("disabled") }}
</pw-toggle>
</p>
<p v-if="fb.currentSettings.length == 0">
<button class="" @click="initSettings">
<i class="material-icons">sync</i>
<span>{{ $t("turn_on") + " " + $t("sync") }}</span>
</button>
</p>
</div>
<div v-else>
<label>{{ $t("login_with") }}</label>
<p>
<button class="icon" @click="signInWithGoogle">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="material-icons"
>
<path
d="M12.24 10.285V14.4h6.806c-.275 1.765-2.056 5.174-6.806 5.174-4.095 0-7.439-3.389-7.439-7.574s3.345-7.574 7.439-7.574c2.33 0 3.891.989 4.785 1.849l3.254-3.138C18.189 1.186 15.479 0 12.24 0c-6.635 0-12 5.365-12 12s5.365 12 12 12c6.926 0 11.52-4.869 11.52-11.726 0-.788-.085-1.39-.189-1.989H12.24z"
/>
</svg>
<span>Google</span>
</button>
<br />
<button class="icon" @click="signInWithGithub">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="material-icons"
>
<path
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
/>
</svg>
<span>GitHub</span>
</button>
</p>
</div>
</li>
</ul>
</pw-section>
<pw-section class="cyan" :label="$t('theme')" ref="theme"> <pw-section class="cyan" :label="$t('theme')" ref="theme">
<ul> <ul>
<li> <li>
@@ -137,6 +220,9 @@
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
<script> <script>
import firebase from "firebase/app";
import { fb } from "../functions/fb";
export default { export default {
components: { components: {
"pw-section": () => import("../components/section"), "pw-section": () => import("../components/section"),
@@ -238,7 +324,9 @@ export default {
this.$store.state.postwoman.settings.PROXY_URL || this.$store.state.postwoman.settings.PROXY_URL ||
"https://postwoman.apollotv.xyz/", "https://postwoman.apollotv.xyz/",
PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || "" PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || ""
} },
fb
}; };
}, },
@@ -289,8 +377,88 @@ export default {
toggleSetting(key) { toggleSetting(key) {
this.settings[key] = !this.settings[key]; this.settings[key] = !this.settings[key];
this.$store.commit("postwoman/applySetting", [key, this.settings[key]]); this.$store.commit("postwoman/applySetting", [key, this.settings[key]]);
},
logout() {
fb.currentUser = null;
firebase
.auth()
.signOut()
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
this.$toast.info(this.$t("logged_out"), {
icon: "vpn_key"
});
},
signInWithGoogle() {
const provider = new firebase.auth.GoogleAuthProvider();
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
icon: "sync",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
onClick: (e, toastObject) => {
fb.writeSettings("syncHistory", true);
fb.writeSettings("syncCollections", false);
this.$router.push({ path: "/settings" });
toastObject.remove();
}
}
});
}
})
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
},
signInWithGithub() {
const provider = new firebase.auth.GithubAuthProvider();
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
icon: "sync",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
onClick: (e, toastObject) => {
fb.writeSettings("syncHistory", true);
fb.writeSettings("syncCollections", false);
this.$router.push({ path: "/settings" });
toastObject.remove();
}
}
});
}
})
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
},
toggleSettings(s, v) {
fb.writeSettings(s, !v);
},
initSettings() {
fb.writeSettings("syncHistory", true);
fb.writeSettings("syncCollections", false);
} }
}, },
beforeMount() { beforeMount() {
this.settings.THEME_COLOR = this.getActiveColor(); this.settings.THEME_COLOR = this.getActiveColor();
}, },

View File

@@ -111,6 +111,12 @@ export const mutations = {
}, },
addNewCollection({ collections }, collection) { addNewCollection({ collections }, collection) {
const { name } = collection;
const duplicateCollection = collections.some(item => item.name === name);
if (duplicateCollection) {
this.$toast.info("Duplicate collection");
return;
}
collections.push({ collections.push({
name: "", name: "",
folders: [], folders: [],
@@ -126,6 +132,12 @@ export const mutations = {
editCollection({ collections }, payload) { editCollection({ collections }, payload) {
const { collection, collectionIndex } = payload; const { collection, collectionIndex } = payload;
const { name } = collection;
const duplicateCollection = collections.some(item => item.name === name);
if (duplicateCollection) {
this.$toast.info("Duplicate collection");
return;
}
collections[collectionIndex] = collection; collections[collectionIndex] = collection;
}, },