Plugin Directory

Changeset 3409533


Ignore:
Timestamp:
12/03/2025 01:05:32 PM (3 months ago)
Author:
abtestkit
Message:

Release 1.0.5

Location:
abtestkit/trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • abtestkit/trunk/abtestkit.php

    r3406331 r3409533  
    33 * Plugin Name:       abtestkit
    44 * Plugin URI:        https://wordpress.org/plugins/abtestkit
    5  * Description:       Split testing for WooCommerce & WordPress, compatible with all page builders & caching plugins.
    6  * Version:           1.0.4
     5 * Description:       Split testing for WooCommerce & WordPress, compatible with all page builders, themes & caching plugins.
     6 * Version:           1.0.5
    77 * Author:            abtestkit
    88 * License:           GPL-2.0-or-later
     
    28692869    }
    28702870
    2871     // Core WP React + components
     2871    // Load the WordPress editor stack so we can embed classic editors in the wizard
     2872    if ( function_exists( 'wp_enqueue_editor' ) ) {
     2873        wp_enqueue_editor();
     2874    } else {
     2875        wp_enqueue_script( 'wp-editor' );
     2876    }
     2877
     2878    // React + components
    28722879    wp_enqueue_script( 'wp-element' );
    28732880    wp_enqueue_script( 'wp-components' );
     
    28822889        'abtestkit-pt-wizard',
    28832890        plugins_url( 'assets/js/pt-wizard.js', __FILE__ ),
    2884         [ 'wp-element', 'wp-components', 'wp-api-fetch' ],
     2891        [ 'wp-element', 'wp-components', 'wp-api-fetch', 'wp-editor' ],
    28852892        '1.0.1',
    28862893        true
  • abtestkit/trunk/assets/css/admin.css

    r3406331 r3409533  
    9494    border-radius: 6px !important;
    9595}
     96
     97/* Ensure lists display correctly in Version A previews on the product test wizard */
     98#abtestkit-pt-wizard-root .abtestkit-html-preview ul,
     99#abtestkit-pt-wizard-root .abtestkit-html-preview ol {
     100  list-style: disc;
     101  margin-left: 1.5em;
     102  padding-left: 1.5em;
     103}
     104
     105#abtestkit-pt-wizard-root .abtestkit-html-preview ol {
     106  list-style: decimal;
     107}
  • abtestkit/trunk/assets/js/dashboard.js

    r3406331 r3409533  
    7979          });
    8080
    81     const makeTab = (label, value) =>
    82       h(
    83         'button',
    84         {
    85           type: 'button',
    86           className:
    87             'nav-tab' + (activeTab === value ? ' nav-tab-active' : ''),
    88           onClick: () => setActiveTab(value),
    89           style: { cursor: 'pointer' },
    90         },
    91         tabCounts[value] > 0 ? `${label} (${tabCounts[value]})` : label
    92       );
     81      const makeTab = (label, value) =>
     82        h(
     83          'button',
     84          {
     85            type: 'button',
     86            className:
     87              'nav-tab' + (activeTab === value ? ' nav-tab-active' : ''),
     88            onClick: () => setActiveTab(value),
     89            style: {
     90              cursor: 'pointer',
     91              flex: 1,
     92              textAlign: 'center',
     93              padding: '8px 16px',
     94            },
     95          },
     96          tabCounts[value] > 0 ? `${label} (${tabCounts[value]})` : label
     97        );
     98
    9399
    94100    return h(
  • abtestkit/trunk/assets/js/pt-wizard.js

    r3406331 r3409533  
    11/* assets/js/pt-wizard.js */
    22(function (wp) {
    3   const { createElement: h, Fragment, useState, useEffect } = wp.element;
     3  const { createElement: h, Fragment, useState, useEffect, useRef } = wp.element;
    44  const {
    55    Button,
     
    6060    frame.open();
    6161  };
     62
     63  // TinyMCE-based classic editor field used for Version B product descriptions
     64  const ClassicEditorField = ({ id, value, onChange, help, readOnly = false }) => {
     65    const textareaId = id;
     66
     67    // Initialise editor once
     68    useEffect(() => {
     69      if (!window.wp || !wp.editor || !wp.editor.initialize) return;
     70
     71      const $ = window.jQuery || window.$;
     72      if (!$) return;
     73
     74      // Set initial value in the textarea before turning it into an editor
     75      if (typeof value === "string") {
     76        $("#" + textareaId).val(value);
     77      }
     78
     79      // Clean up any previous editor instance on this ID
     80      if (wp.editor.remove) {
     81        try {
     82          wp.editor.remove(textareaId);
     83        } catch (e) {}
     84      }
     85
     86      const onInit = (event, editor) => {
     87        if (!editor || editor.id !== textareaId) return;
     88        editor.on("change keyup", () => {
     89          const content = editor.getContent();
     90          if (typeof onChange === "function") {
     91            onChange(content);
     92          }
     93        });
     94      };
     95
     96      $(document).on(
     97        "tinymce-editor-init.abtestkit-" + textareaId,
     98        onInit
     99      );
     100
     101      wp.editor.initialize(textareaId, {
     102        tinymce: {
     103          wpautop: true,
     104          toolbar1: readOnly ? false : "formatselect,bold,italic,bullist,numlist,link,unlink,blockquote,undo,redo",
     105          toolbar2: "",
     106          readonly: readOnly,
     107        },
     108        quicktags: true,
     109      });
     110
     111      return () => {
     112        $(document).off(
     113          "tinymce-editor-init.abtestkit-" + textareaId,
     114          onInit
     115        );
     116        if (wp.editor.remove) {
     117          try {
     118            wp.editor.remove(textareaId);
     119          } catch (e) {}
     120        }
     121      };
     122    }, [textareaId]);
     123
     124    // Keep programmatic value changes (like prefill from Version A) in sync
     125    useEffect(() => {
     126      if (!window.tinymce) return;
     127      const ed = window.tinymce.get(textareaId);
     128      if (!ed) return;
     129      if (typeof value === "string" && value !== ed.getContent()) {
     130        ed.setContent(value);
     131      }
     132    }, [textareaId, value]);
     133
     134    return h(
     135      "div",
     136      null,
     137      [
     138        h("textarea", {
     139          id: textareaId,
     140          defaultValue: value || "",
     141          style: { width: "100%", minHeight: 200 },
     142        }),
     143        help
     144          ? h(
     145              "p",
     146              { style: { marginTop: 4, color: "#6c7781", fontSize: 12 } },
     147              help
     148            )
     149          : null,
     150      ]
     151    );
     152  };
     153
     154  // Utility: strip HTML tags for plain-text placeholders, etc.
     155  const stripHtml = (html) =>
     156    typeof html === "string"
     157      ? html.replace(/<\/?[^>]+(>|$)/g, "").replace(/\s+/g, " ").trim()
     158      : "";
    62159
    63160  /* ──────────────────────────────────────────────────────────────
     
    300397  const hasPicks = Array.isArray(selected) && selected.length > 0;
    301398  const buttonLabel = picking
    302     ? "Now click a target in the preview… (Esc to cancel)"
     399    ? "Now click your target in the preview above… (Esc to cancel)"
    303400    : hasPicks
    304401      ? "Select another"
    305       : "Select target in the Preview";
     402      : "Begin selecting click targets";
    306403
    307404  return h(Fragment, null, [
     
    423520    );
    424521
    425   /* WordPress-style list table for selecting pages */
     522  /* WordPress-style list table for selecting pages (with simple pagination) */
    426523  const PageTable = ({ pages, selectedId, onSelect, empty = "No pages found." }) => {
    427     return h(
     524    const [pageIndex, setPageIndex] = useState(0);
     525    const pageSize = 25;
     526
     527    const list = Array.isArray(pages) ? pages : [];
     528    const total = list.length;
     529    const totalPages = total ? Math.ceil(total / pageSize) : 1;
     530
     531    // Clamp page index when results change (e.g. new search)
     532    useEffect(() => {
     533      if (pageIndex > 0 && pageIndex > totalPages - 1) {
     534        setPageIndex(0);
     535      }
     536    }, [total]);
     537
     538    const start = pageIndex * pageSize;
     539    const end = Math.min(start + pageSize, total);
     540    const visible = total ? list.slice(start, end) : [];
     541
     542    const table = h(
    428543      "table",
    429544      { className: "wp-list-table widefat fixed striped", style: { marginTop: 12 } },
    430545      [
    431         h("thead", null,
     546        h(
     547          "thead",
     548          null,
    432549          h("tr", null, [
    433550            h("th", { style: { width: 24 } }, ""), // radio column
     
    441558          "tbody",
    442559          null,
    443           pages && pages.length
    444             ? pages.map((p) => {
     560          visible && visible.length
     561            ? visible.map((p) => {
    445562                const isSel = String(selectedId || "") === String(p.id);
    446563
    447                 // New: mark pages/products that are already in a running test
     564                // Mark pages/products that are already in a running test
    448565                const isLocked = !!p.in_running_test;
    449566                const statusLabel = isLocked
     
    464581                  },
    465582                  [
    466                     h("td", null,
     583                    h(
     584                      "td",
     585                      null,
    467586                      h("input", {
    468587                        type: "radio",
     
    474593                      })
    475594                    ),
    476                     h("td", null,
    477                       h("strong", null, p.title || "(no title)")
    478                     ),
     595                    h("td", null, h("strong", null, p.title || "(no title)")),
    479596                    h("td", null, p.category || "—"),
    480597                    h("td", null, statusLabel),
     
    488605                h(
    489606                  "td",
    490                   { colSpan: 4, style: { padding: "12px 10px", color: "#6c7781" } },
     607                  {
     608                    colSpan: 5,
     609                    style: { padding: "12px 10px", color: "#6c7781" },
     610                  },
    491611                  empty
    492612                )
     
    495615      ]
    496616    );
     617
     618    const pager =
     619      totalPages > 1
     620        ? h(
     621            "div",
     622            {
     623              style: {
     624                marginTop: 8,
     625                display: "flex",
     626                justifyContent: "space-between",
     627                alignItems: "center",
     628                fontSize: 12,
     629                color: "#6c7781",
     630              },
     631            },
     632            [
     633              h(
     634                "span",
     635                null,
     636                `Showing ${start + 1}–${end} of ${total}`
     637              ),
     638              h(
     639                "div",
     640                { style: { display: "flex", gap: 4 } },
     641                [
     642                  h(
     643                    Button,
     644                    {
     645                      isSmall: true,
     646                      disabled: pageIndex === 0,
     647                      onClick: () => setPageIndex((i) => Math.max(0, i - 1)),
     648                    },
     649                    "Previous"
     650                  ),
     651                  h(
     652                    "span",
     653                    { style: { padding: "2px 6px" } },
     654                    `Page ${pageIndex + 1} of ${totalPages}`
     655                  ),
     656                  h(
     657                    Button,
     658                    {
     659                      isSmall: true,
     660                      disabled: pageIndex >= totalPages - 1,
     661                      onClick: () =>
     662                        setPageIndex((i) =>
     663                          Math.min(totalPages - 1, i + 1)
     664                        ),
     665                    },
     666                    "Next"
     667                  ),
     668                ]
     669              ),
     670            ]
     671          )
     672        : null;
     673
     674    return h("div", null, [table, pager]);
    497675  };
     676
    498677
    499678  // Small iframe preview used on the "Review versions" step
     
    590769    const [showProductBImage, setShowProductBImage] = useState(false);
    591770    const [showProductBGallery, setShowProductBGallery] = useState(false);
     771
     772    // Track whether we've already pre-filled Version B descriptions from Version A
     773    const shortHydratedRef = useRef(false);
     774    const longHydratedRef = useRef(false);
    592775
    593776    // Fetch lists
     
    10031186        null,
    10041187        postType === "product"
    1005           ? "Select a WooCommerce product to test (Version A / control). Start typing to search."
    1006           : "Select a page to test (Version A / control). Start typing to search."
     1188          ? "Select a WooCommerce product to test."
     1189          : "Select a page to test."
    10071190      ),
    10081191      h(SearchControl, {
     
    11081291                  ? productMeta.gallery_urls.join(", ")
    11091292                  : "";
     1293
     1294              // Plain-text versions for placeholders
     1295              const productAShortPlain = stripHtml(productAShort);
     1296              const productALongPlain = stripHtml(productALong);
     1297
     1298              // Prefill Version B editors with Version A content once (but let the user clear them)
     1299              useEffect(() => {
     1300                if (
     1301                  postType === "product" &&
     1302                  productAShort &&
     1303                  !shortHydratedRef.current &&
     1304                  !productBShortDesc
     1305                ) {
     1306                  setProductBShortDesc(productAShort);
     1307                  shortHydratedRef.current = true;
     1308                }
     1309              }, [postType, productAShort, productBShortDesc]);
     1310
     1311              useEffect(() => {
     1312                if (
     1313                  postType === "product" &&
     1314                  productALong &&
     1315                  !longHydratedRef.current &&
     1316                  !productBLongDesc
     1317                ) {
     1318                  setProductBLongDesc(productALong);
     1319                  longHydratedRef.current = true;
     1320                }
     1321              }, [postType, productALong, productBLongDesc]);
    11101322                 
    11111323/* Step 2 – Review versions */
     
    11191331            "p",
    11201332            null,
    1121             "Version A shows the current product fields; Version B lets you override key fields like title, price, descriptions and images."
     1333            "Version A shows the current product fields. Version B lets you override fields like title, price, descriptions and images."
    11221334          ),
    11231335
     
    12001412                    [
    12011413                      h("strong", null, "Short description"),
    1202                       h(
    1203                         "div",
    1204                         {
    1205                           style: {
    1206                             marginTop: 4,
    1207                             padding: "8px 10px",
    1208                             minHeight: 60,
    1209                             background: "#fff",
    1210                             border: "1px solid #dcdcde",
    1211                             borderRadius: 4,
    1212                             whiteSpace: "pre-wrap",
    1213                           },
     1414                      h("div", {
     1415                        className: "abtestkit-html-preview",
     1416                        style: {
     1417                          marginTop: 4,
     1418                          padding: "8px 10px",
     1419                          minHeight: 60,
     1420                          background: "#fff",
     1421                          border: "1px solid #dcdcde",
     1422                          borderRadius: 4,
    12141423                        },
    1215                         productAShort || "—"
    1216                       ),
     1424                        dangerouslySetInnerHTML: {
     1425                          __html:
     1426                            productAShort ||
     1427                            "<span style='color:#6c7781'>—</span>",
     1428                        },
     1429                      }),
    12171430                    ]
    12181431                  ),
     
    12241437                    [
    12251438                      h("strong", null, "Description"),
    1226                       h(
    1227                         "div",
    1228                         {
    1229                           style: {
    1230                             marginTop: 4,
    1231                             padding: "8px 10px",
    1232                             minHeight: 80,
    1233                             maxHeight: 160,
    1234                             overflow: "auto",
    1235                             background: "#fff",
    1236                             border: "1px solid #dcdcde",
    1237                             borderRadius: 4,
    1238                             whiteSpace: "pre-wrap",
    1239                           },
     1439                      h("div", {
     1440                        className: "abtestkit-html-preview",
     1441                        style: {
     1442                          marginTop: 4,
     1443                          padding: "8px 10px",
     1444                          minHeight: 80,
     1445                          maxHeight: 160,
     1446                          overflow: "auto",
     1447                          background: "#fff",
     1448                          border: "1px solid #dcdcde",
     1449                          borderRadius: 4,
    12401450                        },
    1241                         productALong || "—"
    1242                       ),
    1243                     ]
    1244                   ),
    1245 
    1246                   // Main image
    1247                   h(
    1248                     "div",
    1249                     { style: { marginTop: 16 } },
    1250                     [
    1251                       h("strong", null, "Product image"),
    1252                       h(
    1253                         "div",
    1254                         { style: { marginTop: 4 } },
    1255                         productAImageUrl
    1256                           ? h("img", {
    1257                               src: productAImageUrl,
    1258                               alt: "",
    1259                               style: {
    1260                                 maxWidth: "100%",
    1261                                 height: "auto",
    1262                                 borderRadius: 4,
    1263                                 border: "1px solid #dcdcde",
    1264                               },
    1265                             })
    1266                           : h(
    1267                               "span",
    1268                               { style: { color: "#6c7781" } },
    1269                               "No image set"
    1270                             )
    1271                       ),
     1451                        dangerouslySetInnerHTML: {
     1452                          __html:
     1453                            productALong ||
     1454                            "<span style='color:#6c7781'>—</span>",
     1455                        },
     1456                      }),
    12721457                    ]
    12731458                  ),
     
    13731558                            onChange: setProductBSalePrice,
    13741559                            placeholder: productASale || "e.g. 79.00",
    1375                             help:
    1376                               "Leave blank to let WooCommerce use Version B’s regular price only.",
    13771560                          }),
    13781561                        ]
     
    13871570                    [
    13881571                      h("strong", null, "Short description"),
    1389                       h(TextareaControl, {
     1572                      h(ClassicEditorField, {
     1573                        id: "abtestkit-product-short-desc-b",
    13901574                        value: productBShortDesc,
    13911575                        onChange: setProductBShortDesc,
    1392                         rows: 4,
    1393                         placeholder:
    1394                           productAShort || "Version B short description…",
    13951576                      }),
    13961577                    ]
     
    14031584                    [
    14041585                      h("strong", null, "Description"),
    1405                       h(TextareaControl, {
     1586                      h(ClassicEditorField, {
     1587                        id: "abtestkit-product-long-desc-b",
    14061588                        value: productBLongDesc,
    14071589                        onChange: setProductBLongDesc,
    1408                         rows: 6,
    1409                         placeholder:
    1410                           productALong || "Version B full description…",
     1590                        help: productALongPlain
     1591                          ? "Clear this field to reuse Version A’s full description."
     1592                          : "",
    14111593                      }),
    14121594                    ]
  • abtestkit/trunk/readme.txt

    r3406331 r3409533  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.0.4
     7Stable tag: 1.0.5
    88License: GPL-2.0-or-later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Split testing for WooCommerce & WordPress, compatible with all page builders & caching plugins.
     11Split testing for WooCommerce & WordPress, compatible with all themes, page builders & caching plugins.
    1212
    1313== Description ==
     
    1515= The simplest way to A/B test in WordPress =
    1616
    17 **abtestkit** lets you run clean, fast, privacy-friendly AB tests without code or complicated interfaces. 
     17**abtestkit** lets you run clean, fast, privacy-friendly AB tests without coding or complicated interfaces. 
    1818Create full-page split tests in seconds, track performance automatically, and apply the winner with one click.
    1919
     
    6262Full-page testing works universally.
    6363
     64= What themes are supported? =
     65abtestkit works with all themes.
     66
    6467= How are winners decided? =
    65 You don't need to analyse the results yourself. abtestkit uses a **Bayesian evaluation model** with a 95% confidence threshold, then automatically declares the winning variant. You can apply the winner with one click.
     68abtestkit uses a **Bayesian evaluation model** with a 95% confidence threshold, then automatically declares the winning variant. You can apply the winner with one click.
    6669
    6770= Where is data stored? =
    68 All impression and click events are stored in your WordPress database (`wp_ab_test_events` table). Nothing is sent externally unless you explicitly opt into anonymous telemetry.
     71All impression and click events are stored in your WordPress database (`wp_ab_test_events` table).
    6972
    7073= Is this plugin free? =
     
    7275
    7376== Changelog ==
     77
     78= 1.0.5 =
     79* Improved WooCommerce Product test creation UI
    7480
    7581= 1.0.4 =
     
    102108== Upgrade Notice ==
    103109
     110= 1.0.5 =
     111Improved WooCommerce Product test creation UI
     112
    104113= 1.0.4 =
    105114Major update with WooCommerce Product compatibility and off page click conversion goals.
Note: See TracChangeset for help on using the changeset viewer.