Merge pull request #2 from hosseinnedaee/Proxy_Server

Proxy server
This commit is contained in:
John Harker
2019-10-20 00:16:03 +01:00
committed by GitHub
69 changed files with 7434 additions and 2777 deletions

View File

@@ -1,6 +0,0 @@
[Dolphin]
Timestamp=2019,8,23,13,58,13
Version=4
[Settings]
HiddenFilesShown=true

104
.dockerignore Normal file
View File

@@ -0,0 +1,104 @@
Dockerfile
.vscode
.github
# Created by .ignore support plugin (hsz.mobi)
# Firebase
.firebase
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# Mac OSX
.DS_Store
# Vim swap files
*.swp
# Postwoman build data
.postwoman
# File explorer
.directory

View File

@@ -4,7 +4,7 @@ root = true
[*]
indent_size = 2
indent_style = tab
indent_style = space
charset = utf-8
end_of_line = lf
insert_final_newline = true

14
.firebaserc Normal file
View File

@@ -0,0 +1,14 @@
{
"projects": {
"default": "postwoman-api"
},
"targets": {
"postwoman-api": {
"hosting": {
"postwoman": [
"postwoman"
]
}
}
}
}

7
.gitignore vendored
View File

@@ -1,4 +1,8 @@
# Created by .ignore support plugin (hsz.mobi)
# Firebase
.firebase
### Node template
# Logs
logs
@@ -91,3 +95,6 @@ sw.*
# Postwoman build data
.postwoman
# File explorer
.directory

View File

@@ -15,30 +15,39 @@ language: node_js
node_js:
- "12"
addons:
apt:
packages:
- libgconf-2-4 # cypress binary dependency
env:
- DEPLOY_ENV=GH_PAGES
- DEPLOY_ENV=POSTWOMAN_IO
cache:
npm: true
directories:
- "node_modules"
- ~/.cache
branches:
only:
- "master"
install:
- "npm install firebase-tools"
- "npm install"
before_script:
- "npm run test"
script:
- "cd functions"
- "npm install"
- "cd .."
- "npm run generate"
notifications:
webhooks: https://www.travisbuddy.com
deploy:
provider: pages
skip-cleanup: true
# Refer to: https://docs.travis-ci.com/user/deployment/pages/#Setting-the-GitHub-token
github-token: $GITHUB_ACCESS_TOKEN
target-branch: gh-pages
local-dir: dist
on:
branch: master
after_success:
- firebase deploy --token $FIREBASE_TOKEN

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"deepcode.review.results.hideInformationIssues": false
}

View File

@@ -1,38 +1,3 @@
# Changelog
# Title
### Description by [Liyas Thomas](https://github.com/liyasthomas)
---
# 1.0.0
## This is it, Title 1.0.0!
We are finally out of beta, therefore many bugs were fixed and camera received a brand new look.
* **NEW**: Camera redesign
* **NEW**: Camera redesign
* **NEW**: macOS and iOS support
* **IMPROVEMENT**: Major improvements
* **IMPROVEMENT**: Updated libraries
* **FIX**: Fixed many bugs and crashes
* **FIX**: Graphic glitches
* **FIX**: Statusbar too dark
* **TRANSLATION**: Updated translations
* **REVERT**: Brought back the "Help" button
* **OTHER**: Removed all analytics
---
# 0.9.0
## I worked a lot on Web apps, WebAR, WebGL & PWAs
So I think Lvr is now ready to be released :)
I will keep the usual branch model.
* Stable release on `master` branch
---
## Thanks
* [Google](https://www.google.com) - for [Polymer](https://polymer-project.org)
* [v0.1.0](https://github.com/liyasthomas/postwoman/releases/tag/v0.1.0) - Initial 🎉 Initial public release

View File

@@ -1,180 +1,92 @@
# Introduction
# Contributing
### Write something nice here!
When contributing to this repository, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change.
>First off, thank you for considering contributing to Active Admin. It's people like you that make Active Admin such a great tool.
Please note we have a code of conduct, please follow it in all your interactions with the project.
[source: [Active Admin](https://github.com/activeadmin/activeadmin/blob/master/CONTRIBUTING.md)] **Need more inspiration?** [1] [Read The Docs](http://read-the-docs.readthedocs.org/en/latest/contribute.html) [2] [Mustache.js](https://github.com/janl/mustache.js/#contributing)
## Pull Request Process
### Tell them why they should read your guidelines.
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Increase the version numbers in any examples files and the README.md to the new version that this
Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer to merge it for you.
>Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests.
## Code of Conduct
[source: [Hoodie](https://github.com/hoodiehq/hoodie/blob/master/CONTRIBUTING.md)]
### Our Pledge
### Explain what kinds of contributions you are looking for.
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
Keep an open mind! Improving documentation, bug triaging, or writing tutorials are all examples of helpful contributions that mean less work for you.
### Our Standards
> Elasticsearch is an open source project and we love to receive contributions from our community — you! There are many ways to contribute, from writing tutorials or blog posts, improving the documentation, submitting bug reports and feature requests or writing code which can be incorporated into Elasticsearch itself.
Examples of behavior that contributes to creating a positive environment
include:
[source: [Elasticsearch](https://github.com/elastic/elasticsearch/blob/master/CONTRIBUTING.md)] **Need more inspiration?** [1] [Devise](https://github.com/plataformatec/devise/wiki/Contributing) [2] [Geocoder](https://github.com/alexreisner/geocoder#known-issue) (“known issue”)
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
### Explain contributions you are NOT looking for (if any).
Examples of unacceptable behavior by participants include:
Again, defining this up front means less work for you. If someone ignores your guide and submits something you dont want, you can simply close it and point to your policy.
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
> Please, don't use the issue tracker for [support questions]. Check whether the #pocoo IRC channel on Freenode can help with your issue. If your problem is not strictly Werkzeug or Flask specific, #python is generally more active. Stack Overflow is also worth considering.
### Our Responsibilities
[source: [Flask](https://github.com/pallets/flask/blob/master/CONTRIBUTING.rst)] **Need more inspiration?** [1] [cucumber-ruby](https://github.com/cucumber/cucumber-ruby/blob/master/CONTRIBUTING.md#about-to-create-a-new-github-issue) [2] [Read the Docs](http://read-the-docs.readthedocs.org/en/latest/open-source-philosophy.html#unsupported)
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
# Ground Rules
### Set expectations for behavior (yours, and theirs).
This includes not just how to communicate with others (being respectful, considerate, etc) but also technical responsibilities (importance of testing, project dependencies, etc). Mention and link to your code of conduct, if you have one.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
> Responsibilities
> * Ensure cross-platform compatibility for every change that's accepted. Windows, Mac, Debian & Ubuntu Linux.
> * Ensure that code that goes into core meets all requirements in this checklist: https://gist.github.com/audreyr/4feef90445b9680475f2
> * Create issues for any major changes and enhancements that you wish to make. Discuss things transparently and get community feedback.
> * Don't add any classes to the codebase unless absolutely needed. Err on the side of using functions.
> * Keep feature versions as small as possible, preferably one new feature per version.
> * Be welcoming to newcomers and encourage diverse new contributors from all backgrounds. See the [Python Community Code of Conduct](https://www.python.org/psf/codeofconduct/).
### Scope
[source: [cookiecutter](https://github.com/audreyr/cookiecutter/blob/master/CONTRIBUTING.rst)] **Need more inspiration?** [1] [Celery](https://github.com/celery/celery/blob/master/CONTRIBUTING.rst#community-code-of-conduct) [2] [geocoder](https://github.com/alexreisner/geocoder#contributing)
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
# Your First Contribution
Help people who are new to your project understand where they can be most helpful. This is also a good time to let people know if you follow a label convention for flagging beginner issues.
### Enforcement
> Unsure where to begin contributing to Atom? You can start by looking through these beginner and help-wanted issues:
> Beginner issues - issues which should only require a few lines of code, and a test or two.
> Help wanted issues - issues which should be a bit more involved than beginner issues.
> Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
[source: [Atom](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#your-first-code-contribution)] **Need more inspiration?** [1] [Read the Docs](http://docs.readthedocs.org/en/latest/contribute.html#contributing-to-development) [2] [Django](https://docs.djangoproject.com/en/dev/internals/contributing/new-contributors/#first-steps) (scroll down to "Guidelines" as well)
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
### Bonus points: Add a link to a resource for people who have never contributed to open source before.
Here are a couple of friendly tutorials you can include: http://makeapullrequest.com/ and http://www.firsttimersonly.com/
### Attribution
> Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[source: [React](https://github.com/facebook/react/blob/master/CONTRIBUTING.md#pull-requests)]
As a side note, it helps to use newcomer-friendly language throughout the rest of your document. Here are a couple of examples from [Active Admin](https://github.com/activeadmin/activeadmin/blob/master/CONTRIBUTING.md):
>At this point, you're ready to make your changes! Feel free to ask for help; everyone is a beginner at first :smile_cat:
>
>If a maintainer asks you to "rebase" your PR, they're saying that a lot of code has changed, and that you need to update your branch so it's easier to merge.
# Getting started
### Give them a quick walkthrough of how to submit a contribution.
How you write this is up to you, but some things you may want to include:
* Let them know if they need to sign a CLA, agree to a DCO, or get any other legal stuff out of the way
* If tests are required for contributions, let them know, and explain how to run the tests
* If you use anything other than GitHub to manage issues (ex. JIRA or Trac), let them know which tools theyll need to contribute
>For something that is bigger than a one or two line fix:
>1. Create your own fork of the code
>2. Do the changes in your fork
>3. If you like the change and think the project could use it:
* Be sure you have followed the code style for the project.
* Sign the Contributor License Agreement, CLA, with the jQuery Foundation.
* Note the jQuery Foundation Code of Conduct.
* Send a pull request indicating that you have a CLA on file.
[source: [Requirejs](http://requirejs.org/docs/contributing.html)] **Need more inspiration?** [1] [Active Admin](https://github.com/activeadmin/activeadmin/blob/master/CONTRIBUTING.md#1-where-do-i-go-from-here) [2] [Node.js](https://github.com/nodejs/node/blob/master/CONTRIBUTING.md#code-contributions) [3] [Ember.js](https://github.com/emberjs/ember.js/blob/master/CONTRIBUTING.md#pull-requests)
### If you have a different process for small or "obvious" fixes, let them know.
> Small contributions such as fixing spelling errors, where the content is small enough to not be considered intellectual property, can be submitted by a contributor as a patch, without a CLA.
>
>As a rule of thumb, changes are obvious fixes if they do not introduce any new functionality or creative thinking. As long as the change does not affect functionality, some likely examples include the following:
>* Spelling / grammar fixes
>* Typo correction, white space and formatting changes
>* Comment clean up
>* Bug fixes that change default return values or error codes stored in constants
>* Adding logging messages or debugging output
>* Changes to metadata files like Gemfile, .gitignore, build scripts, etc.
>* Moving source files from one directory or package to another
[source: [Chef](https://github.com/chef/chef/blob/master/CONTRIBUTING.md#chef-obvious-fix-policy)] **Need more inspiration?** [1] [Puppet](https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md#making-trivial-changes)
# How to report a bug
### Explain security disclosures first!
At bare minimum, include this sentence:
> If you find a security vulnerability, do NOT open an issue. Email XXXX instead.
If you dont want to use your personal contact information, set up a “security@” email address. Larger projects might have more formal processes for disclosing security, including encrypted communication. (Disclosure: I am not a security expert.)
> Any security issues should be submitted directly to security@travis-ci.org
> In order to determine whether you are dealing with a security issue, ask yourself these two questions:
> * Can I access something that's not mine, or something I shouldn't have access to?
> * Can I disable something for other people?
>
> If the answer to either of those two questions are "yes", then you're probably dealing with a security issue. Note that even if you answer "no" to both questions, you may still be dealing with a security issue, so if you're unsure, just email us at security@travis-ci.org.
[source: [Travis CI](https://github.com/travis-ci/travis-ci/blob/master/CONTRIBUTING.md)] **Need more inspiration?** [1] [Celery](https://github.com/celery/celery/blob/master/CONTRIBUTING.rst#security) [2] [Express.js](https://github.com/expressjs/express/blob/master/Security.md)
### Tell your contributors how to file a bug report.
You can even include a template so people can just copy-paste (again, less work for you).
> When filing an issue, make sure to answer these five questions:
>
> 1. What version of Go are you using (go version)?
> 2. What operating system and processor architecture are you using?
> 3. What did you do?
> 4. What did you expect to see?
> 5. What did you see instead?
> General questions should go to the golang-nuts mailing list instead of the issue tracker. The gophers there will answer or ask you to file an issue if you've tripped over a bug.
[source: [Go](https://github.com/golang/go/blob/master/CONTRIBUTING.md#filing-issues)] **Need more inspiration?** [1] [Celery](https://github.com/celery/celery/blob/master/CONTRIBUTING.rst#other-bugs ) [2] [Atom](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#reporting-bugs) (includes template)
# How to suggest a feature or enhancement
### If you have a particular roadmap, goals, or philosophy for development, share it here.
This information will give contributors context before they make suggestions that may not align with the projects needs.
> The Express philosophy is to provide small, robust tooling for HTTP servers, making it a great solution for single page applications, web sites, hybrids, or public HTTP APIs.
>
> Express does not force you to use any specific ORM or template engine. With support for over 14 template engines via Consolidate.js, you can quickly craft your perfect framework.
[source: [Express](https://github.com/expressjs/express#philosophy)] **Need more inspiration?** [Active Admin](https://github.com/activeadmin/activeadmin#goals)
### Explain your desired process for suggesting a feature.
If there is back-and-forth or signoff required, say so. Ask them to scope the feature, thinking through why its needed and how it might work.
> If you find yourself wishing for a feature that doesn't exist in Elasticsearch, you are probably not alone. There are bound to be others out there with similar needs. Many of the features that Elasticsearch has today have been added because our users saw the need. Open an issue on our issues list on GitHub which describes the feature you would like to see, why you need it, and how it should work.
[source: [Elasticsearch](https://github.com/elastic/elasticsearch/blob/master/CONTRIBUTING.md#feature-requests)] **Need more inspiration?** [1] [Hoodie](https://github.com/hoodiehq/hoodie/blob/master/CONTRIBUTING.md#feature-requests) [2] [Ember.js](https://github.com/emberjs/ember.js/blob/master/CONTRIBUTING.md#requesting-a-feature)
# Code review process
### Explain how a contribution gets accepted after its been submitted.
Who reviews it? Who needs to sign off before its accepted? When should a contributor expect to hear from you? How can contributors get commit access, if at all?
> The core team looks at Pull Requests on a regular basis in a weekly triage meeting that we hold in a public Google Hangout. The hangout is announced in the weekly status updates that are sent to the puppet-dev list. Notes are posted to the Puppet Community community-triage repo and include a link to a YouTube recording of the hangout.
> After feedback has been given we expect responses within two weeks. After two weeks we may close the pull request if it isn't showing any activity.
[source: [Puppet](https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md#submitting-changes)] **Need more inspiration?** [1] [Meteor](https://meteor.hackpad.com/Responding-to-GitHub-Issues-SKE2u3tkSiH ) [2] [Express.js](https://github.com/expressjs/express/blob/master/Contributing.md#becoming-a-committer)
# Community
If there are other channels you use besides GitHub to discuss contributions, mention them here. You can also list the author, maintainers, and/or contributors here, or set expectations for response time.
> You can chat with the core team on https://gitter.im/cucumber/cucumber. We try to have office hours on Fridays.
[source: [cucumber-ruby](https://github.com/cucumber/cucumber-ruby/blob/master/CONTRIBUTING.md#talking-with-other-devs)] **Need more inspiration?**
[1] [Chef](https://github.com/chef/chef/blob/master/CONTRIBUTING.md#-developer-office-hours) [2] [Cookiecutter](https://github.com/audreyr/cookiecutter#community)
# BONUS: Code, commit message and labeling conventions
These sections are not necessary, but can help streamline the contributions you receive.
### Explain your preferred style for code, if you have any.
**Need inspiration?** [1] [Requirejs](http://requirejs.org/docs/contributing.html#codestyle) [2] [Elasticsearch](https://github.com/elastic/elasticsearch/blob/master/CONTRIBUTING.md#contributing-to-the-elasticsearch-codebase)
### Explain if you use any commit message conventions.
**Need inspiration?** [1] [Angular](https://github.com/angular/material/blob/master/.github/CONTRIBUTING.md#submit) [2] [Node.js](https://github.com/nodejs/node/blob/master/CONTRIBUTING.md#step-3-commit)
### Explain if you use any labeling conventions for issues.
**Need inspiration?** [1] [StandardIssueLabels](https://github.com/wagenet/StandardIssueLabels#standardissuelabels) [2] [Atom](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#issue-and-pull-request-labels)
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM node:12.10.0-buster
LABEL maintainer="Liyas Thomas (liyascthomas@gmail.com)"
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "run", "start"]

236
README.md
View File

@@ -1,40 +1,46 @@
```
When I wrote this, only God and I understood what I was doing. Now, only God knows.
```
<div align="center">
<a href="https://liyas-thomas.firebaseapp.com"><img src="https://raw.githubusercontent.com/liyasthomas/templates/master/assets/logo.gif" alt="Liyas Thomas" width="200"></a>
<br>
<h1>Liyas Thomas</h1>
<sub>Built with ❤︎ by
<a href="https://github.com/liyasthomas">liyasthomas</a> and
<a href="https://github.com/liyasthomas/postwoman/graphs/contributors">contributors</a>
</sub>
<a href="https://liyas-thomas.firebaseapp.com"><img src="static/icons/logo.svg" alt="Liyas Thomas" height="128"></a>
<br>
<h1><a href="https://postwoman.io">Postwoman.io</a></h1>
<p>
API request builder - Helps you create your requests faster, saving you precious time on your development - <a href="https://postwoman.launchaco.com">Subscribe</a>
</p>
<p>
[![Build Status](https://travis-ci.com/liyasthomas/postwoman.svg?branch=master)](https://travis-ci.com/liyasthomas/postwoman) [![GitHub release](https://img.shields.io/github/release/liyasthomas/postwoman/all.svg)](https://github.com/liyasthomas/postwoman/releases/latest) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](CONTRIBUTING.md) [![Website](https://img.shields.io/website-up-down-green-red/https/shields.io.svg?label=website)](https://postwoman.io) [![Financial Contributors on Open Collective](https://opencollective.com/postwoman/all/badge.svg?label=financial+contributors)](https://opencollective.com/postwoman) [![Donate](https://img.shields.io/badge/$-donate-blue.svg)](https://www.paypal.me/liyascthomas) [![Chat on Telegram](https://img.shields.io/badge/chat-Telegram-blueviolet)](https://t.me/postwoman_app) [![Chat on Discord](https://img.shields.io/badge/chat-Discord-violet?logo=discord)](https://discord.gg/GAMWxmR)
</p>
<sub>Built with ❤︎ by
<a href="https://github.com/liyasthomas">liyasthomas</a> and
<a href="https://github.com/liyasthomas/postwoman/graphs/contributors">contributors</a>
</sub>
</div>
---
[![Build Status](https://travis-ci.com/liyasthomas/postwoman.svg?branch=master)](https://travis-ci.com/liyasthomas/postwoman) [![GitHub release](https://img.shields.io/github/release/liyasthomas/postwoman/all.svg)](https://github.com/liyasthomas/postwoman/releases/latest) [![repo size](https://img.shields.io/github/repo-size/liyasthomas/postwoman.svg)](https://github.com/liyasthomas/postwoman/archive/master.zip) [![license](https://img.shields.io/github/license/liyasthomas/postwoman.svg)](https://github.com/liyasthomas/postwoman/blob/master/LICENSE) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/liyasthomas/postwoman/issues) [![Website](https://img.shields.io/website-up-down-green-red/https/shields.io.svg?label=website)](https://liyas-thomas.firebaseapp.com) [![Donate](https://img.shields.io/badge/$-donate-ff69b4.svg)](https://www.paypal.me/liyascthomas) [![Buy me a coffee](https://img.shields.io/badge/$-BuyMeACoffee-orange.svg)](https://www.buymeacoffee.com/liyasthomas)
# <img src="static/icon.png" alt="postwoman" width="32"> Postwoman
### API request builder
**Start here: _[Story behind Postwoman](https://dev.to/liyasthomas/i-created-postwoman-an-online-open-source-api-request-builder-41md)_**
**Chat here: _[Telegram](https://t.me/postwoman_app), [Discord](https://discord.gg/GAMWxmR)_**
**Donate here: _[PayPal](https://www.paypal.me/liyascthomas), [Open Collective](https://opencollective.com/postwoman), [Patreon](https://www.patreon.com/liyasthomas)_**
<div align="center">
<br>
<img src="static/screely.png" alt="postwoman" width="100%">
<img src="static/screely2.png" alt="postwoman" width="100%">
<img src="static/screenshot1.png" alt="postwoman" width="100%">
<br>
</div>
### Features :sparkles:
### Features
:heart: **Lightweight**: Crafted with minimalistic UI design
❤️ **Lightweight**: Crafted with minimalistic UI design. Simple design is the best design.
- Faster, lighter, cleaner, minimal & responsive
:zap: **Real-time**: Send requests and get/copy responses right away!
⚡️ **Fast**: Send requests and get/copy responses in real-time! Fast software is the best software.
**Methods:**
- `GET` - Retrieve information about the REST API resource
@@ -47,63 +53,88 @@ When I wrote this, only God and I understood what I was doing. Now, only God kno
_History entries are synced with local session storage_
:rainbow: **VIBGYOR**: Neon combination background, foreground & accent colors - because customization === freedom :sparkles:
🌈 **Make it yours**: Customizable combinations for background, foreground and accent colors: because customization === freedom. [Customize now ✨](https://postwoman.io/settings).
**Customizations:**
- Dark and Light background themes
- Choose accent color
- Choose theme: Kinda Dark (default), Clearly White, Just Black and System theme
- Choose accent color: Green (default), Yellow, Pink, Red, Purple, Orange, Cyan and Blue
- Toggle multi-colored frames
_Customized themes are also synced with local session storage_
:fire: **PWA**: Install as a **[PWA](https://developers.google.com/web/progressive-web-apps)** on your device
🔥 **PWA**: Install as a **[PWA](https://developers.google.com/web/progressive-web-apps)** on your device.
**Features:**
- Instant loading with Service Workers
- Instant loading with [Service Workers](https://developers.google.com/web/fundamentals/primers/service-workers)
- Offline support
- Low RAM/memory and CPU usage
- [Add to Home Screen](https://developers.google.com/web/fundamentals/app-install-banners) (button in footer)
- [Desktop PWA](https://developers.google.com/web/progressive-web-apps/desktop) support (button in footer)
- [Full features](https://developers.google.com/web/progressive-web-apps)
:electric_plug: **Web Socket**: Establish full-duplex communication channels over a single TCP connection
🚀 **Request**: Retrieve data from a URL without having to do a full page refresh.
- Choose `method`
- Enter `URL`
- Enter `Path`
**Features:**
- Copy/share public "Share URL"
- Generate request code for JavaScript XHR, Fetch and cURL
- Copy generated request code to clipboard
- Import cURL
- Label requests
🔌 **Web Socket**: Establish full-duplex communication channels over a single TCP connection.
- Send and receive data
:closed_lock_with_key: **Authentication**: Allows to identity the end user
🔐 **Authentication**: Allows to identity the end user.
**Types:**
- None
- Basic authentication using username and password
- Token based authentication
:loudspeaker: **Headers**: Describes the format the body of your request is being sent as
📢 **Headers**: Describes the format the body of your request is being sent as.
:mailbox: **Parameters**: Use request parameters to set varying parts in simulated requests
- Add or remove Header list
:page_with_curl: **Request Body**: Used to send and receive data via the REST API
📫 **Parameters**: Use request parameters to set varying parts in simulated requests.
📃 **Request Body**: Used to send and receive data via the REST API.
**Options:**
- Set content Type
- Toggle between RAW input and parameter list
- Set Content Type
- Add or remove Parameter list
- Toggle between key-value and RAW input Parameter list
:wave: **Responses**: Contains the status line, headers and the message/response body
👋 **Responses**: Contains the status line, headers and the message/response body.
- Copy response to clipboard
- View preview for HTML responses
_HTML responses have "Preview HTML" feature_
:alarm_clock: **History**: Request entries are synced with local session storage to reuse with a single click
**History**: Request entries are synced with local session storage to reuse with a single click.
**Fields**
- Label
- Timestamp
- Method
- Status code
- URL
- Path
_History entries can be deleted one-by-one or all together_
_History entries can be sorted by any fields_
---
_Histories can deleted one-by-one or all together_
## Demo
## Demo 🚀
[https://liyasthomas.github.io/postwoman](https://liyasthomas.github.io/postwoman)
[https://postwoman.io](https://postwoman.io)
## Usage 💡
1. Specify your request method
2. Type in your API URL
@@ -113,94 +144,143 @@ _History entries can be deleted one-by-one or all together_
You're done!
---
## Built with 🔧
## Built with
* **[Chromium](https://github.com/chromium/chromium)** - Thanks for being so fast!
* [Chromium](https://github.com/chromium/chromium) - Thanks for being so fast!
* HTML - For the web framework
* CSS - For styling components
* JavaScript - For magic!
* [Vue](https://vuejs.org/) - To add to the JavaScript magic!
* [Nuxt](https://nuxtjs.org/) - To add to the Vue magic! <!-- (Nuxt helps create the PWA and single page application.) -->
* [Nuxt](https://nuxtjs.org/) - To add to the Vue magic!
---
## Developing
## Developing 👷
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
1. Install dependencies by running `npm install` within the directory that you cloned (probably `postwoman`).
1. Start the development server with `npm run dev`.
1. Open development site by going to [http://localhost:3000](http://localhost:3000) in your browser.
---
#### Or, with docker-compose:
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
1. Run `docker-compose up`
1. Open development site by going to [http://localhost:3000](http://localhost:3000) in your browser.
## Docker 🐳
```bash
#pull
docker pull liyasthomas/postwoman
#run
docker run -p 3000:3000 liyasthomas/postwoman:latest
#build
docker build -t postwoman:latest
```
## Releasing 🏷️
## Releasing
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
1. Install dependencies by running `npm install` within the directory that you cloned (probably `postwoman`).
1. Build the release files with `npm run build`.
1. Find the built project in `./dist`.
---
## Contributing
## Contributing 🍰
Please read [CONTRIBUTING](CONTRIBUTING.md) for details on our [CODE OF CONDUCT](CODE_OF_CONDUCT.md), and the process for submitting pull requests to us.
---
## Continuous Integration
## Continuous Integration 💚
We use [Travis CI](https://travis-ci.com) for continuous integration. Check out our [Travis CI Status](https://travis-ci.com/liyasthomas/postwoman).
---
## Versioning
## Versioning 🔖
This project is developed by [Liyas Thomas](https://github.com/liyasthomas) using the [Semantic Versioning specification](https://semver.org). For the versions available, see the [releases on this repository](https://github.com/liyasthomas/postwoman/releases).
---
## Change log
## Change log 📝
See the [CHANGELOG](CHANGELOG.md) file for details.
---
## Authors
## Authors 🔮
### Lead Developers
* [**Liyas Thomas**](https://github.com/liyasthomas) - *Author*
* **[Liyas Thomas](https://github.com/liyasthomas)** - *Author*
### Testing and Debugging
* [Liyas Thomas](https://github.com/liyasthomas)
* ([contributors](https://github.com/liyasthomas/postwoman/graphs/contributors))
### Contributors
* [John Harker](https://github.com/NBTX)
* [Andrew Bastin](https://github.com/AndrewBastin)
* [Nick Palenchar](https://github.com/nickpalenchar)
* [Abraham Williams](https://github.com/abraham)
* [Nicholas La Roux](https://github.com/larouxn)
* [RifqiAlAbqary](https://github.com/reefqi037)
* [izerozlu](https://github.com/izerozlu)
* [Thomas Yuba](https://github.com/yubathom)
### Collaborators
### Thanks
* [Dribbble](https://dribbble.com)
<table>
<tr>
<td align="center"><a href="https://github.com/NBTX"><img src="https://github.com/NBTX.png?size=100" width="100px;" alt="John Harker"/><br /><sub><b>John Harker</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=NBTX" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/izerozlu"><img src="https://github.com/izerozlu.png?size=100" width="100px;" alt="izerozlu"/><br /><sub><b>izerozlu</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=izerozlu" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/AndrewBastin"><img src="https://github.com/AndrewBastin.png?size=100" width="100px;" alt="Andrew Bastin"/><br /><sub><b>Andrew Bastin</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=AndrewBastin" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/nickpalenchar"><img src="https://github.com/nickpalenchar.png?size=100" width="100px;" alt="Nick Palenchar"/><br /><sub><b>Nick Palenchar</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=nickpalenchar" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/yubathom"><img src="https://github.com/yubathom.png?size=100" width="100px;" alt="Thomas Yuba"/><br /><sub><b>Thomas Yuba</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=yubathom" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/larouxn"><img src="https://github.com/larouxn.png?size=100" width="100px;" alt="Nicholas La Roux"/><br /><sub><b>Nicholas La Roux</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=larouxn" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/JacobAnavisca"><img src="https://github.com/JacobAnavisca.png?size=100" width="100px;" alt="Jacob Anavisca"/><br /><sub><b>Jacob Anavisca</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=JacobAnavisca" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/nityanandagohain"><img src="https://github.com/nityanandagohain.png?size=100" width="100px;" alt="Nityananda Gohain"/><br /><sub><b>Nityananda Gohain</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=nityanandagohain" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/terranblake"><img src="https://github.com/terranblake.png?size=100" width="100px;" alt="Terran Blake"/><br /><sub><b>Terran Blake</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=terranblake" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/hosseinnedaee"><img src="https://github.com/hosseinnedaee.png?size=100" width="100px;" alt="Hossein Nedaee"/><br /><sub><b>Hossein Nedaee</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=hosseinnedaee" title="Code">💻</a></td>
</tr>
</table>
See the list of [contributors](https://github.com/liyasthomas/postwoman/graphs/contributors) who participated in this project.
---
### Thanks
## License
* [dev.to](https://dev.to)
## Contributors 🚸
### Code Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
<a href="https://github.com/liyasthomas/postwoman/graphs/contributors"><img src="https://opencollective.com/postwoman/contributors.svg?width=890&button=false" /></a>
### Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/postwoman/contribute)]
#### Individuals
<a href="https://opencollective.com/postwoman"><img src="https://opencollective.com/postwoman/individuals.svg"></a>
#### Organizations
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/postwoman/contribute)]
<a href="https://opencollective.com/postwoman/organization/0/website"><img src="https://opencollective.com/postwoman/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/postwoman/organization/1/website"><img src="https://opencollective.com/postwoman/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/postwoman/organization/2/website"><img src="https://opencollective.com/postwoman/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/postwoman/organization/3/website"><img src="https://opencollective.com/postwoman/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/postwoman/organization/4/website"><img src="https://opencollective.com/postwoman/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/postwoman/organization/5/website"><img src="https://opencollective.com/postwoman/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/postwoman/organization/6/website"><img src="https://opencollective.com/postwoman/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/postwoman/organization/7/website"><img src="https://opencollective.com/postwoman/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/postwoman/organization/8/website"><img src="https://opencollective.com/postwoman/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/postwoman/organization/9/website"><img src="https://opencollective.com/postwoman/organization/9/avatar.svg"></a>
## License 📄
This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [LICENSE](LICENSE) file for details.
---
## Acknowledgments
## Acknowledgments 🙏
* Hat tip to anyone who's code was used
* Inspirations:
* [Dribbble](https://dribbble.com)
<div align="center">
<a href="https://liyas-thomas.firebaseapp.com"><img src="https://raw.githubusercontent.com/liyasthomas/templates/master/assets/logo.gif" alt="Liyas Thomas" width="200"></a>
<br>
<h3>Happy Coding ❤︎</h3>
</div>

View File

@@ -1,2 +1,2 @@
// Poppins (Google Fonts)
@import url("https://fonts.googleapis.com/css?family=Poppins:500,700&display=swap");
@import url('https://fonts.googleapis.com/css?family=Material+Icons|Poppins:500,700|Roboto+Mono:400&display=swap');

View File

@@ -7,17 +7,27 @@ $responsiveWidth: 720px;
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
background-color: #4a4a4a;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.5);
}
::placeholder {
color: var(--fg-light-color);
opacity: 0.3;
}
* {
box-sizing: border-box;
outline: 0;
border: 0;
font-family: "Poppins", "Roboto", "Noto", sans-serif;
}
a {
@@ -31,10 +41,11 @@ body {
background-color: var(--bg-color);
color: var(--fg-color);
font-weight: 500;
font-size: 16px;
font-family: "Poppins", "Roboto", "Noto", sans-serif;
line-height: 1.5;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
@@ -45,6 +56,116 @@ h3 {
font-weight: 700;
}
.tooltip {
display: block !important;
z-index: 10000;
.tooltip-inner {
background: black;
color: white;
border-radius: 16px;
padding: 5px 10px 4px;
}
.tooltip-arrow {
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 5px;
border-color: black;
z-index: 1;
}
&[x-placement^="top"] {
margin-bottom: 5px;
.tooltip-arrow {
border-width: 5px 5px 0 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
bottom: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
}
&[x-placement^="bottom"] {
margin-top: 5px;
.tooltip-arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-top-color: transparent !important;
top: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
}
&[x-placement^="right"] {
margin-left: 5px;
.tooltip-arrow {
border-width: 5px 5px 5px 0;
border-left-color: transparent !important;
border-top-color: transparent !important;
border-bottom-color: transparent !important;
left: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
}
&[x-placement^="left"] {
margin-right: 5px;
.tooltip-arrow {
border-width: 5px 0 5px 5px;
border-top-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
right: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
}
&.popover {
$color: #f9f9f9;
.popover-inner {
background: $color;
color: black;
padding: 24px;
border-radius: 5px;
box-shadow: 0 5px 30px rgba(black, .1);
}
.popover-arrow {
border-color: $color;
}
}
&[aria-hidden='true'] {
visibility: hidden;
opacity: 0;
transition: opacity .15s, visibility .15s;
}
&[aria-hidden='false'] {
visibility: visible;
opacity: 1;
transition: opacity .15s;
}
}
h3.title {
margin: 4px;
}
@@ -67,10 +188,7 @@ nav {
}
body.sticky-footer footer {
position: fixed;
right: 0;
bottom: 0;
left: 0;
opacity: .25;
}
.logo {
@@ -78,101 +196,99 @@ body.sticky-footer footer {
}
button {
display: inline-flex;
align-items: center;
justify-content: center;
margin: 4px;
padding: 8px 16px;
border-radius: 4px;
padding: 0 16px;
border-radius: 20px;
background-color: var(--ac-color);
color: var(--act-color);
font-weight: 700;
font-size: 16px;
cursor: pointer;
font-family: "Poppins", "Roboto", "Noto", sans-serif;
transition: all 0.2s ease-in-out;
fill: var(--act-color);
height: 40px;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.02);
cursor: pointer;
&[disabled], &.disabled {
opacity: 0.7;
cursor: default;
span {
display: inline-flex;
margin-left: 8px;
}
// Only show hover and focus if the button is *not*
// disabled.
&:not([disabled]):hover, &:not(.disabled):focus {
background-color: transparent;
box-shadow: inset 0 0 0 2px var(--ac-color);
color: var(--ac-color);
}
&.icon {
background-color: var(--bg-color);
color: var(--fg-light-color);
fill: var(--fg-light-color);
&:not([disabled]):hover {
color: var(--fg-color);
fill: var(--fg-color);
}
}
&:not([disabled]):hover {
color: var(--act-color);
fill: var(--act-color);
}
}
fieldset {
margin: 16px 0;
border: 2px solid var(--fg-color);
border-radius: 4px;
border: 1px solid var(--brd-color);
border-radius: 8px;
background-color: var(--bg-color);
}
legend {
display: flex;
align-items: center;
justify-content: center;
color: var(--fg-color);
font-weight: 700;
cursor: pointer;
}
fieldset textarea,
fieldset pre {
resize: vertical;
}
fieldset.blue {
border-color: #57b5f9;
i {
margin-left: 8px;
}
}
fieldset.blue legend {
color: #57b5f9;
}
fieldset.gray {
border-color: #9B9B9B;
}
fieldset.gray legend {
color: #9B9B9B;
}
fieldset.green {
border-color: #B8E986;
color: #bcc2cd;
}
fieldset.green legend {
color: #B8E986;
}
fieldset.cyan {
border-color: #50E3C2;
color: #50fa7b;
}
fieldset.cyan legend {
color: #50E3C2;
}
fieldset.blue-dark {
border-color: #4A90E2;
}
fieldset.blue-dark legend {
color: #4A90E2;
}
fieldset.purple {
border-color: #C198FB;
color: #8be9fd;
}
fieldset.purple legend {
color: #C198FB;
}
fieldset.orange {
border-color: #F5A623;
color: #bd93f9;
}
fieldset.orange legend {
color: #F5A623;
color: #ffb86c;
}
fieldset.pink legend {
color: #ff79c6;
}
fieldset.red legend {
color: #ff5555;
}
fieldset.yellow legend {
color: #f1fa8c;
}
.hidden {
@@ -185,21 +301,44 @@ option,
textarea,
pre {
margin: 4px;
padding: 8px 16px;
border-radius: 4px;
width: calc(100% - 8px);
padding: 8px;
border-radius: 8px;
background-color: var(--bg-dark-color);
color: var(--fg-color);
font-weight: 700;
font-size: 18px;
font-family: monospace;
font-size: 16px;
font-family: 'Roboto Mono', monospace;
transition: all 0.2s ease-in-out;
user-select: text;
width: calc(100% - 8px);
min-height: 40px;
resize: vertical;
text-overflow: ellipsis;
&:not([readonly]):hover {
background-color: var(--bg-dark-color);
}
}
pre {
display: grid;
}
code {
height: 336px;
border-radius: 8px;
}
.hljs,
.hljs-subst {
background-color: var(--bg-dark-color) !important;
color: var(--fg-color) !important;
font-family: 'Roboto Mono', monospace;
}
// Force the same height, for dropdowns and regular input boxes.
select,
input,
option {
height: 41px;
height: 40px;
}
input[type="checkbox"] {
@@ -212,8 +351,8 @@ input[type="checkbox"] {
&:before {
content: "\2714";
border: 2px solid var(--fg-color);
border-radius: 4px;
border: 1px solid var(--fg-color);
border-radius: 8px;
display: inline-flex;
height: 16px;
width: 16px;
@@ -221,7 +360,7 @@ input[type="checkbox"] {
justify-content: center;
margin: 8px 8px 8px 0;
color: transparent;
transition: .2s;
transition: all 0.2s ease-in-out;
}
}
@@ -232,25 +371,29 @@ input[type="checkbox"] {
}
}
.error {
background-color: var(--err-color);
}
.error,
.disabled,
input[disabled],
button[disabled] {
[disabled] {
background-color: var(--err-color);
color: #b2b2b2;
color: var(--fg-light-color);
fill: var(--fg-light-color);
cursor: default;
&.icon {
color: var(--bg-color);
fill: var(--bg-color);
}
}
label {
padding: 4px;
color: var(--fg-light-color);
}
ul,
ol {
display: flex;
margin: 8px 0 0;
margin: 4px 0 4px;
padding: 0;
list-style-type: none;
}
@@ -264,8 +407,12 @@ ol li {
.flex-wrap {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: space-between;
}
.show-on-small-screen {
display: flex;
}
@media (max-width: $responsiveWidth) {
@@ -293,6 +440,10 @@ ol li {
.hide-on-small-screen {
display: none;
}
.show-on-small-screen {
display: inline-flex;
}
}
#installPWA {
@@ -300,27 +451,27 @@ ol li {
}
.info-response {
background-color: #FFEB3B;
color: #ffeb3b;
}
.success-response {
background-color: #4BB543;
color: #4bb543;
}
.redir-response {
background-color: #FF5722;
color: #ff5722;
}
.cl-error-response {
background-color: #A63232;
color: #a63232;
}
.sv-error-response {
background-color: #B71C1C;
color: #b71c1c;
}
.missing-data-response {
background-color: #676767;
background-color: var(--err-color);
}
.virtual-list::-webkit-scrollbar {
@@ -333,9 +484,9 @@ fieldset#history {
span {
position: absolute;
top: 44px;
right: 20px;
font-family: monospace, monospace;
top: 12px;
right: 12px;
font-family: 'Roboto Mono', monospace;
}
}
}
@@ -355,15 +506,11 @@ fieldset#history {
#response-details-wrapper {
position: relative;
overflow: hidden;
border-radius: 4px;
margin: 4px;
border-radius: 8px;
textarea {
width: 100%;
}
#response-details {
margin: 0;
width: 100%;
}
.covers-response {
@@ -378,7 +525,7 @@ fieldset#history {
}
}
#action {
#send {
#hidden-message {
display: none;
}
@@ -396,3 +543,37 @@ fieldset#history {
}
}
}
section {
display: flex;
flex-wrap: wrap;
}
div.tab {
width: 100%;
order: 1;
}
input[type="radio"],
div.tab {
display: none;
}
input[type="radio"] + label {
padding: 8px 16px;
border-bottom: 2px solid transparent;
cursor: pointer;
transition: all 0.2s ease-in-out;
&:hover {
border-color: var(--brd-color);
}
}
input[type="radio"]:checked + label {
border-color: var(--fg-color);
}
input[type="radio"]:checked + label + div.tab {
display: block;
}

View File

@@ -3,41 +3,116 @@
- dark (default)
- light
- black
- auto
*/
// Dark is the default theme variant.
:root {
--bg-dark-color: #000000;
// Dark Background color
--bg-dark-color: rgb(41, 42, 45);
// Background color
--bg-color: #121212;
// Auto-complete color
--atc-color: #212121;
--bg-color: rgb(37, 38, 40);
// Auto-complete color
--atc-color: rgb(49, 49, 55);
// Text color
--fg-color: #FFF;
--fg-color: rgb(247, 248, 248);
// Light Text color
--fg-light-color: rgb(150, 155, 160);
// Border color
--brd-color: rgb(48, 47, 55);
// Error color
--err-color: #393939;
// Active color
--ac-color: #51FF0D;
--err-color: rgb(41, 42, 45);
// Acent color
--ac-color: #50fa7b;
// Active text color
--act-color: #121212;
--act-color: rgb(37, 38, 40);
}
:root.light {
--bg-dark-color: #ffffff;
// Dark Background color
--bg-dark-color: #f6f6f6;
// Background color
--bg-color: #F6F8FA;
// Auto-complete color
--atc-color: #F1F1F1;
--bg-color: #ffffff;
// Auto-complete color
--atc-color: #ebebeb;
// Text color
--fg-color: #121212;
--fg-color: #525252;
// Light Text color
--fg-light-color: rgb(150, 155, 160);
// Border color
--brd-color: #eeeeed;
// Error color
--err-color: invert(#393939, 1);
// Active color
--ac-color: #51FF0D;
--err-color: #f6f6f6;
// Acent color
--ac-color: #57b5f9;
// Active text color
--act-color: #121212;
--act-color: #ffffff;
}
:root.black {
// Dark Background color
--bg-dark-color: rgb(8, 8, 8);
// Background color
--bg-color: #000000;
// Auto-complete color
--atc-color: rgb(18, 18, 18);
// Text color
--fg-color: rgb(250, 250, 250);
// Light Text color
--fg-light-color: rgb(100, 100, 100);
// Border color
--brd-color: rgb(16, 16, 16);
// Error color
--err-color: rgb(8, 8, 8);
// Acent color
--ac-color: #50fa7b;
// Active text color
--act-color: #000000;
}
@media(prefers-color-scheme: light) {
:root.auto {
// Dark Background color
--bg-dark-color: #f6f6f6;
// Background color
--bg-color: #ffffff;
// Auto-complete color
--atc-color: #ebebeb;
// Text color
--fg-color: #525252;
// Light Text color
--fg-light-color: rgb(150, 155, 160);
// Border color
--brd-color: #eeeeed;
// Error color
--err-color: #f6f6f6;
// Acent color
--ac-color: #57b5f9;
// Active text color
--act-color: #ffffff;
}
}
@media(prefers-color-scheme: dark) {
:root.auto {
// Dark Background color
--bg-dark-color: rgb(41, 42, 45);
// Background color
--bg-color: rgb(37, 38, 40);
// Auto-complete color
--atc-color: rgb(49, 49, 55);
// Text color
--fg-color: rgb(247, 248, 248);
// Light Text color
--fg-light-color: rgb(150, 155, 160);
// Border color
--brd-color: rgb(48, 47, 55);
// Error color
--err-color: rgb(41, 42, 45);
// Acent color
--ac-color: #50fa7b;
// Active text color
--act-color: rgb(37, 38, 40);
}
}

223
assets/js/curlparser.js Normal file
View File

@@ -0,0 +1,223 @@
import * as cookie from "cookie";
import * as URL from "url";
import * as querystring from "querystring";
/**
* given this: [ 'msg1=value1', 'msg2=value2' ]
* output this: 'msg1=value1&msg2=value2'
* @param dataArguments
*/
function joinDataArguments(dataArguments) {
let data = '';
dataArguments.forEach(function(argument, i) {
if (i === 0) {
data += argument;
} else {
data += '&' + argument;
}
})
return data;
}
function parseCurlCommand(curlCommand) {
let newlineFound = /\r|\n/.exec(curlCommand);
if (newlineFound) {
// remove newlines
curlCommand = curlCommand.replace(/\\\r|\\\n/g, '');
}
// yargs parses -XPOST as separate arguments. just prescreen for it.
curlCommand = curlCommand.replace(/ -XPOST/, ' -X POST');
curlCommand = curlCommand.replace(/ -XGET/, ' -X GET');
curlCommand = curlCommand.replace(/ -XPUT/, ' -X PUT');
curlCommand = curlCommand.replace(/ -XPATCH/, ' -X PATCH');
curlCommand = curlCommand.replace(/ -XDELETE/, ' -X DELETE');
curlCommand = curlCommand.trim();
let parsedArguments = require("yargs-parser")(curlCommand);
let cookieString;
let cookies;
let url = parsedArguments._[1];
if (!url) {
for (let argName in parsedArguments) {
if (typeof parsedArguments[argName] === 'string') {
if (['http', 'www.'].includes(parsedArguments[argName])) {
url = parsedArguments[argName];
}
}
}
}
let headers;
let parseHeaders = function(headerFieldName) {
if (parsedArguments[headerFieldName]) {
if (!headers) {
headers = {};
}
if (!Array.isArray(parsedArguments[headerFieldName])) {
parsedArguments[headerFieldName] = [parsedArguments[headerFieldName]];
}
parsedArguments[headerFieldName].forEach(function(header) {
if (header.includes('Cookie')) {
// stupid javascript tricks: closure
cookieString = header;
} else {
let colonIndex = header.indexOf(':');
let headerName = header.substring(0, colonIndex);
let headerValue = header.substring(colonIndex + 1).trim();
headers[headerName] = headerValue;
}
})
}
}
parseHeaders('H');
parseHeaders('header');
if (parsedArguments.A) {
if (!headers) {
headers = [];
}
headers['User-Agent'] = parsedArguments.A;
} else if (parsedArguments['user-agent']) {
if (!headers) {
headers = [];
}
headers['User-Agent'] = parsedArguments['user-agent'];
}
if (parsedArguments.b) {
cookieString = parsedArguments.b;
}
if (parsedArguments.cookie) {
cookieString = parsedArguments.cookie;
}
let multipartUploads;
if (parsedArguments.F) {
multipartUploads = {};
if (!Array.isArray(parsedArguments.F)) {
parsedArguments.F = [parsedArguments.F];
}
parsedArguments.F.forEach(function(multipartArgument) {
// input looks like key=value. value could be json or a file path prepended with an @
let splitArguments = multipartArgument.split('=', 2);
let key = splitArguments[0];
let value = splitArguments[1];
multipartUploads[key] = value;
})
}
if (cookieString) {
let cookieParseOptions = {
decode: function(s) { return s }
}
// separate out cookie headers into separate data structure
// note: cookie is case insensitive
cookies = cookie.parse(cookieString.replace(/^Cookie: /gi, ''), cookieParseOptions);
}
let method;
if (parsedArguments.X === 'POST') {
method = 'post';
} else if (parsedArguments.X === 'PUT' ||
parsedArguments['T']) {
method = 'put';
} else if (parsedArguments.X === 'PATCH') {
method = 'patch';
} else if (parsedArguments.X === 'DELETE') {
method = 'delete';
} else if (parsedArguments.X === 'OPTIONS') {
method = 'options';
} else if ((parsedArguments['d'] ||
parsedArguments['data'] ||
parsedArguments['data-ascii'] ||
parsedArguments['data-binary'] ||
parsedArguments['F'] ||
parsedArguments['form']) && !((parsedArguments['G'] || parsedArguments['get']))) {
method = 'post';
} else if (parsedArguments['I'] ||
parsedArguments['head']) {
method = 'head';
} else {
method = 'get';
}
let compressed = !!parsedArguments.compressed;
let urlObject = URL.parse(url); // eslint-disable-line
// if GET request with data, convert data to query string
// NB: the -G flag does not change the http verb. It just moves the data into the url.
if (parsedArguments['G'] || parsedArguments['get']) {
urlObject.query = urlObject.query ? urlObject.query : '';
let option = 'd' in parsedArguments ? 'd' : 'data' in parsedArguments ? 'data' : null;
if (option) {
let urlQueryString = '';
if (!url.includes('?')) {
url += '?';
} else {
urlQueryString += '&';
}
if (typeof(parsedArguments[option]) === 'object') {
urlQueryString += parsedArguments[option].join('&');
} else {
urlQueryString += parsedArguments[option];
}
urlObject.query += urlQueryString;
url += urlQueryString;
delete parsedArguments[option];
}
}
let query = querystring.parse(urlObject.query, null, null, { maxKeys: 10000 });
urlObject.search = null // Clean out the search/query portion.
let request = {
url: url,
urlWithoutQuery: URL.format(urlObject)
}
if (compressed) {
request['compressed'] = true;
}
if (Object.keys(query).length > 0) {
request.query = query;
}
if (headers) {
request.headers = headers;
}
request['method'] = method;
if (cookies) {
request.cookies = cookies;
request.cookieString = cookieString.replace('Cookie: ', '');
}
if (multipartUploads) {
request.multipartUploads = multipartUploads;
}
if (parsedArguments.data) {
request.data = parsedArguments.data;
} else if (parsedArguments['data-binary']) {
request.data = parsedArguments['data-binary']
request.isDataBinary = true;
} else if (parsedArguments['d']) {
request.data = parsedArguments['d'];
} else if (parsedArguments['data-ascii']) {
request.data = parsedArguments['data-ascii'];
}
if (parsedArguments['u']) {
request.auth = parsedArguments['u'];
}
if (parsedArguments['user']) {
request.auth = parsedArguments['user'];
}
if (Array.isArray(request.data)) {
request.dataArray = request.data
request.data = joinDataArguments(request.data);
}
if (parsedArguments['k'] || parsedArguments['insecure']) {
request.insecure = true;
}
return request;
}
export default parseCurlCommand;

View File

@@ -23,7 +23,7 @@ export default () => {
// Show the install button if the prompt appeared.
if (!pwaInstalled) {
document.querySelector('#installPWA').style.display = 'block';
document.querySelector('#installPWA').style.display = 'inline-flex';
}
});

View File

@@ -1,46 +1,54 @@
const axios = require('axios');
const fs = require('fs');
const { spawnSync } = require('child_process');
const runCommand = (command, args) => spawnSync(command, args).stdout.toString().replace(/\n/g, "");
const axios = require("axios");
const fs = require("fs");
const { spawnSync } = require("child_process");
const runCommand = (command, args) =>
spawnSync(command, args)
.stdout.toString()
.replace(/\n/g, "");
const FAIL_ON_ERROR = false;
const PW_BUILD_DATA_DIR = "./.postwoman";
const IS_DEV_MODE = process.argv.includes("--dev");
try {
(async () => {
// Create the build data directory if it does not exist.
if (!fs.existsSync(PW_BUILD_DATA_DIR)) {
fs.mkdirSync(PW_BUILD_DATA_DIR);
}
(async () => {
// Create the build data directory if it does not exist.
if (!fs.existsSync(PW_BUILD_DATA_DIR)) {
fs.mkdirSync(PW_BUILD_DATA_DIR);
}
let version = {};
// Get the current version name as the tag from Git.
version.name = process.env.TRAVIS_TAG || runCommand("git", ["tag"]);
let version = {};
// Get the current version name as the tag from Git.
version.name = (process.env.TRAVIS_TAG || runCommand('git', ['tag']));
// FALLBACK: If version.name was unset, let's grab it from GitHub.
if (!version.name) {
version.name = (await axios
.get("https://api.github.com/repos/liyasthomas/postwoman/releases")
// If we can't get it from GitHub, we'll resort to getting it from package.json
.catch(ex => ({
data: [{ tag_name: require("./package.json").version }]
}))).data[0]["tag_name"];
}
// FALLBACK: If version.name was unset, let's grab it from GitHub.
if(!version.name){
version.name = (
await axios.get("https://api.github.com/repos/liyasthomas/postwoman/releases")
// If we can't get it from GitHub, we'll resort to getting it from package.json
.catch(
(ex) => ({ data: [{ 'tag_name': require('./package.json').version }] })
)
).data[0]['tag_name'];
}
// Get the current version hash as the short hash from Git.
version.hash = runCommand("git", ["rev-parse", "--short", "HEAD"]);
// Get the 'variant' name as the branch, if it's not master.
version.variant =
process.env.TRAVIS_BRANCH ||
runCommand("git", ["branch"])
.split("* ")[1]
.split(" ")[0] + (IS_DEV_MODE ? " - DEV MODE" : "");
if (["", "master"].includes(version.variant))
delete version.variant;
// Get the current version hash as the short hash from Git.
version.hash = runCommand('git', ['rev-parse', '--short', 'HEAD']);
// Get the 'variant' name as the branch, if it's not master.
version.variant = (process.env.TRAVIS_BRANCH || runCommand('git', ['branch']).split("* ")[1].split(" ")[0] + (IS_DEV_MODE ? " - DEV MODE" : ""));
if(version.variant === "" || version.variant === "master") delete version.variant;
// Write version data into a file
fs.writeFileSync(PW_BUILD_DATA_DIR + "/version.json", JSON.stringify(version));
})();
}catch(ex){
console.error(ex);
process.exit(FAIL_ON_ERROR ? 1 : 0);
// Write version data into a file
fs.writeFileSync(
PW_BUILD_DATA_DIR + "/version.json",
JSON.stringify(version)
);
})();
} catch (ex) {
console.error(ex);
process.exit(FAIL_ON_ERROR ? 1 : 0);
}

View File

@@ -1,190 +1,204 @@
<template>
<div class="autocomplete-wrapper">
<label>
<slot />
<input type="text"
:placeholder="placeholder"
v-model="value"
@input="updateSuggestions"
@keyup="updateSuggestions"
@click="updateSuggestions"
@keydown="handleKeystroke"
ref="acInput"
:spellcheck="spellcheck"
:autocapitalize="spellcheck"
:autocorrect="spellcheck">
<ul class="suggestions" v-if="suggestions.length > 0 && suggestionsVisible" :style="{ transform: `translate(${suggestionsOffsetLeft}px, 0)` }">
<li v-for="(suggestion, index) in suggestions" @click.prevent="forceSuggestion(suggestion)" :class="{ active: currentSuggestionIndex === index }">{{ suggestion }}</li>
</ul>
</label>
</div>
<div class="autocomplete-wrapper">
<input
type="text"
:placeholder="placeholder"
v-model="value"
@input="updateSuggestions"
@keyup="updateSuggestions"
@click="updateSuggestions"
@keydown="handleKeystroke"
ref="acInput"
:spellcheck="spellcheck"
:autocapitalize="spellcheck"
:autocorrect="spellcheck"
/>
<ul
class="suggestions"
v-if="suggestions.length > 0 && suggestionsVisible"
:style="{ transform: `translate(${suggestionsOffsetLeft}px, 0)` }"
>
<li
v-for="(suggestion, index) in suggestions"
@click.prevent="forceSuggestion(suggestion)"
:class="{ active: currentSuggestionIndex === index }"
:key="index"
>{{ suggestion }}</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.autocomplete-wrapper {
position: relative;
position: relative;
input:focus + ul.suggestions,
ul.suggestions:hover {
display: block;
}
input:focus + ul.suggestions, ul.suggestions:hover {
display: block;
}
ul.suggestions {
display: none;
background-color: var(--atc-color);
position: absolute;
top: calc(100% - 4px);
margin: 0 4px;
left: 0;
padding: 0;
border-radius: 0 0 4px 4px;
z-index: 9999;
transition: transform 200ms ease-out;
ul.suggestions {
display: none;
background-color: var(--atc-color);
position: absolute;
top: 90%;
margin: 0 4px;
left: 0;
li {
width: 100%;
display: block;
padding: 8px 16px;
font-size: 18px;
font-family: 'Roboto Mono', monospace;
white-space: pre-wrap;
padding: 0;
border-radius: 0 0 4px 4px;
z-index: 9999;
transition: transform 200ms ease-out;
li {
width: 100%;
display: block;
margin: 5px 0;
padding: 10px 10px;
font-weight: 700;
font-size: 18px;
font-family: monospace;
white-space: pre-wrap;
&:hover, &.active {
background-color: var(--ac-color);
}
}
}
&:last-child {
border-radius: 0 0 4px 4px;
}
&:hover,
&.active {
background-color: var(--ac-color);
color: var(--act-color);
cursor: pointer;
}
}
}
}
</style>
<script>
const KEY_TAB = 9;
const KEY_ESC = 27;
const KEY_TAB = 9;
const KEY_ESC = 27;
const KEY_ARROW_UP = 38;
const KEY_ARROW_DOWN = 40;
const KEY_ARROW_UP = 38;
const KEY_ARROW_DOWN = 40;
export default {
props: {
spellcheck: {
type: Boolean,
default: true,
required: false
},
export default {
props: {
spellcheck: {
type: Boolean,
default: true,
required: false
},
placeholder: {
type: String,
default: 'Start typing...',
required: false
},
placeholder: {
type: String,
default: "Start typing...",
required: false
},
source: {
type: Array,
required: true
},
source: {
type: Array,
required: true
}
},
value: {}
},
watch: {
value() {
this.$emit("input", this.value);
}
},
watch: {
value () {
this.$emit('input', this.value);
}
},
data() {
return {
value: "application/json",
selectionStart: 0,
suggestionsOffsetLeft: 0,
currentSuggestionIndex: -1,
suggestionsVisible: false
};
},
data () {
return {
value: "",
selectionStart: 0,
suggestionsOffsetLeft: 0,
currentSuggestionIndex: -1,
suggestionsVisible: false
}
},
methods: {
updateSuggestions (event) {
// Hide suggestions if ESC pressed.
if(event.which && event.which === KEY_ESC){
event.preventDefault();
this.suggestionsVisible = false;
this.currentSuggestionIndex = -1;
return;
}
// As suggestions is a reactive property, this implicitly
// causes suggestions to update.
this.selectionStart = this.$refs.acInput.selectionStart;
this.suggestionsOffsetLeft = (12 * this.selectionStart);
this.suggestionsVisible = true;
},
forceSuggestion (text) {
let input = this.value.substring(0, this.selectionStart);
this.value = input + text;
this.selectionStart = this.value.length;
this.suggestionsVisible = true;
this.currentSuggestionIndex = -1;
},
handleKeystroke (event) {
if(event.which === KEY_ARROW_UP){
event.preventDefault();
this.currentSuggestionIndex = this.currentSuggestionIndex - 1 >= 0
? this.currentSuggestionIndex - 1
: 0;
}else if(event.which === KEY_ARROW_DOWN){
event.preventDefault();
this.currentSuggestionIndex = this.currentSuggestionIndex < this.suggestions.length - 1
? this.currentSuggestionIndex + 1
: this.suggestions.length - 1;
}
if(event.which === KEY_TAB){
event.preventDefault();
let activeSuggestion = this.suggestions[this.currentSuggestionIndex >= 0 ? this.currentSuggestionIndex : 0];
if(activeSuggestion){
let input = this.value.substring(0, this.selectionStart);
this.value = input + activeSuggestion;
}
}
}
},
computed: {
/**
* Gets the suggestions list to be displayed under the input box.
*
* @returns {default.props.source|{type, required}}
*/
suggestions () {
let input = this.value.substring(0, this.selectionStart);
return this.source.filter((entry) => {
return entry.toLowerCase().startsWith(input.toLowerCase())
&& input.toLowerCase() !== entry.toLowerCase();
})
// Cut off the part that's already been typed.
.map((entry) => entry.substring(this.selectionStart))
// We only want the top 3 suggestions.
.slice(0, 3);
}
},
mounted () {
this.updateSuggestions({
target: this.$refs.acInput
});
methods: {
updateSuggestions(event) {
// Hide suggestions if ESC pressed.
if (event.which && event.which === KEY_ESC) {
event.preventDefault();
this.suggestionsVisible = false;
this.currentSuggestionIndex = -1;
return;
}
// As suggestions is a reactive property, this implicitly
// causes suggestions to update.
this.selectionStart = this.$refs.acInput.selectionStart;
this.suggestionsOffsetLeft = 12 * this.selectionStart;
this.suggestionsVisible = true;
},
forceSuggestion(text) {
let input = this.value.substring(0, this.selectionStart);
this.value = input + text;
this.selectionStart = this.value.length;
this.suggestionsVisible = true;
this.currentSuggestionIndex = -1;
},
handleKeystroke(event) {
switch (event.which) {
case KEY_ARROW_UP:
event.preventDefault();
this.currentSuggestionIndex =this.currentSuggestionIndex - 1 >= 0 ? this.currentSuggestionIndex - 1 : 0;
break;
case KEY_ARROW_DOWN:
event.preventDefault();
this.currentSuggestionIndex = this.currentSuggestionIndex < this.suggestions.length - 1 ? this.currentSuggestionIndex + 1
: this.suggestions.length - 1;
break;
case KEY_TAB:
event.preventDefault();
let activeSuggestion = this.suggestions[this.currentSuggestionIndex >= 0 ? this.currentSuggestionIndex : 0];
if (activeSuggestion) {
let input = this.value.substring(0, this.selectionStart);
this.value = input + activeSuggestion;
}
break;
default:
break;
}
}
},
computed: {
/**
* Gets the suggestions list to be displayed under the input box.
*
* @returns {default.props.source|{type, required}}
*/
suggestions() {
let input = this.value.substring(0, this.selectionStart);
return (
this.source
.filter(entry => {
return (
entry.toLowerCase().startsWith(input.toLowerCase()) &&
input.toLowerCase() !== entry.toLowerCase()
);
})
// Cut off the part that's already been typed.
.map(entry => entry.substring(this.selectionStart))
// We only want the top 3 suggestions.
.slice(0, 3)
);
}
},
mounted() {
this.updateSuggestions({
target: this.$refs.acInput
});
}
};
</script>

View File

@@ -2,41 +2,56 @@
<pw-section class="gray" label="History">
<ul>
<li id="filter-history">
<label for="filter-history-input">Search History</label>
<input id="filter-history-input" type="text" :disabled="history.length === 0 || isClearingHistory" v-model="filterText">
<input aria-label="Search" type="text" placeholder="search history" :readonly="history.length === 0" v-model="filterText">
</li>
</ul>
<virtual-list class="virtual-list" :class="{filled: filteredHistory.length}" :size="89" :remain="Math.min(5, filteredHistory.length)">
<ul v-for="entry in filteredHistory" :key="entry.time" class="entry">
<ul>
<li @click="sort_by_label()">
<label for="" class="flex-wrap">Label<i class="material-icons">sort</i></label>
</li>
<li @click="sort_by_time()">
<label for="" class="flex-wrap">Time<i class="material-icons">sort</i></label>
</li>
<li @click="sort_by_status_code()">
<label for="" class="flex-wrap">Status<i class="material-icons">sort</i></label>
</li>
<li @click="sort_by_url()">
<label for="" class="flex-wrap">URL<i class="material-icons">sort</i></label>
</li>
<li @click="sort_by_path()">
<label for="" class="flex-wrap">Path<i class="material-icons">sort</i></label>
</li>
</ul>
<virtual-list class="virtual-list" :class="{filled: filteredHistory.length}" :size="54" :remain="Math.min(5, filteredHistory.length)">
<ul v-for="(entry, index) in filteredHistory" :key="index" class="entry">
<li>
<label :for="'time#' + entry.time">Time</label>
<input :id="'time#' + entry.time" type="text" readonly :value="entry.time" :title="entry.date">
<input aria-label="Label" type="text" readonly :value="entry.label" placeholder="No label">
</li>
<li>
<input aria-label="Time" type="text" readonly :value="entry.time" :title="entry.date">
</li>
<li class="method-list-item">
<label :for="'time#' + entry.time">Method</label>
<input :id="'method#' + entry.time" type="text" readonly :value="entry.method" :class="findEntryStatus(entry).className" :style="{'--status-code': entry.status}">
<span class="entry-status-code">{{entry.status}}</span>
<input aria-label="Method" type="text" readonly :value="entry.method" :class="findEntryStatus(entry).className" :style="{'--status-code': entry.status}">
<span class="entry-status-code" :class="findEntryStatus(entry).className" :style="{'--status-code': entry.status}">{{entry.status}}</span>
</li>
<li>
<label :for="'url#' + entry.time">URL</label>
<input :id="'url#' + entry.time" type="text" readonly :value="entry.url">
<input aria-label="URL" type="text" readonly :value="entry.url">
</li>
<li>
<label :for="'path#' + entry.time">Path</label>
<input :id="'path#' + entry.time" type="text" readonly :value="entry.path">
</li>
<li>
<label :for="'delete-button#' + entry.time" class="hide-on-small-screen">&nbsp;</label>
<button :id="'delete-button#' + entry.time" :disabled="isClearingHistory" @click="deleteHistory(entry)">
Delete
</button>
</li>
<li>
<label :for="'use-button#' + entry.time" class="hide-on-small-screen">&nbsp;</label>
<button :id="'use-button#' + entry.time" :disabled="isClearingHistory" @click="useHistory(entry)">
Use
</button>
<input aria-label="Path" type="text" readonly :value="entry.path" placeholder="No path">
</li>
<div class="show-on-small-screen">
<li>
<button v-tooltip="'Delete'" class="icon" :id="'delete-button#'+index" @click="deleteHistory(entry)" aria-label="Delete">
<i class="material-icons">delete</i>
</button>
</li>
<li>
<button v-tooltip="'Edit'" class="icon" :id="'use-button#'+index" @click="useHistory(entry)" aria-label="Edit">
<i class="material-icons">edit</i>
</button>
</li>
</div>
</ul>
</virtual-list>
<ul :class="{hidden: filteredHistory.length != 0 || history.length === 0 }">
@@ -44,20 +59,26 @@
<label>Nothing found for "{{filterText}}"</label>
</li>
</ul>
<ul>
<ul v-if="history.length === 0">
<li>
<label>History is empty</label>
</li>
</ul>
<ul v-if="history.length !== 0">
<li v-if="!isClearingHistory">
<button id="clear-history-button" :disabled="history.length === 0" @click="enableHistoryClearing">
Clear History
<button class="icon" id="clear-history-button" :disabled="history.length === 0" @click="enableHistoryClearing">
<i class="material-icons">clear_all</i>
<span>Clear All</span>
</button>
</li>
<li v-else>
<div class="flex-wrap">
<label for="clear-history-button">Are you sure?</label>
<div>
<button id="confirm-clear-history-button" @click="clearHistory">
<button class="icon" id="confirm-clear-history-button" @click="clearHistory">
Yes
</button>
<button id="reject-clear-history-button" @click="disableHistoryClearing">
<button class="icon" id="reject-clear-history-button" @click="disableHistoryClearing">
No
</button>
</div>
@@ -85,7 +106,12 @@
history: localStorageHistory || [],
filterText: '',
showFilter: false,
isClearingHistory: false
isClearingHistory: false,
reverse_sort_label: false,
reverse_sort_time: false,
reverse_sort_status_code: false,
reverse_sort_url: false,
reverse_sort_path: false
}
},
computed: {
@@ -106,6 +132,9 @@
this.filterText = '';
this.disableHistoryClearing();
updateOnLocalStorage('history', this.history);
this.$toast.error('History Deleted', {
icon: 'delete'
});
},
useHistory(entry) {
this.$emit('useHistory', entry);
@@ -122,6 +151,9 @@
this.filterText = '';
}
updateOnLocalStorage('history', this.history);
this.$toast.error('Deleted', {
icon: 'delete'
});
},
addEntry(entry) {
this.history.push(entry);
@@ -133,6 +165,67 @@
},
disableHistoryClearing() {
this.isClearingHistory = false;
},
sort_by_time() {
let byDate = this.history.slice(0);
byDate.sort((a,b) =>{
let date_a = a.date.split("/");
let date_b = b.date.split("/");
let time_a = a.time.split(":")
let time_b = b.time.split(":")
let final_a = new Date(date_a[2], date_a[1], date_a[0], time_a[0], time_a[1], time_a[2]);
let final_b = new Date(date_b[2], date_b[1], date_b[0], time_b[0], time_b[1], time_b[2]);
if(this.reverse_sort_time)
return final_b - final_a;
else
return final_a - final_b;
})
this.history = byDate;
this.reverse_sort_time = !this.reverse_sort_time;
},
sort_by_status_code() {
let byCode = this.history.slice(0);
byCode.sort((a,b) =>{
if(this.reverse_sort_status_code)
return b.status - a.status;
else
return a.status - b.status;
})
this.history = byCode;
this.reverse_sort_status_code = !this.reverse_sort_status_code;
},
sort_by_url() {
let byUrl = this.history.slice(0);
byUrl.sort((a, b)=>{
if(this.reverse_sort_url)
return a.url == b.url ? 0 : +(a.url < b.url) || -1;
else
return a.url == b.url ? 0 : +(a.url > b.url) || -1;
});
this.history = byUrl;
this.reverse_sort_url = !this.reverse_sort_url;
},
sort_by_label() {
let byLabel = this.history.slice(0);
byLabel.sort((a, b)=>{
if(this.reverse_sort_label)
return a.label == b.label ? 0 : +(a.label < b.label) || -1;
else
return a.label == b.label ? 0 : +(a.label > b.label) || -1;
});
this.history = byLabel;
this.reverse_sort_label = !this.reverse_sort_label;
},
sort_by_path() {
let byPath = this.history.slice(0);
byPath.sort((a, b)=>{
if(this.reverse_sort_path)
return a.path == b.path ? 0 : +(a.path < b.path) || -1;
else
return a.path == b.path ? 0 : +(a.path > b.path) || -1;
});
this.history = byPath;
this.reverse_sort_path = !this.reverse_sort_path;
}
}
}
@@ -147,7 +240,7 @@
@media (max-width: 720px) {
.virtual-list.filled {
min-height: 430px;
min-height: 200px;
}
}

View File

@@ -1,29 +1,32 @@
<template>
<svg version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 612.001 612.001" style="enable-background:new 0 0 612.001 612.001;" xml:space="preserve">
<defs id="defs11" />
<g id="g3826" transform="translate(-516.40798,-163.88978)">
<circle transform="scale(1,-1)" style="stroke-width:1.19531453" r="178.70923" cy="-501.55591" cx="822.40845" id="circle3814" />
<g id="g3820" transform="translate(516.40798,163.89028)">
<g id="g3818">
<path :fill="color" id="path3816" data-old_color="#121212" class="active-path" data-original="#121212" d="M 64.601,236.822 C 64.601,394.256 192.786,612 306.001,612 412.582,612 547.4,394.256 547.4,236.822 547.4,79.388 439.322,0 306,0 172.678,0 64.601,79.388 64.601,236.822 Z m 304.12,116.415 c 29.475,-29.475 70.598,-40.195 108.552,-32.173 8.021,37.954 -2.698,79.077 -32.173,108.552 -29.475,29.475 -70.598,40.195 -108.552,32.173 -8.022,-37.955 2.698,-79.078 32.173,-108.552 z M 134.727,321.063 c 37.954,-8.021 79.077,2.698 108.552,32.173 29.475,29.475 40.195,70.598 32.173,108.552 -37.954,8.021 -79.077,-2.698 -108.552,-32.173 -29.475,-29.476 -40.194,-70.598 -32.173,-108.552 z" />
</g>
</g>
</g>
</svg>
<svg version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 612.001 612.001" style="enable-background:new 0 0 612.001 612.001;" xml:space="preserve">
<defs id="defs11" />
<g id="g3826" transform="translate(-516.40798,-163.88978)">
<circle :fill="color" transform="scale(1,-1)" style="stroke-width:1.19531453" r="178.70923" cy="-501.55591" cx="822.40845" id="circle3814" />
<g id="g3820" transform="translate(516.40798,163.89028)">
<g id="g3818">
<path :fill="color" id="path3816" data-old_color="#121212" class="active-path" data-original="#121212" d="M 64.601,236.822 C 64.601,394.256 192.786,612 306.001,612 412.582,612 547.4,394.256 547.4,236.822 547.4,79.388 439.322,0 306,0 172.678,0 64.601,79.388 64.601,236.822 Z m 304.12,116.415 c 29.475,-29.475 70.598,-40.195 108.552,-32.173 8.021,37.954 -2.698,79.077 -32.173,108.552 -29.475,29.475 -70.598,40.195 -108.552,32.173 -8.022,-37.955 2.698,-79.078 32.173,-108.552 z M 134.727,321.063 c 37.954,-8.021 79.077,2.698 108.552,32.173 29.475,29.475 40.195,70.598 32.173,108.552 -37.954,8.021 -79.077,-2.698 -108.552,-32.173 -29.475,-29.476 -40.194,-70.598 -32.173,-108.552 z" />
</g>
</g>
</g>
</svg>
</template>
<style>
#path3816 {
fill: var(--ac-color);
}
#circle3814 {
fill: var(--bg-color);
}
#path3816 {
fill: var(--ac-color);
}
</style>
<script>
export default {
props: {
'color': {
type: String
}
}
export default {
props: {
'color': {
type: String
}
}
</script>
}
</script>

74
components/modal.vue Normal file
View File

@@ -0,0 +1,74 @@
<template>
<transition name="modal-fade">
<div class="modal-backdrop">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header"></slot>
</div>
<div class="modal-body">
<slot name="body"></slot>
</div>
<div class="modal-footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</div>
</transition>
</template>
<style scoped>
.modal-backdrop {
position: fixed;
z-index: 998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.modal-wrapper {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
max-width: 800px;
}
.modal-container {
display: flex;
flex-grow: 1;
flex-direction: column;
margin: 8px;
padding: 12px;
transition: all 0.2s ease;
background-color: var(--bg-color);
border-radius: 8px;
box-shadow: rgba(0, 0, 0, 0.5) 0px 16px 70px;
}
/*
* The following styles are auto-applied to elements with
* transition="modal" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the modal transition by editing
* these styles.
*/
.modal-fade-enter,
.modal-fade-leave-active {
opacity: 0;
}
.modal-fade-enter .modal-container,
.modal-fade-leave-active .modal-container {
transform: scale(0.9);
transition: all 0.1s ease-in-out;
}
</style>

View File

@@ -1,47 +1,48 @@
<template>
<fieldset :id="label.toLowerCase()" :class="{ 'no-colored-frames': noFrameColors }">
<legend @click.prevent="collapse">{{ label }} </legend>
<div class="collapsible" :class="{ hidden: collapsed }">
<slot />
</div>
</fieldset>
<fieldset :id="label.toLowerCase()" :class="{ 'no-colored-frames': noFrameColors }">
<legend @click.prevent="collapse"><span>{{ label }}</span><i class="material-icons" v-if="isCollapsed">expand_more</i><i class="material-icons" v-if="!isCollapsed">expand_less</i></legend>
<div class="collapsible" :class="{ hidden: collapsed }">
<slot />
</div>
</fieldset>
</template>
<style>
fieldset.no-colored-frames {
border-color: #afafaf !important;
}
fieldset.no-colored-frames legend {
color: var(--ac-color);
}
fieldset.no-colored-frames legend {
color: var(--fg-color);
}
</style>
<script>
export default {
computed: {
noFrameColors () {
return this.$store.state.postwoman.settings.DISABLE_FRAME_COLORS || false;
}
},
props: {
"label": {
type: String,
default: "Section"
},
"collapsed": {
type: Boolean
}
},
methods: {
collapse({ target }) {
const parent = target.parentNode;
parent.querySelector(".collapsible").classList.toggle('hidden');
},
}
export default {
computed: {
noFrameColors() {
return this.$store.state.postwoman.settings.DISABLE_FRAME_COLORS || false;
},
},
data() {
return {
isCollapsed: false
}
},
props: {
label: {
type: String,
default: "Section"
},
collapsed: {
type: Boolean
}
},
methods: {
collapse({ target }) {
const parent = target.parentNode.parentNode;
parent.querySelector(".collapsible").classList.toggle("hidden");
this.isCollapsed = !this.isCollapsed;
}
}
};
</script>

View File

@@ -1,68 +1,68 @@
<template>
<div class="color" :data-color="color">
<span :style="{backgroundColor: color}" class="preview">
<svg v-if="active" class="activeTick" width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M21 6.285l-11.16 12.733-6.84-6.018 1.319-1.49 5.341 4.686 9.865-11.196 1.475 1.285z"/></svg>
</span>
{{ name || color }}
</div>
<div class="color" :data-color="color">
<span :style="{backgroundColor: color}" class="preview">
<i v-if="active" class="material-icons activeTick">done</i>
</span>
{{ name || color }}
</div>
</template>
<style lang="scss">
.color {
display: inline-block;
vertical-align: middle;
.color {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 16px 0 4px;
margin: 4px;
background-color: var(--bg-dark-color);
color: var(--fg-color);
border-radius: 20px;
cursor: pointer;
height: 40px;
padding: 8px 16px;
margin: 4px;
background-color: rgba(93, 93, 93, 0.2);
border-radius: 4px;
cursor: pointer;
&.active {
background-color: rgba(93, 93, 93, 0.3);
}
.preview {
vertical-align: middle;
display: inline-block;
width: 32px;
height: 32px;
border-radius: 100%;
margin-right: 8px;
position: relative;
.activeTick {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
fill: white;
}
}
&.active {
background-color: var(--bg-dark-color);
}
.color.vibrant {
.preview .activeTick {
fill: black;
}
.preview {
vertical-align: middle;
display: inline-block;
border-radius: 100%;
margin-right: 8px;
padding: 16px;
position: relative;
.activeTick {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #ffffff;
}
}
}
.color.vibrant {
.preview .activeTick {
color: var(--act-color);
}
}
</style>
<script>
export default {
props: {
'color': {
type: String,
required: true
},
'name': {
type: String
},
'active': {
type: Boolean,
default: false
}
}
export default {
props: {
'color': {
type: String,
required: true
},
'name': {
type: String
},
'active': {
type: Boolean,
default: false
}
}
</script>
}
</script>

View File

@@ -4,32 +4,34 @@
<span class="handle"></span>
</label>
<label class="caption">
<slot /></label>
<slot/>
</label>
</div>
</template>
<style lang="scss" scoped>
$useBorder: true;
$borderColor: var(--fg-color);
$useBorder: false;
$borderColor: var(--fg-light-color);
$activeColor: var(--ac-color);
$inactiveColor: var(--fg-color);
$inactiveColor: var(--fg-light-color);
$inactiveHandleColor: $inactiveColor;
$inactiveHandleColor: var(--bg-color);
$activeHandleColor: var(--act-color);
$width: 50px;
$height: 20px;
$width: 32px;
$height: 16px;
$handleSpacing: 4px;
$transition: all 0.2s ease-in-out;
div {
display: inline-block;
cursor: pointer;
}
label.caption {
margin-left: 4px;
vertical-align: middle;
cursor: pointer;
}
label.toggle {
@@ -41,11 +43,12 @@
background-color: if($useBorder, transparent, $inactiveColor);
vertical-align: middle;
border-radius: 100px;
border-radius: 32px;
transition: $transition;
box-sizing: initial;
padding: 0;
margin: 10px 5px;
margin: 8px 4px;
cursor: pointer;
.handle {
position: absolute;
@@ -62,8 +65,6 @@
pointer-events: none;
transition: $transition;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
}
&.on {

9
cypress.json Normal file
View File

@@ -0,0 +1,9 @@
{
"baseUrl": "http://localhost:3000",
"integrationFolder": "tests/e2e/integration",
"screenshotsFolder": "tests/e2e/screenshots",
"fixturesFolder": "tests/e2e/fixtures",
"supportFile": "tests/e2e/support",
"pluginsFile": false,
"video": false
}

7
database.rules.json Normal file
View File

@@ -0,0 +1,7 @@
{
/* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
"rules": {
".read": false,
".write": false
}
}

19
docker-compose.yml Normal file
View File

@@ -0,0 +1,19 @@
version: "3.7"
services:
web:
build:
context: .
volumes:
- "./.postwoman:/app/.postwoman"
- "./assets:/app/assets"
- "./directives:/app/directives"
- "./layouts:/app/layouts"
- "./middleware:/app/middleware"
- "./pages:/app/pages"
- "./plugins:/app/plugins"
- "./static:/app/static"
- "./store:/app/store"
ports:
- "3000:3000"
command: "npm run dev"

22
firebase.json Normal file
View File

@@ -0,0 +1,22 @@
{
"database": {
"rules": "database.rules.json"
},
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"hosting": {
"target": "postwoman",
"public": "dist",
"cleanUrls": true,
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
},
"storage": {
"rules": "storage.rules"
}
}

26
firestore.indexes.json Normal file
View File

@@ -0,0 +1,26 @@
{
// Example:
//
// "indexes": [
// {
// "collectionGroup": "widgets",
// "queryScope": "COLLECTION",
// "fields": [
// { "fieldPath": "foo", "arrayConfig": "CONTAINS" },
// { "fieldPath": "bar", "mode": "DESCENDING" }
// ]
// },
//
// "fieldOverrides": [
// {
// "collectionGroup": "widgets",
// "fieldPath": "baz",
// "indexes": [
// { "order": "ASCENDING", "queryScope": "COLLECTION" }
// ]
// },
// ]
// ]
"indexes": [],
"fieldOverrides": []
}

7
firestore.rules Normal file
View File

@@ -0,0 +1,7 @@
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}

1
functions/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

8
functions/index.js Normal file
View File

@@ -0,0 +1,8 @@
const functions = require('firebase-functions');
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });

1915
functions/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
functions/package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "8"
},
"dependencies": {
"firebase-admin": "^8.0.0",
"firebase-functions": "^3.1.0"
},
"devDependencies": {
"firebase-functions-test": "^0.1.6"
},
"private": true
}

View File

@@ -4,18 +4,17 @@
<div>
<div class="slide-in">
<nuxt-link to="/">
<h1 class="logo">
<logo alt="" style="height: 24px; margin-right: 16px" />Postwoman</h1>
<h1 class="logo"><logo alt="" style="height: 24px; margin-right: 16px"></logo>Postwoman</h1>
</nuxt-link>
<h3>API request builder</h3>
</div>
<nav>
<nuxt-link to="/">HTTP</nuxt-link>
<nuxt-link to="/websocket">WebSocket</nuxt-link>
<nuxt-link to="/settings">
<nuxt-link v-tooltip="'Settings'" to="/settings" aria-label="Settings">
<!-- Settings cog -->
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
<path d="M24 14.187v-4.374c-2.148-.766-2.726-.802-3.027-1.529-.303-.729.083-1.169 1.059-3.223l-3.093-3.093c-2.026.963-2.488 1.364-3.224 1.059-.727-.302-.768-.889-1.527-3.027h-4.375c-.764 2.144-.8 2.725-1.529 3.027-.752.313-1.203-.1-3.223-1.059l-3.093 3.093c.977 2.055 1.362 2.493 1.059 3.224-.302.727-.881.764-3.027 1.528v4.375c2.139.76 2.725.8 3.027 1.528.304.734-.081 1.167-1.059 3.223l3.093 3.093c1.999-.95 2.47-1.373 3.223-1.059.728.302.764.88 1.529 3.027h4.374c.758-2.131.799-2.723 1.537-3.031.745-.308 1.186.099 3.215 1.062l3.093-3.093c-.975-2.05-1.362-2.492-1.059-3.223.3-.726.88-.763 3.027-1.528zm-4.875.764c-.577 1.394-.068 2.458.488 3.578l-1.084 1.084c-1.093-.543-2.161-1.076-3.573-.49-1.396.581-1.79 1.693-2.188 2.877h-1.534c-.398-1.185-.791-2.297-2.183-2.875-1.419-.588-2.507-.045-3.579.488l-1.083-1.084c.557-1.118 1.066-2.18.487-3.58-.579-1.391-1.691-1.784-2.876-2.182v-1.533c1.185-.398 2.297-.791 2.875-2.184.578-1.394.068-2.459-.488-3.579l1.084-1.084c1.082.538 2.162 1.077 3.58.488 1.392-.577 1.785-1.69 2.183-2.875h1.534c.398 1.185.792 2.297 2.184 2.875 1.419.588 2.506.045 3.579-.488l1.084 1.084c-.556 1.121-1.065 2.187-.488 3.58.577 1.391 1.689 1.784 2.875 2.183v1.534c-1.188.398-2.302.791-2.877 2.183zm-7.125-5.951c1.654 0 3 1.346 3 3s-1.346 3-3 3-3-1.346-3-3 1.346-3 3-3zm0-2c-2.762 0-5 2.238-5 5s2.238 5 5 5 5-2.238 5-5-2.238-5-5-5z" />
<path d="M24 13.616v-3.232c-1.651-.587-2.694-.752-3.219-2.019v-.001c-.527-1.271.1-2.134.847-3.707l-2.285-2.285c-1.561.742-2.433 1.375-3.707.847h-.001c-1.269-.526-1.435-1.576-2.019-3.219h-3.232c-.582 1.635-.749 2.692-2.019 3.219h-.001c-1.271.528-2.132-.098-3.707-.847l-2.285 2.285c.745 1.568 1.375 2.434.847 3.707-.527 1.271-1.584 1.438-3.219 2.02v3.232c1.632.58 2.692.749 3.219 2.019.53 1.282-.114 2.166-.847 3.707l2.285 2.286c1.562-.743 2.434-1.375 3.707-.847h.001c1.27.526 1.436 1.579 2.019 3.219h3.232c.582-1.636.75-2.69 2.027-3.222h.001c1.262-.524 2.12.101 3.698.851l2.285-2.286c-.744-1.563-1.375-2.433-.848-3.706.527-1.271 1.588-1.44 3.221-2.021zm-12 2.384c-2.209 0-4-1.791-4-4s1.791-4 4-4 4 1.791 4 4-1.791 4-4 4z"/>
</svg>
</nuxt-link>
</nav>
@@ -24,21 +23,34 @@
<nuxt id="main" />
<footer>
<!-- Top section of footer: GitHub/install links -->
<div>
<div>
<a href="https://github.com/liyasthomas/postwoman" target="_blank"><img id="imgGitHub" src="~static/icons/github.svg" alt="" :style="logoStyle()">GitHub</a>
</div>
<button id="installPWA" @click.prevent="showInstallPrompt()">
Install PWA
<div class="flex-wrap">
<a href="https://github.com/liyasthomas/postwoman" target="_blank" rel="noopener">
<button class="icon">
<img id="imgGitHub" src="~static/icons/github.svg" alt="GitHub" :style="logoStyle()">
<span>GitHub</span>
</button>
</a>
<button class="icon" id="installPWA" @click.prevent="showInstallPrompt()">
<i class="material-icons">add_to_home_screen</i>
<span>Install PWA</span>
</button>
<button class="icon" onClick="window.open('https://twitter.com/share?text=👽 Postwoman • API request builder - Helps you create your requests faster, saving you precious time on your development&url=https://postwoman.io&hashtags=postwoman&via=liyasthomas');">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
<path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"/>
</svg>
<span>Tweet</span>
</button>
</div>
<!-- Bottom section of footer: version/author information -->
<p class="align-center">
<span v-if="version.name">{{ version.name }}
<span v-if="version.hash">- {{ version.hash }}</span>
<span v-if="version.variant"> ({{ version.variant }})</span>
&#x2022; </span>by <a href="https://liyasthomas.web.app" target="_blank">Liyas Thomas 🦄</a>
<span v-if="version.name">
<a v-bind:href="'https://github.com/liyasthomas/postwoman/releases/tag/' + version.name" target="_blank" rel="noopener">{{version.name}}</a>
<span v-if="version.hash">
- <a v-bind:href="'https://github.com/liyasthomas/postwoman/commit/' + version.hash" target="_blank" rel="noopener">{{version.hash}}</a>
</span>
<span v-if="version.variant"> ({{version.variant}})</span>
&#x2022;
</span> by <a href="https://liyasthomas.web.app" target="_blank" rel="noopener">Liyas Thomas 🦄</a> &#x2022; <a href="https://postwoman.launchaco.com" target="_blank" rel="noopener">Subscribe</a>
</p>
</footer>
</div>
@@ -69,7 +81,7 @@
}
footer {
margin: 40px auto;
margin: 32px auto;
}
nav {
@@ -102,12 +114,12 @@
right: 0;
z-index: -1;
background-color: var(--ac-color);
border-radius: 4px;
border-radius: 8px;
margin: auto;
}
&:not(.nuxt-link-exact-active):hover:before {
animation: linkHover 0.3s forwards ease-in-out;
animation: linkHover 0.2s forwards ease-in-out;
}
@keyframes linkHover {
@@ -142,7 +154,7 @@
// prompt.
showInstallPrompt: null,
logoStyle() {
return "margin-right: 16px;" + (((this.$store.state.postwoman.settings.THEME_CLASS || '').includes("light")) ? " filter: invert(100%); -webkit-filter: invert(100%);" : '')
return (((this.$store.state.postwoman.settings.THEME_CLASS || '').includes("light")) ? " filter: invert(100%); -webkit-filter: invert(100%);" : '')
},
version: {}
@@ -158,11 +170,11 @@
// Apply theme from settings.
document.documentElement.className = this.$store.state.postwoman.settings.THEME_CLASS || '';
// Load theme color data from settings, or use default color.
let color = this.$store.state.postwoman.settings.THEME_COLOR || '#51FF0D';
let color = this.$store.state.postwoman.settings.THEME_COLOR || '#50fa7b';
let vibrant = this.$store.state.postwoman.settings.THEME_COLOR_VIBRANT;
if (vibrant == null) vibrant = true;
document.documentElement.style.setProperty('--ac-color', color);
document.documentElement.style.setProperty('--act-color', vibrant ? '#121212' : '#fff');
document.documentElement.style.setProperty('--act-color', vibrant ? 'rgb(37, 38, 40)' : '#ffffff');
})();
},
@@ -171,6 +183,23 @@
// etc.
(async () => {
this.showInstallPrompt = await intializePwa();
let cookiesAllowed = localStorage.getItem('cookiesAllowed') === 'yes';
if(!cookiesAllowed) {
this.$toast.show('We use cookies', {
icon: 'info',
duration: 5000,
theme: 'toasted-primary',
action: [
{
text: 'Dismiss',
onClick: (e, toastObject) => {
localStorage.setItem('cookiesAllowed', 'yes');
toastObject.goAway(0);
}
}
]
});
}
})();
}
}

View File

@@ -1,8 +1,8 @@
<template>
<div class="page page-error">
<h1>{{ error.statusCode }}</h1>
<h2>{{ error.message }}</h2>
<br>
<img src="~static/icons/error.svg" alt="Error" class="error_banner">
<h2>{{ error.statusCode }}</h2>
<h3>{{ error.message }}</h3>
<p><nuxt-link to="/"><button>Go Home</button></nuxt-link></p>
<p><a href="" @click.prevent="reloadApplication">Reload</a></p>
</div>
@@ -11,14 +11,16 @@
<style lang="scss">
// Center the error page in the viewport.
.page-error {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
}
.error_banner {
width: 256px;
}
</style>
<script>

View File

@@ -1,4 +1,3 @@
// Some helpful application constants.
// TODO: Use these when rendering the pages (rather than just for head/meta tags...)
export const meta = {
@@ -6,10 +5,8 @@ export const meta = {
shortDescription: "API request builder",
description: "The Postwoman API request builder helps you create your requests faster, saving you precious time on your development."
};
// Sets the base path for the router.
// Important for deploying to GitHub pages.
// -- Travis includes the author in the repo slug,
// so if there's a /, we need to get everything after it.
let repoName = (process.env.TRAVIS_REPO_SLUG || '').split('/').pop();
@@ -22,143 +19,276 @@ export const routerBase = process.env.DEPLOY_ENV === 'GH_PAGES' ? {
base: '/'
}
};
export default {
mode: 'spa',
/*
** Headers of the page
*/
** Headers of the page
*/
server: {
host: '0.0.0.0', // default: localhost
},
serverMiddleware: [
'~/proxy/index.js'
],
head: {
title: `${meta.name} \u2022 ${meta.shortDescription}`,
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1, minimum-scale=1, shrink-to-fit=no, minimal-ui' },
{ hid: 'description', name: 'description', content: meta.description || '' },
{ name: 'keywords', content: 'postwoman, api, request, testing, tool, rest, websocket'},
{ name: 'X-UA-Compatible', content: "IE=edge, chrome=1" },
{ itemprop: "name", content: `${meta.name} \u2022 ${meta.shortDescription}` },
{ itemprop: "description", content: meta.description },
{ itemprop: "image", content: `${routerBase.router.base}icons/icon-192x192.png` },
{
charset: 'utf-8'
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1, minimum-scale=1, shrink-to-fit=no, minimal-ui'
},
{
hid: 'description',
name: 'description',
content: meta.description || ''
},
{
name: 'keywords',
content: 'postwoman, postwoman chrome, postwoman online, postwoman for mac, postwoman app, postwoman for windows, postwoman google chrome, postwoman chrome app, get postwoman, postwoman web, postwoman android, postwoman app for chrome, postwoman mobile app, postwoman web app, api, request, testing, tool, rest, websocket'
},
{
name: 'X-UA-Compatible',
content: "IE=edge, chrome=1"
},
{
itemprop: "name",
content: `${meta.name} \u2022 ${meta.shortDescription}`
},
{
itemprop: "description",
content: meta.description
},
{
itemprop: "image",
content: `${routerBase.router.base}icons/icon-192x192.png`
},
// Add to homescreen for Chrome on Android. Fallback for PWA (handled by nuxt)
{ name: 'application-name', content: meta.name },
{
name: 'application-name',
content: meta.name
},
// Add to homescreen for Safari on iOS
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
{ name: 'apple-mobile-web-app-title', content: meta.name },
{
name: 'apple-mobile-web-app-capable',
content: 'yes'
},
{
name: 'apple-mobile-web-app-status-bar-style',
content: 'black-translucent'
},
{
name: 'apple-mobile-web-app-title',
content: meta.name
},
// Windows phone tile icon
{ name: 'msapplication-TileImage', content: `${routerBase.router.base}icons/icon-144x144.png` },
{ name: 'msapplication-TileColor', content: '#121212' },
{ name: 'msapplication-tap-highlight', content: 'no' },
{
name: 'msapplication-TileImage',
content: `${routerBase.router.base}icons/icon-144x144.png`
},
{
name: 'msapplication-TileColor',
content: '#252628'
},
{
name: 'msapplication-tap-highlight',
content: 'no'
},
// OpenGraph
{ property: 'og:site_name', content: meta.name },
{ property: 'og:url', content: 'https://liyasthomas.github.io/postwoman' },
{ property: 'og:type', content: 'website' },
{ property: 'og:title', content: `${meta.name} \u2022 ${meta.shortDescription}` },
{ property: 'og:description', content: meta.description },
{ property: 'og:image', content: `${routerBase.router.base}icons/icon-144x144.png` },
{
property: 'og:site_name',
content: meta.name
},
{
property: 'og:url',
content: 'https://postwoman.io'
},
{
property: 'og:type',
content: 'website'
},
{
property: 'og:title',
content: `${meta.name} \u2022 ${meta.shortDescription}`
},
{
property: 'og:description',
content: meta.description
},
{
property: 'og:image',
content: `${routerBase.router.base}icons/icon-144x144.png`
},
// Twitter
{ name: 'twitter:card', content: "summary" },
{ name: 'twitter:site', content: "@liyasthomas" },
{ name: 'twitter:creator', content: "@liyasthomas" },
{ name: 'twitter:url', content: "https://liyasthomas.github.io/postwoman" },
{ name: 'twitter:title', content: meta.name },
{ name: 'twitter:description', content: meta.shortDescription },
{ name: 'twitter:image', content: `${routerBase.router.base}icons/icon-144x144.png` },
{
name: 'twitter:card',
content: "summary"
},
{
name: 'twitter:site',
content: "@liyasthomas"
},
{
name: 'twitter:creator',
content: "@liyasthomas"
},
{
name: 'twitter:url',
content: "https://postwoman.io"
},
{
name: 'twitter:title',
content: meta.name
},
{
name: 'twitter:description',
content: meta.shortDescription
},
{
name: 'twitter:image',
content: `${routerBase.router.base}icons/icon-144x144.png`
},
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{
rel: 'icon',
type: 'image/x-icon',
href: `${routerBase.router.base}favicon.ico`
},
// Home-screen icons (iOS)
{ rel: 'apple-touch-icon', href: `${routerBase.router.base}icons/icon-48x48.png` },
{ rel: 'apple-touch-icon', sizes: '72x72', href: `${routerBase.router.base}icons/icon-72x72.png` },
{ rel: 'apple-touch-icon', sizes: '96x96', href: `${routerBase.router.base}icons/icon-96x96.png` },
{ rel: 'apple-touch-icon', sizes: '144x144', href: `${routerBase.router.base}icons/icon-144x144.png` },
{ rel: 'apple-touch-icon', sizes: '192x192', href: `${routerBase.router.base}icons/icon-192x192.png` },
{
rel: 'apple-touch-icon',
href: `${routerBase.router.base}icons/icon-48x48.png`
},
{
rel: 'apple-touch-icon',
sizes: '72x72',
href: `${routerBase.router.base}icons/icon-72x72.png`
},
{
rel: 'apple-touch-icon',
sizes: '96x96',
href: `${routerBase.router.base}icons/icon-96x96.png`
},
{
rel: 'apple-touch-icon',
sizes: '144x144',
href: `${routerBase.router.base}icons/icon-144x144.png`
},
{
rel: 'apple-touch-icon',
sizes: '192x192',
href: `${routerBase.router.base}icons/icon-192x192.png`
},
]
},
/*
** Customize the progress-bar color
*/
loading: { color: 'var(--ac-color)' },
** Customize the progress-bar color
*/
loading: {
color: 'var(--ac-color)'
},
/*
** Global CSS
*/
** Customize the loading indicator
*/
loadingIndicator: {
name: 'pulse',
color: 'var(--ac-color)',
background: 'var(--bg-color)'
},
/*
** Global CSS
*/
css: [
'@/assets/css/themes.scss',
'@/assets/css/fonts.scss',
'@/assets/css/styles.scss'
],
/*
** Plugins to load before mounting the App
*/
** Plugins to load before mounting the App
*/
plugins: [
{ src: '~/plugins/vuex-persist' }
{
src: '~/plugins/vuex-persist'
},
{
src: '~/plugins/v-tooltip'
}
],
/*
** Nuxt.js dev-modules
*/
** Nuxt.js dev-modules
*/
buildModules: [
],
/*
** Nuxt.js modules
*/
** Nuxt.js modules
*/
modules: [
// See https://goo.gl/OOhYW5
['@nuxtjs/pwa', {
manifest: {
name: meta.name,
short_name: meta.name,
description: meta.shortDescription,
display: "standalone",
theme_color: "#121212",
background_color: "#121212",
icons: ((sizes) => {
let icons = [];
for(let size of sizes){
icons.push({
"src": `${routerBase.router.base}icons/icon-${size}x${size}.png`,
"type": "image/png",
"sizes": `${size}x${size}`
});
}
return icons;
})([48, 72, 96, 144, 192, 512])
}
}],
['@nuxtjs/axios']
['@nuxtjs/pwa'],
['@nuxtjs/axios'],
['@nuxtjs/toast'],
['@nuxtjs/google-analytics'],
['@nuxtjs/sitemap'],
['@nuxtjs/google-tag-manager', { id: process.env.GTM_ID || 'GTM-MXWD8NQ' }]
],
pwa: {
manifest: {
name: meta.name,
short_name: meta.name,
display: "standalone",
theme_color: "#252628",
background_color: "#252628",
start_url: `${routerBase.router.base}`
},
meta: {
description: meta.shortDescription,
theme_color: "#252628",
},
icons: ((sizes) => {
let icons = [];
for (let size of sizes) {
icons.push({
"src": `${routerBase.router.base}icons/icon-${size}x${size}.png`,
"type": "image/png",
"sizes": `${size}x${size}`
});
}
return icons;
})([48, 72, 96, 144, 192, 512])
},
toast: {
position: 'bottom-center',
duration: 2000,
theme: 'bubble'
},
googleAnalytics: {
id: process.env.GA_ID || 'UA-61422507-2'
},
sitemap: {
hostname: 'https://postwoman.io'
},
/*
** Build configuration
*/
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend (config, ctx) {
}
** You can extend webpack config here
*/
extend(config, ctx) {}
},
/*
** Generate configuration
*/
** Generate configuration
*/
generate: {
fallback: true
},
/*
** Router configuration
*/

3072
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,41 @@
{
"name": "postwoman",
"version": "0.1.0",
"description": "Lightweight API request builder by Liyas Thomas",
"author": "liyasthomas",
"private": true,
"scripts": {
"predev": "node build.js --dev",
"dev": "nuxt",
"prebuild": "node build.js",
"build": "nuxt build",
"start": "nuxt start",
"pregenerate": "node build.js",
"generate": "nuxt generate"
},
"dependencies": {
"@nuxtjs/axios": "^5.6.0",
"@nuxtjs/pwa": "^3.0.0-0",
"nuxt": "^2.9.2",
"vue-virtual-scroll-list": "^1.4.2",
"vuejs-auto-complete": "^0.9.0",
"vuex-persist": "^2.1.0"
},
"devDependencies": {
"node-sass": "^4.12.0",
"sass-loader": "^7.3.1"
}
"name": "postwoman",
"version": "0.1.0",
"description": "Lightweight API request builder by Liyas Thomas",
"author": "liyasthomas",
"private": true,
"scripts": {
"predev": "node build.js --dev",
"dev": "nuxt",
"prebuild": "node build.js",
"build": "nuxt build",
"start": "nuxt start",
"pregenerate": "node build.js",
"generate": "nuxt generate",
"e2e": "cypress run",
"e2e:open": "cypress open",
"dev:e2e": "start-server-and-test dev http://localhost:3000 e2e:open",
"test": "start-server-and-test dev http://localhost:3000 e2e"
},
"dependencies": {
"@nuxtjs/axios": "^5.6.0",
"@nuxtjs/google-analytics": "^2.2.0",
"@nuxtjs/google-tag-manager": "^2.3.0",
"@nuxtjs/pwa": "^3.0.0-beta.19",
"@nuxtjs/sitemap": "^2.0.0",
"@nuxtjs/toast": "^3.2.1",
"highlight.js": "^9.15.10",
"nuxt": "^2.10.1",
"v-tooltip": "^2.0.2",
"vue-virtual-scroll-list": "^1.4.2",
"vuejs-auto-complete": "^0.9.0",
"vuex-persist": "^2.1.0",
"yargs-parser": "^15.0.0"
},
"devDependencies": {
"cypress": "^3.4.1",
"node-sass": "^4.12.0",
"sass-loader": "^7.3.1",
"start-server-and-test": "^1.10.6"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +1,37 @@
<template>
<div class="page">
<pw-section class="blue" label="Theme">
<ul>
<li>
<h3 class="title">Background</h3>
<div class="backgrounds">
<div class="page">
<pw-section class="cyan" label="Theme">
<ul>
<li>
<h3 class="title">Background</h3>
<div class="backgrounds">
<span :key="theme.class" @click="applyTheme(theme.class)" v-for="theme in themes">
<swatch :active="settings.THEME_CLASS === theme.class" :class="{ vibrant: theme.vibrant }" :color="theme.color" :name="theme.name"></swatch>
</span>
</div>
</li>
</ul>
<ul>
<li>
<h3 class="title">Color</h3>
<div class="colors">
</div>
</li>
</ul>
<ul>
<li>
<h3 class="title">Color</h3>
<div class="colors">
<span :key="entry.color" @click.prevent="setActiveColor(entry.color, entry.vibrant)" v-for="entry in colors">
<swatch :active="settings.THEME_COLOR === entry.color.toUpperCase()" :class="{ vibrant: entry.vibrant }" :color="entry.color" :name="entry.name" />
</span>
</div>
</li>
</ul>
<ul>
<li>
<h3 class="title">Frames</h3>
<pw-toggle :on="!settings.DISABLE_FRAME_COLORS" @change="toggleSetting('DISABLE_FRAME_COLORS')">
Multi-color {{ settings.DISABLE_FRAME_COLORS ? "disabled" : "enabled" }}
</pw-toggle>
</li>
</ul>
</pw-section>
<!--
PROXY SETTINGS
--------------
This feature is currently not finished.
</div>
</li>
</ul>
<ul>
<li>
<h3 class="title">Frames</h3>
<span>
<pw-toggle :on="!settings.DISABLE_FRAME_COLORS" @change="toggleSetting('DISABLE_FRAME_COLORS')">
Multi-color {{ settings.DISABLE_FRAME_COLORS ? "Disabled" : "Enabled" }}
</pw-toggle>
</span>
</li>
</ul>
</pw-section>
<pw-section class="blue" label="Proxy">
<ul>
<li>
@@ -44,7 +40,10 @@
</pw-toggle>
</li>
</ul>
<!--
PROXY SETTINGS URL AND KEY
--------------
This feature is currently not finished.
<ul>
<li>
<label for="url">URL</label>
@@ -55,143 +54,166 @@
<input id="key" type="password" v-model="settings.PROXY_KEY" :disabled="!settings.PROXY_ENABLED" @change="applySetting('PROXY_KEY', $event)">
</li>
</ul>
-->
</pw-section>
-->
</div>
</div>
</template>
<script>
import section from "../components/section";
import swatch from "../components/settings/swatch";
import toggle from "../components/toggle";
import section from "../components/section";
import swatch from "../components/settings/swatch";
import toggle from "../components/toggle";
export default {
components: {
'pw-section': section,
'pw-toggle': toggle,
'swatch': swatch
},
export default {
components: {
'pw-section': section,
'pw-toggle': toggle,
'swatch': swatch
},
data() {
return {
// NOTE:: You need to first set the CSS for your theme in /assets/css/themes.scss
// You should copy the existing light theme as a template and then just
// set the relevant values.
themes: [{
"color": "#121212",
"name": "Dark (Default)",
"class": ""
},
{
"color": "#DFDFDF",
"name": "Light",
"vibrant": true,
"class": "light"
}
],
// You can define a new color here! It will simply store the color value.
colors: [
// If the color is vibrant, black is used as the active foreground color.
{
"color": "#51ff0d",
"name": "Lime (Default)",
"vibrant": true
},
{
"color": "#FFC107",
"name": "Yellow",
"vibrant": true
},
{
"color": "#E91E63",
"name": "Pink",
"vibrant": false
},
{
"color": "#e74c3c",
"name": "Red",
"vibrant": false
},
{
"color": "#9b59b6",
"name": "Purple",
"vibrant": false
},
{
"color": "#2980b9",
"name": "Blue",
"vibrant": false
},
],
data() {
return {
// NOTE:: You need to first set the CSS for your theme in /assets/css/themes.scss
// You should copy the existing light theme as a template and then just
// set the relevant values.
themes: [
{
"color": "rgb(37, 38, 40)",
"name": "Kinda Dark",
"class": ""
},
{
"color": "#ffffff",
"name": "Clearly White",
"vibrant": true,
"class": "light"
},
{
"color": "#000000",
"name": "Just Black",
"class": "black"
},
{
"color": "var(--bg-color)",
"name": "Auto (system)",
"vibrant": window.matchMedia('(prefers-color-scheme: light)').matches,
"class": "auto"
}
],
// You can define a new color here! It will simply store the color value.
colors: [
// If the color is vibrant, black is used as the active foreground color.
{
"color": "#50fa7b",
"name": "Green",
"vibrant": true
},
{
"color": "#f1fa8c",
"name": "Yellow",
"vibrant": true
},
{
"color": "#ff79c6",
"name": "Pink",
"vibrant": true
},
{
"color": "#ff5555",
"name": "Red",
"vibrant": false
},
{
"color": "#bd93f9",
"name": "Purple",
"vibrant": true
},
{
"color": "#ffb86c",
"name": "Orange",
"vibrant": true
},
{
"color": "#8be9fd",
"name": "Cyan",
"vibrant": true
},
{
"color": "#57b5f9",
"name": "Blue",
"vibrant": false
},
],
settings: {
THEME_CLASS: this.$store.state.postwoman.settings.THEME_CLASS || '',
THEME_COLOR: '',
THEME_COLOR_VIBRANT: true,
settings: {
THEME_CLASS: this.$store.state.postwoman.settings.THEME_CLASS || '',
THEME_COLOR: '',
THEME_COLOR_VIBRANT: true,
DISABLE_FRAME_COLORS: this.$store.state.postwoman.settings.DISABLE_FRAME_COLORS || false,
PROXY_ENABLED: this.$store.state.postwoman.settings.PROXY_ENABLED || false,
PROXY_URL: this.$store.state.postwoman.settings.PROXY_URL || '',
PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || ''
}
}
},
watch: {
proxySettings: {
deep: true,
handler (value) {
this.applySetting('PROXY_URL', value.url);
this.applySetting('PROXY_KEY', value.key);
}
}
},
methods: {
applyTheme(name) {
this.applySetting('THEME_CLASS', name);
document.documentElement.className = name;
let imgGitHub = document.getElementById("imgGitHub");
imgGitHub.style['filter'] = "";
imgGitHub.style['webkit-filter'] = "invert(100%)";
if (name.includes("light")){
imgGitHub.style['filter'] = "invert(100%)";
imgGitHub.style['webkit-filter'] = "invert(100%)";
}
},
setActiveColor(color, vibrant) {
// By default, the color is vibrant.
if (vibrant == null) vibrant = true;
document.documentElement.style.setProperty('--ac-color', color);
document.documentElement.style.setProperty('--act-color', vibrant ? '#121212' : '#fff');
this.applySetting('THEME_COLOR', color.toUpperCase());
this.applySetting('THEME_COLOR_VIBRANT', vibrant);
},
getActiveColor() {
// This strips extra spaces and # signs from the strings.
const strip = (str) => str.replace(/#/g, '').replace(/ /g, '');
return `#${strip(window.getComputedStyle(document.documentElement).getPropertyValue('--ac-color')).toUpperCase()}`;
},
applySetting(key, value) {
this.settings[key] = value;
this.$store.commit('postwoman/applySetting', [key, value]);
},
toggleSetting(key) {
this.settings[key] = !this.settings[key];
this.$store.commit('postwoman/applySetting', [key, this.settings[key]]);
}
},
beforeMount() {
this.settings.THEME_COLOR = this.getActiveColor();
},
computed: {
proxySettings () {
return {
url: this.settings.PROXY_URL,
key: this.settings.PROXY_KEY
}
}
DISABLE_FRAME_COLORS: this.$store.state.postwoman.settings.DISABLE_FRAME_COLORS || false,
PROXY_ENABLED: this.$store.state.postwoman.settings.PROXY_ENABLED || false,
PROXY_URL: this.$store.state.postwoman.settings.PROXY_URL || '',
PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || ''
}
}
},
watch: {
proxySettings: {
deep: true,
handler(value) {
this.applySetting('PROXY_URL', value.url);
this.applySetting('PROXY_KEY', value.key);
}
}
},
methods: {
applyTheme(name) {
this.applySetting('THEME_CLASS', name);
document.documentElement.className = name;
let imgGitHub = document.getElementById("imgGitHub");
imgGitHub.style['filter'] = "";
imgGitHub.style['webkit-filter'] = "invert(100%)";
if (name.includes("light")) {
imgGitHub.style['filter'] = "invert(100%)";
imgGitHub.style['webkit-filter'] = "invert(100%)";
}
},
setActiveColor(color, vibrant) {
// By default, the color is vibrant.
if (vibrant == null) vibrant = true;
document.documentElement.style.setProperty('--ac-color', color);
document.documentElement.style.setProperty('--act-color', vibrant ? 'rgb(37, 38, 40)' : '#f8f8f2');
this.applySetting('THEME_COLOR', color.toUpperCase());
this.applySetting('THEME_COLOR_VIBRANT', vibrant);
},
getActiveColor() {
// This strips extra spaces and # signs from the strings.
const strip = (str) => str.replace(/#/g, '').replace(/ /g, '');
return `#${strip(window.getComputedStyle(document.documentElement).getPropertyValue('--ac-color')).toUpperCase()}`;
},
applySetting(key, value) {
this.settings[key] = value;
this.$store.commit('postwoman/applySetting', [key, value]);
},
toggleSetting(key) {
this.settings[key] = !this.settings[key];
this.$store.commit('postwoman/applySetting', [key, this.settings[key]]);
}
},
beforeMount() {
this.settings.THEME_COLOR = this.getActiveColor();
},
computed: {
proxySettings() {
return {
url: this.settings.PROXY_URL,
key: this.settings.PROXY_KEY
}
}
}
}
</script>

View File

@@ -7,8 +7,14 @@
<input id="url" type="url" :class="{ error: !urlValid }" v-model="url" @keyup.enter="urlValid ? toggleConnection() : null">
</li>
<li>
<label for="action" class="hide-on-small-screen">&nbsp;</label>
<button :disabled="!urlValid" name="action" @click="toggleConnection">{{ toggleConnectionVerb }}</button>
<label for="connect" class="hide-on-small-screen">&nbsp;</label>
<button :disabled="!urlValid" id="connect" name="connect" @click="toggleConnection">
{{ toggleConnectionVerb }}
<span>
<i class="material-icons" v-if="!connectionState">sync</i>
<i class="material-icons" v-if="connectionState">sync_disabled</i>
</span>
</button>
</li>
</ul>
</pw-section>
@@ -18,20 +24,25 @@
<label for="log">Log</label>
<div id="log" name="log" class="log">
<span v-if="communication.log">
<span v-for="logEntry in communication.log" :style="{ color: logEntry.color }">@ {{ logEntry.ts }} {{ getSourcePrefix(logEntry.source) }} {{ logEntry.payload }}</span>
<span v-for="(logEntry, index) in communication.log" :style="{ color: logEntry.color }" :key="index">@ {{ logEntry.ts }} {{ getSourcePrefix(logEntry.source) }} {{ logEntry.payload }}</span>
</span>
<span v-else>(Waiting for connection...)</span>
<span v-else>(waiting for connection)</span>
</div>
</li>
</ul>
<ul>
<li>
<label for="message">Message</label>
<input id="message" name="message" type="text" v-model="communication.input" :disabled="!connectionState" @keyup.enter="connectionState ? sendMessage() : null">
<input id="message" name="message" type="text" v-model="communication.input" :readonly="!connectionState" @keyup.enter="connectionState ? sendMessage() : null">
</li>
<li>
<label for="send" class="hide-on-small-screen">&nbsp;</label>
<button name="send" :disabled="!connectionState" @click="sendMessage">Send</button>
<button id="send" name="send" :disabled="!connectionState" @click="sendMessage">
Send
<span>
<i class="material-icons">send</i>
</span>
</button>
</li>
</ul>
</pw-section>
@@ -42,7 +53,7 @@
margin: 4px;
padding: 8px 16px;
width: calc(100% - 8px);
border-radius: 4px;
border-radius: 8px;
background-color: var(--bg-dark-color);
color: var(--fg-color);
height: 256px;
@@ -50,9 +61,8 @@
&,
span {
font-weight: 700;
font-size: 18px;
font-family: monospace;
font-family: 'Roboto Mono', monospace;
}
span {
@@ -104,7 +114,7 @@
this.communication.log = [{
payload: `Connecting to ${this.url}...`,
source: 'info',
color: 'lime'
color: 'var(--ac-color)'
}];
try {
this.socket = new WebSocket(this.url);
@@ -113,9 +123,12 @@
this.communication.log = [{
payload: `Connected to ${this.url}.`,
source: 'info',
color: 'lime',
color: 'var(--ac-color)',
ts: (new Date()).toLocaleTimeString()
}];
this.$toast.success('Connected', {
icon: 'sync'
});
};
this.socket.onerror = (event) => {
this.handleError();
@@ -125,9 +138,12 @@
this.communication.log.push({
payload: `Disconnected from ${this.url}.`,
source: 'info',
color: 'red',
color: '#ff5555',
ts: (new Date()).toLocaleTimeString()
});
this.$toast.error('Disconnected', {
icon: 'sync_disabled'
});
};
this.socket.onmessage = (event) => {
this.communication.log.push({
@@ -138,6 +154,9 @@
}
} catch (ex) {
this.handleError(ex);
this.$toast.error('Something went wrong!', {
icon: 'error'
});
}
},
disconnect() {
@@ -149,13 +168,13 @@
this.communication.log.push({
payload: `An error has occurred.`,
source: 'info',
color: 'red',
color: '#ff5555',
ts: (new Date()).toLocaleTimeString()
});
if (error != null) this.communication.log.push({
payload: error,
source: 'info',
color: 'red',
color: '#ff5555',
ts: (new Date()).toLocaleTimeString()
});
},

4
plugins/v-tooltip.js Normal file
View File

@@ -0,0 +1,4 @@
import Vue from 'vue';
import VTooltip from 'v-tooltip';
Vue.use(VTooltip);

50
proxy/index.js Normal file
View File

@@ -0,0 +1,50 @@
import express from 'express';
import bodyParser from 'body-parser';
import axios from 'axios';
const app = express();
app.use(bodyParser.json());
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});
app.post('/', async function(req, res) {
const {method, url, auth, headers, data} = req.body;
try {
const payload = await axios({
method,
url,
auth,
headers,
data
})
return res.json({
data: payload.data,
status: payload.status,
statusText: payload.statusText,
headers: payload.headers,
});
} catch(error) {
if(error.response) {
const errorResponse = error.response;
return res.json({
data: errorResponse.data,
status: errorResponse.status,
statusText: errorResponse.statusText,
headers: errorResponse.headers,
});
} else {
return res.status(500).send();
}
}
});
export default {
path: '/proxy',
handler: app
}

16
static/.htaccess Normal file
View File

@@ -0,0 +1,16 @@
<IfModule mod_expires.c>
ExpiresActive On
# Images
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
# CSS, JavaScript
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>

1
static/CNAME Normal file
View File

@@ -0,0 +1 @@
postwoman.io

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

1
static/icons/error.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -59,4 +59,4 @@
data-original="#121212"
d="M 64.601,236.822 C 64.601,394.256 192.786,612 306.001,612 412.582,612 547.4,394.256 547.4,236.822 547.4,79.388 439.322,0 306,0 172.678,0 64.601,79.388 64.601,236.822 Z m 304.12,116.415 c 29.475,-29.475 70.598,-40.195 108.552,-32.173 8.021,37.954 -2.698,79.077 -32.173,108.552 -29.475,29.475 -70.598,40.195 -108.552,32.173 -8.022,-37.955 2.698,-79.078 32.173,-108.552 z M 134.727,321.063 c 37.954,-8.021 79.077,2.698 108.552,32.173 29.475,29.475 40.195,70.598 32.173,108.552 -37.954,8.021 -79.077,-2.698 -108.552,-32.173 -29.475,-29.476 -40.194,-70.598 -32.173,-108.552 z"
inkscape:connector-curvature="0"
style="fill:#51ff0d" /></g></g></g></svg>
style="fill:#50fa7b" /></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

BIN
static/screenshot.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

BIN
static/screenshot1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
static/screenshot2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

7
storage.rules Normal file
View File

@@ -0,0 +1,7 @@
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth!=null;
}
}
}

View File

@@ -48,16 +48,15 @@ export const state = () => ({
export const mutations = {
applySetting (state, setting) {
if(setting == null || !(setting instanceof Array) || setting.length !== 2)
if (setting == null || !(setting instanceof Array) || setting.length !== 2)
throw new Error("You must provide a setting (array in the form [key, value])");
let key = setting[0];
let value = setting[1];
const [key, value] = setting;
// Do not just remove this check.
// Add your settings key to the SETTINGS_KEYS array at the
// top of the file.
// This is to ensure that application settings remain documented.
if(!SETTINGS_KEYS.includes(key)) throw new Error("The settings structure does not include the key " + key);
if (!SETTINGS_KEYS.includes(key)) throw new Error("The settings structure does not include the key " + key);
state.settings[key] = value;
}

View File

@@ -0,0 +1 @@
{ "message": "FAKE Cat API" }

View File

@@ -0,0 +1,7 @@
describe('Visit home', () => {
it('Have a page title with "Postwoman"', () => {
cy.visit('/')
.get('title')
.should('contain','Postwoman')
})
})

View File

@@ -0,0 +1,57 @@
describe('Methods', () => {
const methods = [ 'POST', 'HEAD', 'POST', 'PUT', 'DELETE','OPTIONS', 'PATCH']
methods.forEach(method => {
it(`Change the default method GET to ${method} with url query`, () => {
cy.visit(`/?method=${method}`)
.get('#method').contains(method)
})
})
})
describe('Url and path', () => {
it('Change default url with query and reset default path to empty string and make a request to cat api', () => {
cy.seedAndVisit('catapi', '/?url=https://api.thecatapi.com&path=')
.get('#url').then(el => expect(el.val() === 'https://api.thecatapi.com').to.equal(true))
.get("#path").then(el => expect(el.val() === '').to.equal(true))
.get('#response-details-wrapper').should($wrapper => {
expect($wrapper).to.contain('FAKE Cat API')
})
})
})
describe('Authentication', () => {
it(`Change default auth 'None' to 'Basic' and set httpUser and httpPassword with url query`, () => {
cy.visit(`?&auth=Basic&httpUser=foo&httpPassword=bar`, { retryOnStatusCodeFailure: true })
.get('#authentication').contains('Authentication').click()
.then(() => {
cy.get('input[name="http_basic_user"]', { timeout: 500 })
.invoke('val')
.then(user => {
expect(user === 'foo').to.equal(true)
cy.log('Success! user === foo')
})
cy.get('input[name="http_basic_passwd"]')
.invoke('val')
.then(user => {
expect(user === 'bar').to.equal(true)
cy.log('Success! password === bar')
})
})
})
const base64Tkn = encodeURI(btoa('{"alg":"HS256", "typ": "JWT"}'))
it(`Change default auth 'None' to 'Bearer token' and set bearerToken with url query`, () => {
cy.visit(`/?auth=Bearer Token&bearerToken=${base64Tkn}`, { retryOnStatusCodeFailure: true })
.get('#authentication').contains('Authentication').click()
.then(() => {
cy.get('input[name="bearer_token"]', { timeout: 500 })
.invoke('val')
.then(tkn => {
expect(tkn === base64Tkn).to.equal(true)
cy.log(`Success! input[name="bearer_token"] === ${base64Tkn}`)
})
})
})
})

View File

@@ -0,0 +1,16 @@
/**
* Creates cy.seedAndVisit() function
* This function will go to some path and wait for some fake response from 'src/tests/fixtures/*.json'
* @param { String } seedData The name of json at 'src/tests/fixtures/
* @param { String } path The path or query parameters to go -ex. '/?path=/api/users'
* @param { String } method The fake request method
*/
Cypress.Commands.add('seedAndVisit', (seedData, path = '/', method = 'GET') => {
cy.server()
.route(method, 'https://api.thecatapi.com/', `fixture:${seedData}`).as(
'load'
)
cy.visit(path)
.get('#send').click()
.wait('@load')
})

View File

@@ -0,0 +1 @@
import './commands'

View File

@@ -1,8 +0,0 @@
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('Travis!\n'); // build should pass now!
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');