Skip to content
Open
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.DS_Store
emdash-exporter.zip
189 changes: 189 additions & 0 deletions plugins/emdash-exporter/assets/wizard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
.emdash-wizard {
max-width: 860px;
}

.emdash-title {
margin-bottom: 20px;
}

.emdash-title-sub {
color: #2271b1;
font-weight: 600;
}

.emdash-step {
background: #fff;
border: 1px solid #c3c4c7;
border-radius: 6px;
margin-bottom: 16px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
}

.emdash-step-header {
display: flex;
align-items: flex-start;
gap: 14px;
padding: 16px 20px;
border-bottom: 1px solid #f0f0f1;
}

.emdash-step-header h2 {
margin: 0 0 4px;
font-size: 16px;
}

.emdash-step-header p {
margin: 0;
color: #646970;
}

.emdash-step-badge {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: 50%;
background: #f0f0f1;
color: #3c434a;
font-weight: 700;
}

.emdash-step.is-active .emdash-step-badge {
background: #2271b1;
color: #fff;
}

.emdash-step.is-done .emdash-step-badge {
background: #00a32a;
color: #fff;
}

.emdash-step-status {
margin-inline-start: auto;
align-self: center;
color: #00a32a;
font-weight: 600;
white-space: nowrap;
}

.emdash-step-body {
padding: 16px 20px 20px;
}

.emdash-checks {
margin: 0 0 16px;
}

.emdash-check {
display: flex;
gap: 10px;
padding: 10px 12px;
border-radius: 4px;
margin-bottom: 8px;
background: #f6f7f7;
}

.emdash-check p {
margin: 2px 0 0;
}

.emdash-check-icon {
flex-shrink: 0;
font-weight: 700;
width: 18px;
text-align: center;
}

.emdash-check--pass .emdash-check-icon { color: #00a32a; }
.emdash-check--warn { background: #fcf9e8; }
.emdash-check--warn .emdash-check-icon { color: #dba617; }
.emdash-check--fail { background: #fcf0f1; }
.emdash-check--fail .emdash-check-icon { color: #d63638; }

.emdash-check-fix {
color: #646970;
}

.emdash-check-fix code {
user-select: all;
}

.emdash-key-row {
display: flex;
gap: 8px;
margin-top: 16px;
}

.emdash-key-input {
flex: 1;
font-family: Consolas, Monaco, monospace;
font-size: 13px;
padding: 6px 10px;
}

#emdash-key-error {
margin-top: 12px;
}

.emdash-columns {
display: flex;
gap: 24px;
flex-wrap: wrap;
margin-bottom: 8px;
}

.emdash-column {
flex: 1;
min-width: 280px;
}

.emdash-column h3 {
margin-top: 4px;
}

.emdash-deploy-btn {
display: inline-flex !important;
align-items: center;
gap: 8px;
background: #f6821f !important;
border-color: #f6821f !important;
}

.emdash-deploy-btn:hover,
.emdash-deploy-btn:focus {
background: #e07408 !important;
border-color: #e07408 !important;
}

.emdash-cf-icon {
display: inline-block;
width: 18px;
height: 12px;
background: #fff;
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 16'%3E%3Cpath d='M16.5 13.7l.4-1.4c.2-.8.1-1.5-.3-2-.4-.5-1-.8-1.8-.8l-9.7-.1c-.1 0-.1 0-.2-.1v-.2c0-.1.1-.1.2-.1l9.8-.1c1.2-.1 2.4-1 2.9-2.2l.6-1.5v-.3C17.7 2 15.2 0 12.2 0 9.4 0 7 1.8 6.2 4.3c-.5-.4-1.2-.6-2-.5C2.9 4 1.9 5 1.7 6.3c0 .3 0 .7.1 1C-.3 7.4 0 9.2 0 10.9c0 .1.1.2.2.2h16.1c.1 0 .2 0 .2-.1v2.7z'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 16'%3E%3Cpath d='M16.5 13.7l.4-1.4c.2-.8.1-1.5-.3-2-.4-.5-1-.8-1.8-.8l-9.7-.1c-.1 0-.1 0-.2-.1v-.2c0-.1.1-.1.2-.1l9.8-.1c1.2-.1 2.4-1 2.9-2.2l.6-1.5v-.3C17.7 2 15.2 0 12.2 0 9.4 0 7 1.8 6.2 4.3c-.5-.4-1.2-.6-2-.5C2.9 4 1.9 5 1.7 6.3c0 .3 0 .7.1 1C-.3 7.4 0 9.2 0 10.9c0 .1.1.2.2.2h16.1c.1 0 .2 0 .2-.1v2.7z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.emdash-overview {
margin: 0;
}

.emdash-overview li {
padding: 4px 0;
border-bottom: 1px solid #f0f0f1;
}

.emdash-count {
color: #646970;
font-size: 12px;
background: #f0f0f1;
border-radius: 10px;
padding: 1px 8px;
margin-inline-start: 6px;
}

.emdash-yes { color: #00a32a; }
.emdash-no { color: #d63638; }
.emdash-overview--excluded li { color: #646970; }
96 changes: 96 additions & 0 deletions plugins/emdash-exporter/assets/wizard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* EmDash migration wizard: key generation, copy, revoke.
* Vanilla JS, no dependencies.
*/
(function () {
"use strict";

var cfg = window.emdashWizard || {};
var generateBtn = document.getElementById("emdash-generate-key");
var revokeBtn = document.getElementById("emdash-revoke-key");
var result = document.getElementById("emdash-key-result");
var keyInput = document.getElementById("emdash-key-value");
var copyBtn = document.getElementById("emdash-copy-key");
var errorBox = document.getElementById("emdash-key-error");

function post(action) {
var body = new URLSearchParams();
body.set("action", action);
body.set("nonce", cfg.nonce);
return fetch(cfg.ajaxUrl, {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: body.toString(),
}).then(function (response) {
return response.json();
});
}

function showError(message) {
if (!errorBox) return;
errorBox.querySelector("p").textContent = message || cfg.i18n.error;
errorBox.hidden = false;
}

if (generateBtn) {
generateBtn.addEventListener("click", function () {
generateBtn.disabled = true;
var originalText = generateBtn.textContent;
generateBtn.textContent = cfg.i18n.generating;
errorBox.hidden = true;

post("emdash_generate_key")
.then(function (data) {
if (data && data.success && data.data && data.data.key) {
keyInput.value = data.data.key;
result.hidden = false;
keyInput.focus();
keyInput.select();
} else {
showError(data && data.data && data.data.message);
}
})
.catch(function () {
showError();
})
.finally(function () {
generateBtn.disabled = false;
generateBtn.textContent = originalText;
});
});
}

if (copyBtn) {
copyBtn.addEventListener("click", function () {
keyInput.select();
var done = function () {
copyBtn.textContent = cfg.i18n.copied;
setTimeout(function () {
copyBtn.textContent = cfg.i18n.copy;
}, 2000);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(keyInput.value).then(done);
} else {
document.execCommand("copy");
done();
}
});
}

if (revokeBtn) {
revokeBtn.addEventListener("click", function () {
if (!window.confirm(cfg.i18n.revokeConfirm)) return;
revokeBtn.disabled = true;
post("emdash_revoke_key")
.then(function () {
window.location.reload();
})
.catch(function () {
revokeBtn.disabled = false;
showError();
});
});
}
})();
56 changes: 17 additions & 39 deletions plugins/emdash-exporter/emdash-exporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,46 @@
/**
* Plugin Name: EmDash Exporter
* Plugin URI: https://github.com/emdash-cms/wp-emdash
* Description: Export your WordPress content to EmDash CMS with one click
* Version: 1.0.0
* Description: Migrate your WordPress content to EmDash CMS with a guided wizard and one-click migration key
* Version: 1.2.0
* Requires at least: 5.6
* Requires PHP: 7.4
* Author: Matt Kane
* Author: EmDash
* License: GPL3
* Text Domain: emdash-exporter
*/

defined('ABSPATH') || exit;

define('EMDASH_EXPORTER_VERSION', '1.0.0');
define('EMDASH_EXPORTER_VERSION', '1.2.0');
define('EMDASH_EXPORTER_PATH', plugin_dir_path(__FILE__));

// Load REST API endpoints
require_once EMDASH_EXPORTER_PATH . 'includes/class-rest-controller.php';
require_once EMDASH_EXPORTER_PATH . 'includes/class-content-exporter.php';
require_once EMDASH_EXPORTER_PATH . 'includes/class-media-exporter.php';
require_once EMDASH_EXPORTER_PATH . 'includes/class-menu-exporter.php';
require_once EMDASH_EXPORTER_PATH . 'includes/class-comment-exporter.php';
require_once EMDASH_EXPORTER_PATH . 'includes/class-i18n-exporter.php';
require_once EMDASH_EXPORTER_PATH . 'includes/class-health-check.php';
require_once EMDASH_EXPORTER_PATH . 'includes/class-migration-key.php';
require_once EMDASH_EXPORTER_PATH . 'includes/class-admin-page.php';

/**
* Initialize the plugin
* Register REST API routes
*/
function emdash_exporter_init() {
// Register REST API routes
$controller = new EmDash_Exporter_REST_Controller();
$controller->register_routes();
}
add_action('rest_api_init', 'emdash_exporter_init');

/**
* Add admin notice with connection info
*/
function emdash_exporter_admin_notice() {
$screen = get_current_screen();
if ($screen->id !== 'plugins') {
return;
}

$site_url = get_site_url();
$api_url = rest_url('emdash/v1/');

?>
<div class="notice notice-info is-dismissible">
<p>
<strong>EmDash Exporter:</strong>
Your site is ready for export. Connect from EmDash using:
<code><?php echo esc_html($site_url); ?></code>
</p>
<p>
<small>API endpoint: <code><?php echo esc_html($api_url); ?></code></small>
</p>
</div>
<?php
}
add_action('admin_notices', 'emdash_exporter_admin_notice');
// Migration wizard (Tools → EmDash Migration)
(new EmDash_Admin_Page())->register();

/**
* Add settings link on plugins page
* Send the user into the wizard right after activation.
*/
function emdash_exporter_settings_link($links) {
$api_url = rest_url('emdash/v1/probe');
$links[] = '<a href="' . esc_url($api_url) . '" target="_blank">Test API</a>';
return $links;
function emdash_exporter_activate() {
set_transient('emdash_exporter_activated', 1, 60);
}
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'emdash_exporter_settings_link');
register_activation_hook(__FILE__, 'emdash_exporter_activate');
Loading