diff --git a/CHANGELOG.md b/CHANGELOG.md
index dddb2c0..1f11be6 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,24 @@ All notable changes to PRAutoBlogger will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project uses [Semantic Versioning](https://semver.org/).
+## [0.6.0] — 2026-04-20
+
+### Added
+- **Generate article from idea.** New "Generate" column in the Ideas browser.
+ Click any idea's Generate button to produce an article directly, bypassing
+ the scheduled pipeline's collect → analyze → score steps. The button shows
+ real-time stage updates (drafting, editing, publishing) via per-idea status
+ polling, then swaps to Edit/View links with cost on completion.
+ - `Ideas_Browser::on_ajax_generate_from_idea()` — AJAX trigger that stores
+ the idea and schedules a one-shot WP-Cron event.
+ - `Ideas_Browser::on_cron_generate_from_idea()` — Background handler that
+ runs Article_Worker for the selected idea.
+ - `Ideas_Browser::on_ajax_idea_gen_status()` — Per-idea status polling.
+ - `assets/js/ideas-browser.js` — Button click, AJAX, polling, and UI state.
+ - Per-idea transients (`prab_idea_gen_{id}`) for independent status tracking.
+ - Page-load polling resume for ideas that were mid-generation.
+ - Generation lock respected — only one generation at a time.
+
## [0.5.3] — 2026-04-20
### Fixed
diff --git a/assets/js/ideas-browser.js b/assets/js/ideas-browser.js
new file mode 100644
index 0000000..c393a2e
--- /dev/null
+++ b/assets/js/ideas-browser.js
@@ -0,0 +1,157 @@
+/**
+ * PRAutoBlogger Ideas Browser — "Generate Article" button handler.
+ *
+ * Each idea row has a Generate button that triggers single-article generation
+ * via AJAX → WP-Cron background job. Polls for status and shows stage updates
+ * in the button cell, then swaps to a "View" link on completion.
+ *
+ * @see admin/class-ideas-browser.php — AJAX handlers.
+ * @see templates/admin/ideas-browser.php — Button markup.
+ */
+(function ($) {
+ 'use strict';
+
+ var config = window.prautobloggerAdmin || {};
+ var pollTimers = {};
+
+ /**
+ * Kick off generation for an idea and start polling.
+ *
+ * @param {jQuery} $cell The table cell containing the button.
+ * @param {number} ideaId Analysis result row ID.
+ */
+ function generateFromIdea($cell, ideaId) {
+ $cell.html(
+ ' ' +
+ 'Starting…'
+ );
+
+ $.ajax({
+ url: config.ajaxUrl,
+ method: 'POST',
+ data: {
+ action: 'prautoblogger_generate_from_idea',
+ nonce: config.ideaGenNonce,
+ idea_id: ideaId
+ },
+ timeout: 15000,
+ success: function (response) {
+ if (response.success) {
+ pollIdeaStatus($cell, ideaId);
+ } else {
+ showIdeaError($cell, ideaId, response.data && response.data.message || 'Failed to start.');
+ }
+ },
+ error: function () {
+ showIdeaError($cell, ideaId, 'Request failed.');
+ }
+ });
+ }
+
+ /**
+ * Poll the per-idea status endpoint until generation completes.
+ *
+ * @param {jQuery} $cell The table cell to update.
+ * @param {number} ideaId Analysis result row ID.
+ */
+ function pollIdeaStatus($cell, ideaId) {
+ pollTimers[ideaId] = setInterval(function () {
+ $.ajax({
+ url: config.ajaxUrl,
+ method: 'POST',
+ data: {
+ action: 'prautoblogger_idea_gen_status',
+ nonce: config.ideaGenNonce,
+ idea_id: ideaId
+ },
+ timeout: 10000,
+ success: function (response) {
+ if (!response.success) return;
+ var d = response.data;
+
+ if (d.status === 'running') {
+ $cell.find('.prab-idea-stage').text(d.stage || 'Generating…');
+ return;
+ }
+
+ // Terminal — stop polling.
+ clearInterval(pollTimers[ideaId]);
+ delete pollTimers[ideaId];
+
+ if (d.status === 'complete') {
+ showIdeaComplete($cell, ideaId, d);
+ } else if (d.status === 'error') {
+ showIdeaError($cell, ideaId, d.message || 'Generation failed.');
+ } else {
+ showIdeaError($cell, ideaId, 'Unknown status.');
+ }
+ }
+ });
+ }, 4000);
+ }
+
+ /**
+ * Show the completion state with a View link.
+ *
+ * @param {jQuery} $cell The table cell.
+ * @param {number} ideaId Analysis result row ID.
+ * @param {Object} data Server response data.
+ */
+ function showIdeaComplete($cell, ideaId, data) {
+ var html = ' ';
+ html += 'Done';
+
+ if (data.post_id) {
+ var editUrl = config.adminUrl + 'post.php?post=' + data.post_id + '&action=edit';
+ var viewUrl = config.adminUrl + '?p=' + data.post_id;
+ html += ' — Edit';
+ html += ' | View';
+ }
+
+ if (data.cost > 0) {
+ html += '
$' + parseFloat(data.cost).toFixed(4) + '
';
+ }
+
+ $cell.html(html);
+ }
+
+ /**
+ * Show an error state with a retry button.
+ *
+ * @param {jQuery} $cell The table cell.
+ * @param {number} ideaId Analysis result row ID.
+ * @param {string} message Error message.
+ */
+ function showIdeaError($cell, ideaId, message) {
+ $cell.html(
+ ' ' +
+ '' + $('').text(message).html() + '' +
+ '
'
+ );
+ }
+
+ // Click handler for Generate / Retry buttons (event delegation).
+ $(document).on('click', '.prab-gen-idea-btn', function () {
+ var $btn = $(this);
+ if ($btn.prop('disabled')) return;
+
+ var ideaId = $btn.data('idea-id');
+ var $cell = $btn.closest('td');
+
+ generateFromIdea($cell, ideaId);
+ });
+
+ // On page load, resume polling for any ideas that are mid-generation.
+ $(function () {
+ $('.prab-idea-gen-cell[data-status="running"]').each(function () {
+ var $cell = $(this);
+ var ideaId = $cell.data('idea-id');
+ $cell.html(
+ ' ' +
+ 'Resuming…'
+ );
+ pollIdeaStatus($cell, ideaId);
+ });
+ });
+
+})(jQuery);
diff --git a/includes/admin/class-admin-page.php b/includes/admin/class-admin-page.php
index f6f319e..f080879 100755
--- a/includes/admin/class-admin-page.php
+++ b/includes/admin/class-admin-page.php
@@ -72,11 +72,13 @@ class PRAutoBlogger_Admin_Page {
wp_localize_script( 'prautoblogger-admin', 'prautobloggerAdmin', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
+ 'adminUrl' => admin_url(),
'generateNonce' => wp_create_nonce( 'prautoblogger_generate_now' ),
'imageNonce' => wp_create_nonce( 'prautoblogger_generate_image' ),
'testNonce' => wp_create_nonce( 'prautoblogger_test_connection' ),
'modelsNonce' => wp_create_nonce( 'prautoblogger_get_models' ),
'reviewNonce' => wp_create_nonce( 'prautoblogger_review_queue' ),
+ 'ideaGenNonce' => wp_create_nonce( 'prautoblogger_idea_gen' ),
'generatingText' => __( 'Generating...', 'prautoblogger' ),
'generateText' => __( 'Generate Now', 'prautoblogger' ),
'testingText' => __( 'Testing...', 'prautoblogger' ),
@@ -84,6 +86,11 @@ class PRAutoBlogger_Admin_Page {
// Image models for the picker — defined in the settings class to keep this file short.
'imageModels' => PRAutoBlogger_Settings_Fields_Extended::get_image_models(),
] );
+
+ // Ideas browser: generate-from-idea JS (only on the Ideas page).
+ if ( 'prautoblogger_page_prautoblogger-ideas' === $hook_suffix ) {
+ wp_enqueue_script( 'prautoblogger-ideas-browser', PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/ideas-browser.js', [ 'jquery', 'prautoblogger-admin' ], PRAUTOBLOGGER_VERSION, true );
+ }
}
/** Render the settings page (delegates to template). */
diff --git a/includes/admin/class-ideas-browser.php b/includes/admin/class-ideas-browser.php
index e80e87e..7f64510 100644
--- a/includes/admin/class-ideas-browser.php
+++ b/includes/admin/class-ideas-browser.php
@@ -2,30 +2,25 @@
declare(strict_types=1);
/**
- * Admin page listing analysis results (article ideas) from all pipeline runs.
- *
- * Shows every idea the content analyzer has surfaced: topic, type, relevance
- * score, frequency, source, and metadata (key points, keywords). Filterable
- * by type and source, with pagination. Gives the CEO a live view of the idea
- * pipeline without needing to trigger a generation run.
+ * Admin page listing analysis results (article ideas) with per-idea generation.
*
* Triggered by: PRAutoBlogger::register_admin_hooks() on `admin_menu`.
- * Dependencies: WordPress $wpdb for direct table queries.
+ * Dependencies: WordPress $wpdb, Article_Worker, Cost_Tracker, Generation_Lock.
*
- * @see core/class-content-analyzer.php — Produces the rows this page displays.
- * @see models/class-analysis-result.php — Data model for analysis results.
+ * @see core/class-article-worker.php — Generates articles from ideas.
* @see templates/admin/ideas-browser.php — Renders the HTML.
- * @see ARCHITECTURE.md — Data flow step 2.
*/
class PRAutoBlogger_Ideas_Browser {
private const PER_PAGE = 30;
- /**
- * Register the Ideas submenu page under PRAutoBlogger.
- *
- * @return void
- */
+ /** Transient prefix for per-idea generation status. */
+ private const STATUS_PREFIX = 'prab_idea_gen_';
+
+ /** How long to keep per-idea status (seconds). */
+ private const STATUS_TTL = 600;
+
+ /** Register the Ideas submenu page under PRAutoBlogger. */
public function on_register_menu(): void {
add_submenu_page(
'prautoblogger-settings',
@@ -37,14 +32,7 @@ class PRAutoBlogger_Ideas_Browser {
);
}
- /**
- * Render the ideas browser page.
- *
- * Reads filter/pagination parameters from $_GET, queries the analysis_results
- * table, and includes the template.
- *
- * @return void
- */
+ /** Render the ideas browser page. */
public function render_page(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
@@ -52,9 +40,8 @@ class PRAutoBlogger_Ideas_Browser {
$paged = isset( $_GET['paged'] ) ? absint( $_GET['paged'] ) : 1;
$type_filter = isset( $_GET['type'] ) ? sanitize_key( $_GET['type'] ) : '';
- $source_filter = isset( $_GET['source'] ) ? sanitize_key( $_GET['source'] ) : '';
- $result = $this->query_ideas( $paged, $type_filter, $source_filter );
+ $result = $this->query_ideas( $paged, $type_filter );
$rows = $result['rows'];
$total = $result['total'];
$total_pages = (int) ceil( $total / self::PER_PAGE );
@@ -64,16 +51,202 @@ class PRAutoBlogger_Ideas_Browser {
}
/**
- * Query analysis results with optional filtering and pagination.
+ * AJAX: trigger article generation from a specific idea.
*
- * Side effects: database SELECT.
+ * Loads the analysis result, stores it as a transient, sets a per-idea
+ * status transient, and schedules a one-shot cron event so the actual
+ * generation runs in a separate PHP process (Hostinger 120s limit).
*
- * @param int $paged Page number (1-based).
- * @param string $type Filter by analysis_type (empty = all).
- * @param string $source Filter: 'llm_research' or 'reddit' based on source_ids content.
- * @return array{rows: array[], total: int}
+ * Side effects: transient writes, cron scheduling.
*/
- private function query_ideas( int $paged, string $type, string $source ): array {
+ public function on_ajax_generate_from_idea(): void {
+ check_ajax_referer( 'prautoblogger_idea_gen', 'nonce' );
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( [ 'message' => 'Insufficient permissions.' ], 403 );
+ return;
+ }
+
+ $idea_id = absint( $_POST['idea_id'] ?? 0 );
+ if ( $idea_id < 1 ) {
+ wp_send_json_error( [ 'message' => 'Invalid idea ID.' ] );
+ return;
+ }
+
+ // Build Article_Idea from the analysis row.
+ $idea_data = self::load_idea_data( $idea_id );
+ if ( null === $idea_data ) {
+ wp_send_json_error( [ 'message' => 'Idea not found.' ] );
+ return;
+ }
+
+ // Store idea for the cron handler and set initial "running" status.
+ set_transient( self::STATUS_PREFIX . 'data_' . $idea_id, $idea_data, self::STATUS_TTL );
+ set_transient( self::STATUS_PREFIX . $idea_id, [
+ 'status' => 'running',
+ 'stage' => __( 'Starting generation…', 'prautoblogger' ),
+ 'started' => time(),
+ ], self::STATUS_TTL );
+
+ // Schedule background cron — passes idea_id as argument.
+ $hook = 'prautoblogger_generate_from_idea';
+ if ( ! wp_next_scheduled( $hook, [ $idea_id ] ) ) {
+ wp_schedule_single_event( time(), $hook, [ $idea_id ] );
+ }
+ spawn_cron();
+ wp_remote_post(
+ site_url( 'wp-cron.php?doing_wp_cron=' . sprintf( '%.22F', microtime( true ) ) ),
+ [ 'timeout' => 0.01, 'blocking' => false, 'sslverify' => false ]
+ );
+
+ wp_send_json_success( [ 'message' => 'Generation started.' ] );
+ }
+
+ /**
+ * AJAX: return per-idea generation status for frontend polling.
+ *
+ * Side effects: none (reads transient only).
+ */
+ public function on_ajax_idea_gen_status(): void {
+ check_ajax_referer( 'prautoblogger_idea_gen', 'nonce' );
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( [ 'message' => 'Insufficient permissions.' ], 403 );
+ return;
+ }
+
+ $idea_id = absint( $_POST['idea_id'] ?? 0 );
+ $status = get_transient( self::STATUS_PREFIX . $idea_id );
+ if ( ! is_array( $status ) ) {
+ wp_send_json_success( [ 'status' => 'idle' ] );
+ return;
+ }
+
+ wp_send_json_success( $status );
+ }
+
+ /**
+ * Cron handler: generate a single article from a stored idea.
+ *
+ * Runs in a background PHP process. Acquires the generation lock,
+ * runs Article_Worker, and writes the result to the per-idea status transient.
+ *
+ * Side effects: LLM API calls, DB writes, post creation, cost logging.
+ *
+ * @param int $idea_id Analysis result row ID.
+ */
+ public static function on_cron_generate_from_idea( int $idea_id ): void {
+ // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
+ @ignore_user_abort( true );
+ // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
+ @set_time_limit( 300 );
+
+ $key = self::STATUS_PREFIX . $idea_id;
+ $idea_data = get_transient( self::STATUS_PREFIX . 'data_' . $idea_id );
+ if ( ! is_array( $idea_data ) ) {
+ set_transient( $key, [ 'status' => 'error', 'message' => 'Idea data expired.' ], self::STATUS_TTL );
+ return;
+ }
+
+ if ( ! PRAutoBlogger_Generation_Lock::acquire() ) {
+ set_transient( $key, [ 'status' => 'error', 'message' => 'Another generation is running.' ], self::STATUS_TTL );
+ return;
+ }
+
+ try {
+ $idea = new PRAutoBlogger_Article_Idea( $idea_data );
+ $cost_tracker = new PRAutoBlogger_Cost_Tracker();
+ $cost_tracker->set_run_id( wp_generate_uuid4() );
+
+ self::update_idea_stage( $idea_id, __( 'Generating article draft…', 'prautoblogger' ) );
+ $worker = new PRAutoBlogger_Article_Worker( $cost_tracker );
+ $result = $worker->generate( $idea );
+
+ // Amortize any research costs for this single-article run.
+ PRAutoBlogger_Post_Assembler::amortize_research_costs( $cost_tracker->get_run_id() );
+
+ // Find the generated post ID for the "View" link.
+ $post_id = self::find_post_by_run_id( $cost_tracker->get_run_id() );
+
+ set_transient( $key, [
+ 'status' => 'complete',
+ 'generated' => $result['generated'],
+ 'published' => $result['published'],
+ 'cost' => $result['cost'],
+ 'post_id' => $post_id,
+ ], self::STATUS_TTL );
+ } catch ( \Throwable $e ) {
+ PRAutoBlogger_Logger::instance()->error(
+ sprintf( 'Idea generation %s for #%d: %s', get_class( $e ), $idea_id, $e->getMessage() ),
+ 'pipeline'
+ );
+ set_transient( $key, [ 'status' => 'error', 'message' => $e->getMessage() ], self::STATUS_TTL );
+ }
+
+ PRAutoBlogger_Generation_Lock::release();
+ delete_transient( self::STATUS_PREFIX . 'data_' . $idea_id );
+ }
+
+ // ── Private helpers ─────────────────────────────────────────────────
+
+ /** Update the per-idea status transient with a stage message. */
+ private static function update_idea_stage( int $idea_id, string $stage ): void {
+ $key = self::STATUS_PREFIX . $idea_id;
+ $current = get_transient( $key );
+ if ( is_array( $current ) ) {
+ $current['stage'] = $stage;
+ set_transient( $key, $current, self::STATUS_TTL );
+ }
+ }
+
+ /** Find the most recent post created by a specific run_id. */
+ private static function find_post_by_run_id( string $run_id ): ?int {
+ global $wpdb;
+ $table = $wpdb->prefix . 'prautoblogger_generation_log';
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
+ $post_id = $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT DISTINCT post_id FROM {$table} WHERE run_id = %s AND post_id IS NOT NULL LIMIT 1",
+ $run_id
+ )
+ );
+ return null !== $post_id ? (int) $post_id : null;
+ }
+
+ /**
+ * Load an analysis result row and map it to Article_Idea constructor data.
+ *
+ * @param int $idea_id Analysis result row ID.
+ * @return array|null Article_Idea-compatible array, or null.
+ */
+ private static function load_idea_data( int $idea_id ): ?array {
+ global $wpdb;
+ $table = $wpdb->prefix . 'prautoblogger_analysis_results';
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
+ $row = $wpdb->get_row(
+ $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $idea_id ),
+ ARRAY_A
+ );
+ if ( ! $row ) {
+ return null;
+ }
+
+ $meta = json_decode( $row['metadata_json'] ?? '{}', true ) ?: [];
+ return [
+ 'topic' => $row['topic'],
+ 'article_type' => $row['analysis_type'],
+ 'suggested_title' => $meta['suggested_title'] ?? $row['topic'],
+ 'summary' => $row['summary'] ?? '',
+ 'score' => (float) $row['relevance_score'],
+ 'analysis_id' => (int) $row['id'],
+ 'source_ids' => json_decode( $row['source_ids_json'] ?? '[]', true ) ?: [],
+ 'key_points' => $meta['key_points'] ?? [],
+ 'target_keywords' => $meta['target_keywords'] ?? [],
+ ];
+ }
+
+ /** Query analysis results with optional filtering and pagination. */
+ private function query_ideas( int $paged, string $type ): array {
global $wpdb;
$table = $wpdb->prefix . 'prautoblogger_analysis_results';
$where = [];
@@ -103,19 +276,10 @@ class PRAutoBlogger_Ideas_Browser {
? $wpdb->get_results( $full_sql, ARRAY_A )
: $wpdb->get_results( $wpdb->prepare( $full_sql, ...$params ), ARRAY_A );
- return [
- 'rows' => $rows ?: [],
- 'total' => $total,
- ];
+ return [ 'rows' => $rows ?: [], 'total' => $total ];
}
- /**
- * Get counts per analysis_type for the filter badges.
- *
- * Side effects: database SELECT.
- *
- * @return array Map of analysis_type => count.
- */
+ /** Get counts per analysis_type for the filter badges. */
private function get_type_counts(): array {
global $wpdb;
$table = $wpdb->prefix . 'prautoblogger_analysis_results';
diff --git a/includes/class-prautoblogger.php b/includes/class-prautoblogger.php
index cb641df..d70b42b 100644
--- a/includes/class-prautoblogger.php
+++ b/includes/class-prautoblogger.php
@@ -73,7 +73,8 @@ class PRAutoBlogger {
$review_queue = new PRAutoBlogger_Review_Queue();
add_action( 'admin_menu', [ $review_queue, 'on_register_menu' ] );
- add_action( 'admin_menu', [ new PRAutoBlogger_Ideas_Browser(), 'on_register_menu' ] );
+ $ideas_browser = new PRAutoBlogger_Ideas_Browser();
+ add_action( 'admin_menu', [ $ideas_browser, 'on_register_menu' ] );
add_action( 'admin_menu', [ new PRAutoBlogger_Log_Viewer(), 'on_register_menu' ] );
( new PRAutoBlogger_Post_List_Columns() )->register();
@@ -107,6 +108,9 @@ class PRAutoBlogger {
$registry = $this->executor->get_model_registry();
add_action( 'prautoblogger_refresh_model_registry', [ $registry, 'refresh' ] );
+
+ // Single-idea generation from the Ideas browser page.
+ add_action( 'prautoblogger_generate_from_idea', [ 'PRAutoBlogger_Ideas_Browser', 'on_cron_generate_from_idea' ] );
}
/** Register AJAX handlers for admin actions. */
@@ -125,6 +129,10 @@ class PRAutoBlogger {
add_action( 'wp_ajax_prautoblogger_reject_post', [ $review_queue, 'on_ajax_reject_post' ] );
add_action( 'wp_ajax_prautoblogger_clear_logs', [ new PRAutoBlogger_Log_Viewer(), 'on_ajax_clear_logs' ] );
+
+ $ideas = new PRAutoBlogger_Ideas_Browser();
+ add_action( 'wp_ajax_prautoblogger_generate_from_idea', [ $ideas, 'on_ajax_generate_from_idea' ] );
+ add_action( 'wp_ajax_prautoblogger_idea_gen_status', [ $ideas, 'on_ajax_idea_gen_status' ] );
}
/**
diff --git a/prautoblogger.php b/prautoblogger.php
index 0d82547..1899fa2 100644
--- a/prautoblogger.php
+++ b/prautoblogger.php
@@ -8,7 +8,7 @@ declare(strict_types=1);
* Plugin Name: PRAutoBlogger
* Plugin URI: https://peptiderepo.com/prautoblogger
* Description: Monitors social media for trending topics, generates SEO-friendly blog posts using AI, and publishes them on a daily schedule with full cost tracking and self-improvement metrics.
- * Version: 0.5.3
+ * Version: 0.6.0
* Requires at least: 6.0
* Requires PHP: 7.4
* Author: PeptideRepo
@@ -35,7 +35,7 @@ if ( ! defined( 'ABSPATH' ) ) {
| and limits without magic strings.
*/
-define( 'PRAUTOBLOGGER_VERSION', '0.5.3' );
+define( 'PRAUTOBLOGGER_VERSION', '0.6.0' );
define( 'PRAUTOBLOGGER_DB_VERSION', '1.1.0' );
define( 'PRAUTOBLOGGER_PLUGIN_FILE', __FILE__ );
define( 'PRAUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
diff --git a/templates/admin/ideas-browser.php b/templates/admin/ideas-browser.php
index 498185d..c1da706 100644
--- a/templates/admin/ideas-browser.php
+++ b/templates/admin/ideas-browser.php
@@ -19,6 +19,10 @@ if ( ! defined( 'ABSPATH' ) ) {
$base_url = admin_url( 'admin.php?page=prautoblogger-ideas' );
?>
+
@@ -50,35 +54,41 @@ $base_url = admin_url( 'admin.php?page=prautoblogger-ideas' );
- |
- |
+ |
+ |
|
- |
- |
- |
+ |
+ |
+ |
+ |
|
-
-
+
+
@@ -95,9 +105,7 @@ $base_url = admin_url( 'admin.php?page=prautoblogger-ideas' );
|
- = 70 ? '#00a32a' : ( $score_pct >= 40 ? '#dba617' : '#d63638' );
- ?>
+ = 70 ? '#00a32a' : ( $score_pct >= 40 ? '#dba617' : '#d63638' ); ?>
@@ -130,6 +138,24 @@ $base_url = admin_url( 'admin.php?page=prautoblogger-ideas' );
|
+
+
+
+ Edit
+ | View
+
+
+
+
+
+
+ |
|