Initial commit

This commit is contained in:
2018-04-19 18:52:59 -04:00
commit 278329c5bd
23 changed files with 6475 additions and 0 deletions

9
.babelrc Normal file
View File

@@ -0,0 +1,9 @@
{
"presets": [
["env", {
"es2015": {
"modules": false
}
}]
]
}

2
.gitignore vendored Normal file
View File

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

22
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Listen for XDebug",
"type": "php",
"request": "launch",
"port": 9000
},
{
"name": "Launch currently open script",
"type": "php",
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"port": 9000
}
]
}

21
LICENSE.md Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Chris Kaczor
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

11
README.md Normal file
View File

@@ -0,0 +1,11 @@
# etsy-shop-widget
A WordPress plugin to display the contents of an Etsy shop in a widget.
## Authors
* **Chris Kaczor** - *Initial work* - https://github.com/ckaczor - https://chriskaczor.com
## License
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.

4
cd.command Normal file
View File

@@ -0,0 +1,4 @@
# Open vue director in Terminal
cd -- "$(dirname "$BASH_SOURCE")"
$SHELL

3
npm-run-build.command Normal file
View File

@@ -0,0 +1,3 @@
cd -- "$(dirname "$BASH_SOURCE")"
npm run build
# read -p "Press Return to Close..."

2
npm-run-dev.command Normal file
View File

@@ -0,0 +1,2 @@
cd -- "$(dirname "$BASH_SOURCE")"
npm run dev

44
package.json Normal file
View File

@@ -0,0 +1,44 @@
{
"name": "etsy-shop-widget",
"description": "A Vue.js Webpack templage for a WordPress plugin",
"version": "1.0.0",
"author": "Chris Kaczor <ckaczor@vertical.com>",
"private": true,
"scripts": {
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
"watch": "npm-watch"
},
"watch": {
"build": {
"patterns": [
"src",
"test"
],
"extensions": "ts,js,jsx,scss,vue"
}
},
"dependencies": {
"axios": "^0.17.1",
"vue": "^2.2.6"
},
"devDependencies": {
"babel-core": "^6.0.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.1",
"cross-env": "^5.1.3",
"css-loader": "^0.28.9",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.6",
"node-sass": "^4.5.0",
"npm-watch": "^0.3.0",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"sass-loader": "^6.0.6",
"ts-loader": "^3.4.0",
"typescript": "^2.7.1",
"vue-loader": "^14.0.3",
"vue-property-decorator": "^6.0.0",
"vue-template-compiler": "^2.2.1",
"webpack": "^3.10.0",
"webpack-dev-server": "^2.2.0"
}
}

63
php/actions.php Normal file
View File

@@ -0,0 +1,63 @@
<?php
/**
* PHP version 5
*
* @category Category
* @package Etsy_Shop_Widget
* @author Chris Kaczor <chris@kaczor.us>
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @link https://kaczor.us
*/
/**
* Handles requests for listings
*
* @return null
*/
function ESW_Listings_request()
{
$listings = get_transient('etsy_shop_widget_listings');
if ($listings === false) {
$options = get_option('ESW_settings');
$response = wp_remote_request('https://openapi.etsy.com/v2/shops/' . $options['ESW_Etsy_Shop_Name'] . '/listings/active?includes=MainImage&api_key=' . $options['ESW_Etsy_API_Key'] . '');
$listings = $response['body'];
set_transient('etsy_shop_widget_listings', $listings, $options['ESW_Cache_Time'] * 60);
}
echo $listings;
die();
}
add_action('admin_post_esw_listings', 'ESW_Listings_request');
add_action('admin_post_nopriv_esw_listings', 'ESW_Listings_request');
/**
* Handles requests for to clear cache
*
* @return null
*/
function ESW_Clear_Cache_request()
{
delete_transient('etsy_shop_widget_listings');
die();
}
add_action('admin_post_esw_clearcache', 'ESW_Clear_Cache_request');
/**
* Handles shortcode
*
* @return null
*/
function ESW_shortcode()
{
return '<div id="etsy-shop-widget"></div>';
}
add_shortcode('etsy-shop-widget', 'ESW_shortcode');

170
php/options.php Normal file
View File

@@ -0,0 +1,170 @@
<?php
/**
* PHP version 5
*
* @category Category
* @package Etsy_Shop_Widget
* @author Chris Kaczor <chris@kaczor.us>
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @link https://kaczor.us
*/
add_action('admin_menu', 'ESW_Add_Admin_menu');
add_action('admin_init', 'ESW_Settings_init');
/**
* Adds admin menu
*
* @return null
*/
function ESW_Add_Admin_menu()
{
add_options_page('Etsy Shop Widget', 'Etsy Shop Widget', 'manage_options', 'etsy_shop_widget', 'ESW_Options_page');
}
/**
* Initializes settings
*
* @return null
*/
function ESW_Settings_init()
{
wp_enqueue_style('settings', ESW_PLUGIN_URL . '/php/settings.css', null, ESW_PLUGIN_VER, 'all');
register_setting('pluginPage', 'ESW_settings');
add_settings_section(
'ESW_pluginPage_section',
'',
'',
'pluginPage'
);
add_settings_field(
'ESW_Etsy_API_Key',
__('Etsy API key', 'wordpress'),
'ESW_API_Key_render',
'pluginPage',
'ESW_pluginPage_section'
);
add_settings_field(
'ESW_Etsy_Shop_Name',
__('Etsy shop name', 'wordpress'),
'ESW_Shop_Name_render',
'pluginPage',
'ESW_pluginPage_section'
);
add_settings_field(
'ESW_Cache_Time',
__('Cache duration', 'wordpress'),
'ESW_Cache_Time_render',
'pluginPage',
'ESW_pluginPage_section'
);
$options = get_option('ESW_settings');
if ($options['ESW_Cache_Time'] === '') {
$options['ESW_Cache_Time'] = 1;
update_option('ESW_settings', $options);
}
}
/**
* Render API key
*
* @return null
*/
function ESW_API_Key_render()
{
$options = get_option('ESW_settings');
?>
<input type='text' id='esw-etsy-api-key' name='ESW_settings[ESW_Etsy_API_Key]' value='<?php echo $options['ESW_Etsy_API_Key']; ?>'>
<?php
}
/**
* Render shop name
*
* @return null
*/
function ESW_Shop_Name_render()
{
$options = get_option('ESW_settings');
?>
<input type='text' id='esw-etsy-shop-name' name='ESW_settings[ESW_Etsy_Shop_Name]' value='<?php echo $options['ESW_Etsy_Shop_Name']; ?>'>
<?php
}
/**
* Render cache time
*
* @return null
*/
function ESW_Cache_Time_render()
{
$options = get_option('ESW_settings');
?>
<input id='esw-cache-time' type='number' min='1' max='24' name='ESW_settings[ESW_Cache_Time]' value='<?php echo $options['ESW_Cache_Time']; ?>'>
hours
<btn id='esw-cache-clear-now' class="button button-primary" onclick="clearCache()">Clear Now</btn>
<span id='esw-cache-clear-now-done' class="dashicons dashicons-yes"></span>
<?php
}
/**
* Render options page
*
* @return null
*/
function ESW_Options_page()
{
$site_url = get_site_url();
?>
<script>
function clearCache() {
jQuery('#esw-cache-clear-now').attr('disabled', true);
jQuery('#esw-cache-clear-now-done').css('visibility', 'hidden');
jQuery.post('<?php echo $site_url ?>/wp-admin/admin-post.php?action=esw_clearcache', undefined, (response) => {
jQuery('#esw-cache-clear-now').attr('disabled', false);
jQuery('#esw-cache-clear-now-done').css('visibility', 'visible');
setTimeout(() => {
jQuery('#esw-cache-clear-now-done').css('visibility', 'hidden');
}, 2000);
});
}
</script>
<form action='options.php' method='post'>
<h2>Etsy Shop Widget</h2>
<?php
settings_fields('pluginPage');
do_settings_sections('pluginPage');
submit_button();
?>
</form>
<?php
}
?>

22
php/settings.css Normal file
View File

@@ -0,0 +1,22 @@
#esw-etsy-api-key {
width: 300px;
}
#esw-etsy-shop-name {
width: 300px;
}
#esw-cache-time {
margin-right: 5px;
width: 50px;
}
#esw-cache-clear-now {
margin-left: 20px;
}
#esw-cache-clear-now-done {
font-size: 24pt;
color: darkgreen;
visibility: hidden;
}

27
php/webpack_enqueue.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
/**
* PHP version 5
*
* @category Category
* @package Etsy_Shop_Widget
* @author Chris Kaczor <chris@kaczor.us>
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @link https://kaczor.us
*/
/**
* Loads scripts
*
* @return null
*/
function ESW_Load_scripts()
{
wp_enqueue_style('main', ESW_PLUGIN_URL . '/assets/style.css', null, ESW_PLUGIN_VER, 'all');
wp_enqueue_script('vendor', ESW_PLUGIN_URL . '/assets/vendor.js', null, ESW_PLUGIN_VER, true);
wp_enqueue_script('main', ESW_PLUGIN_URL . '/assets/main.js', array('vendor'), ESW_PLUGIN_VER, true);
wp_localize_script('main', 'esw_wp', array( 'siteurl' => get_option('siteurl') ));
}
add_action('wp_enqueue_scripts', 'ESW_Load_scripts');

32
plugin.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
/**
* Plugin Name: Etsy Shop Widget
* Plugin URI: https://kaczor.us
* Description: Adds a widget to display the contents of an Etsy shop.
* Version: 1.0
* Author: Chris Kaczor
* Author URI: https://kaczor.us
*
* PHP version 5
*
* @category Category
* @package Etsy_Shop_Widget
* @author Chris Kaczor <chris@kaczor.us>
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @link https://kaczor.us
*/
if (defined('ESW_PLUGIN_VER')) {
return;
}
define('ESW_PLUGIN_VER', '0.0.1');
define('ESW_PLUGIN_PATH', plugin_dir_path(__FILE__));
define('ESW_PLUGIN_URL', plugin_dir_url(__FILE__));
define('ESW_PLUGIN_BASENAME', plugin_basename(__FILE__));
require_once __DIR__ . '/php/webpack_enqueue.php';
require_once __DIR__ . '/php/options.php';
require_once __DIR__ . '/php/actions.php';

22
src/components/App.vue Normal file
View File

@@ -0,0 +1,22 @@
<script lang="ts" src="./App.vue.ts"></script>
<style lang="scss" src="./App.vue.scss"></style>
<template>
<div class="esw-listing-container">
<div class="esw-listing-item" v-for="listing in listings" v-bind:key="listing.listing_id">
<a :href="listing.url" target="_blank">
<img class="esw-listing-item-image" :src="listing.MainImage.url_170x135" />
</a>
<a :href="listing.url" target="_blank">
<div class="esw-listing-item-title" v-html="listing.title"></div>
</a>
<div class="esw-listing-item-price">
{{ listing.price }}
{{ listing.currency_code }}
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,19 @@
.esw-listing-container {
overflow-y: auto;
max-height: 50vh;
}
.esw-listing-item {
text-align: center;
padding: 10px 25px;
}
.esw-listing-item-title {
font-size: 90%;
padding: 0 5px;
}
.esw-listing-item-price {
font-size: 75%;
font-weight: 600;
}

40
src/components/App.vue.ts Normal file
View File

@@ -0,0 +1,40 @@
import Vue from 'vue';
import { Component } from 'vue-property-decorator';
import Axios from 'axios';
class EtsyResult {
results: Array<EtsyListing>;
}
class EtsyListing {
listing_id: number;
state: string;
title: string;
description: string;
price: string;
currency_code: string;
quantity: string;
url: string;
last_modified_tsz: number;
MainImage: EtsyListingImage;
}
class EtsyListingImage {
listing_image_id: number;
url_75x75: string;
url_170x135: string;
url_570xN: string;
url_fullxfull: string;
}
@Component
export default class App extends Vue {
listings: Array<EtsyListing> | null = null;
async mounted() {
const response = await Axios.get<EtsyResult>(window['esw_wp'].siteurl + '/wp-admin/admin-post.php?action=esw_listings');
this.listings = response.data.results.sort((a, b) => a.last_modified_tsz - b.last_modified_tsz);
}
}

14
src/index.html Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>etsy-shop-widget</title>
</head>
<body>
<div id="etsy-shop-widget"></div>
<script src="/dist/build.js"></script>
</body>
</html>

7
src/main.ts Normal file
View File

@@ -0,0 +1,7 @@
import Vue from 'vue';
import App from './components/App.vue';
new Vue({
el: '#etsy-shop-widget',
render: h => h(App)
});

24
tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "es2015",
"moduleResolution": "node",
"target": "es5",
"noImplicitAny": true,
"strictNullChecks": false,
"rootDir": ".",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"suppressImplicitAnyIndexErrors": true,
"lib": [
"dom",
"es5",
"es2015.promise",
"es2015.iterable"
]
},
"exclude": [
"node_modules"
]
}

57
tslint.json Normal file
View File

@@ -0,0 +1,57 @@
{
"rules": {
"class-name": true,
"comment-format": [
true,
"check-space"
],
"indent": [
true,
"tabs"
],
"no-unused-variable": true,
"no-duplicate-variable": true,
"no-eval": true,
"no-internal-module": false,
"no-trailing-whitespace": false,
"no-var-keyword": true,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"quotemark": [
true,
"single"
],
"semicolon": [
true
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": [
true,
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}

124
webpack.config.js Normal file
View File

@@ -0,0 +1,124 @@
var path = require('path')
var webpack = require('webpack')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var isProduction = process.env.NODE_ENV === 'production';
module.exports = {
entry: './src/main.ts',
output: {
path: path.resolve(__dirname, './assets'),
publicPath: 'http://localhost:8080/assets/',
filename: '[name].js'
},
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: isProduction ? {
'css': ExtractTextPlugin.extract({
fallback: 'vue-style-loader',
use: 'css-loader'
}),
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
// the "scss" and "sass" values for the lang attribute to the right configs here.
// other preprocessors should work out of the box, no loader config like this necessary.
'scss': ExtractTextPlugin.extract({
fallback: 'vue-style-loader',
use: 'css-loader!sass-loader'
}),
'sass': ExtractTextPlugin.extract({
fallback: 'vue-style-loader',
use: 'css-loader!sass-loader?indentedSyntax'
})
} : {}
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: [/node_modules/, /assets/]
},
{
test: /\.ts$/,
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/]
},
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
extensions: [".ts", ".js", ".vue", ".json"],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
devServer: {
historyApiFallback: true,
noInfo: true,
headers: {
'Access-Control-Allow-Origin': '*',
},
proxy: {
"**": "http://localhost/wordpress/"
}
},
performance: {
hints: false
},
devtool: '#eval-source-map',
plugins: [
new webpack.NamedModulesPlugin()
]
}
if (isProduction) {
module.exports.devtool = '#source-map';
module.exports.output.publicPath = './wp-content/plugins/etsy-shop-widget/assets/';
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
}),
// extract css into its own file
new ExtractTextPlugin('style.css'),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin(),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(path.join(__dirname, './node_modules')) === 0
)
}
})
])
}

5736
yarn.lock Normal file

File diff suppressed because it is too large Load Diff