Plugin Directory

Changeset 3494810


Ignore:
Timestamp:
03/30/2026 04:28:22 PM (2 days ago)
Author:
pagepin
Message:

Release 1.0.3

Location:
pagepin
Files:
14 edited
1 copied

Legend:

Unmodified
Added
Removed
  • pagepin/tags/1.0.3/admin/class-wizard.php

    r3491645 r3494810  
    3838    public function __construct() {
    3939        $this->steps = array(
    40             'welcome'    => array(
     40            'welcome'       => array(
    4141                'name'    => __( 'Welcome', 'pagepin' ),
    4242                'handler' => 'render_step_welcome',
    4343            ),
    44             'recipients' => array(
     44            'recipients'    => array(
    4545                'name'    => __( 'Recipients', 'pagepin' ),
    4646                'handler' => 'render_step_recipients',
    4747            ),
    48             'roles'      => array(
     48            'roles'         => array(
    4949                'name'    => __( 'Roles', 'pagepin' ),
    5050                'handler' => 'render_step_roles',
    5151            ),
    52             'public'     => array(
     52            'public'        => array(
    5353                'name'    => __( 'Public', 'pagepin' ),
    5454                'handler' => 'render_step_public',
     
    341341                    </label>
    342342                    <p class="pagepin-wizard-toggle-description">
    343                         <?php esc_html_e( 'When enabled, any visitor can submit feedback without logging in.', 'pagepin' ); ?>
     343                        <?php esc_html_e( 'When enabled, anyone can submit feedback without a WordPress account.', 'pagepin' ); ?>
    344344                    </p>
    345345                </div>
     
    384384            <h2><?php esc_html_e( 'Pinpoint Feature', 'pagepin' ); ?></h2>
    385385            <p class="pagepin-wizard-description">
    386                 <?php esc_html_e( 'Pinpoint allows your team to discuss specific elements on any page with threaded comments.', 'pagepin' ); ?>
     386                <?php esc_html_e( 'Pinpoint lets your team pin comments to specific page elements for focused, threaded discussions.', 'pagepin' ); ?>
    387387            </p>
    388388
     
    395395                    </label>
    396396                    <p class="pagepin-wizard-toggle-description">
    397                         <?php esc_html_e( 'When enabled, a sidebar will appear on frontend pages for element-based discussions.', 'pagepin' ); ?>
     397                        <?php esc_html_e( 'When enabled, users can pin threaded comments directly to page elements.', 'pagepin' ); ?>
    398398                    </p>
    399399                </div>
    400400
    401401                <div class="pagepin-wizard-conditional" id="pagepin-pinpoint-options" style="<?php echo $pinpoint_enabled ? '' : esc_attr( 'display: none;' ); ?>">
    402                     <h3 style="margin-top: 20px; margin-bottom: 10px; font-size: 14px; font-weight: 600;">
     402                    <h3>
    403403                        <?php esc_html_e( 'Who can create pinpoints?', 'pagepin' ); ?>
    404404                    </h3>
    405                     <div class="pagepin-wizard-roles-grid" style="margin-bottom: 15px;">
     405                    <div class="pagepin-wizard-roles-grid">
    406406                        <?php foreach ( $all_roles as $role_key => $role_name ) : ?>
    407407                            <?php $is_checked = in_array( $role_key, (array) $can_create, true ); ?>
     
    416416                    </div>
    417417
    418                     <h3 style="margin-top: 15px; margin-bottom: 10px; font-size: 14px; font-weight: 600;">
     418                    <h3>
    419419                        <?php esc_html_e( 'Who can view and comment?', 'pagepin' ); ?>
    420420                    </h3>
    421                     <div class="pagepin-wizard-roles-grid" style="margin-bottom: 15px;">
     421                    <div class="pagepin-wizard-roles-grid">
    422422                        <?php foreach ( $all_roles as $role_key => $role_name ) : ?>
    423423                            <?php $is_checked = in_array( $role_key, (array) $can_view, true ); ?>
     
    432432                    </div>
    433433
    434                     <h3 style="margin-top: 15px; margin-bottom: 10px; font-size: 14px; font-weight: 600;">
     434                    <h3>
    435435                        <?php esc_html_e( 'Who can resolve and delete?', 'pagepin' ); ?>
    436436                    </h3>
     
    452452            <p class="pagepin-wizard-hint">
    453453                <span class="dashicons dashicons-info-outline"></span>
    454                 <?php esc_html_e( 'Pinpoint is ideal for internal team discussions about design and content.', 'pagepin' ); ?>
     454                <?php esc_html_e( 'Pinpoint is ideal for team discussions about design, content, and layout decisions.', 'pagepin' ); ?>
    455455            </p>
    456456        </div>
     
    478478        ?>
    479479        <div class="pagepin-wizard-step-inner">
    480             <h2><?php esc_html_e( 'Externe Collaborators', 'pagepin' ); ?></h2>
     480            <h2><?php esc_html_e( 'External Collaborators', 'pagepin' ); ?></h2>
    481481            <p class="pagepin-wizard-description">
    482                 <?php esc_html_e( 'Laden Sie externe Personen per E-Mail-Link zur Zusammenarbeit ein — ohne WordPress-Account.', 'pagepin' ); ?>
     482                <?php esc_html_e( 'Invite external people via email link to collaborate — no WordPress account required.', 'pagepin' ); ?>
    483483            </p>
    484484
     
    488488                        <input type="checkbox" name="enable_collaborators" value="1" <?php checked( $enable_collaborators ); ?> id="pagepin-enable-collaborators" />
    489489                        <span class="pagepin-wizard-toggle-slider"></span>
    490                         <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Externe Collaborators aktivieren', 'pagepin' ); ?></span>
     490                        <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Enable external collaborators', 'pagepin' ); ?></span>
    491491                    </label>
    492492                    <p class="pagepin-wizard-toggle-description">
    493                         <?php esc_html_e( 'Wenn aktiviert, können externe Personen ohne WordPress-Account Feedback geben.', 'pagepin' ); ?>
     493                        <?php esc_html_e( 'When enabled, external people can provide feedback without a WordPress account.', 'pagepin' ); ?>
    494494                    </p>
    495495                </div>
     
    499499                        <input type="checkbox" name="enable_share_links" value="1" <?php checked( $enable_share_links ); ?> />
    500500                        <span class="pagepin-wizard-toggle-slider"></span>
    501                         <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Token-basierte Share-Links aktivieren', 'pagepin' ); ?></span>
     501                        <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Enable token-based share links', 'pagepin' ); ?></span>
    502502                    </label>
    503503                    <p class="pagepin-wizard-toggle-description">
    504                         <?php esc_html_e( 'Erzeugt eindeutige Links, die Sie an externe Personen weiterleiten können.', 'pagepin' ); ?>
     504                        <?php esc_html_e( 'Generates unique links that you can share with external people.', 'pagepin' ); ?>
    505505                    </p>
    506506
    507                     <div style="margin-top: 15px;">
    508                         <label for="pagepin-collaborator-link-expiry" style="display: block; margin-bottom: 5px; font-weight: 600; font-size: 13px;">
    509                             <?php esc_html_e( 'Link-Ablauf', 'pagepin' ); ?>
     507                    <div class="pagepin-wizard-conditional-field">
     508                        <label for="pagepin-collaborator-link-expiry" class="pagepin-wizard-conditional-label">
     509                            <?php esc_html_e( 'Link Expiration', 'pagepin' ); ?>
    510510                        </label>
    511                         <select name="collaborator_link_expiry" id="pagepin-collaborator-link-expiry" class="pagepin-wizard-input" style="width: 100%; max-width: 300px;">
    512                             <option value="0" <?php selected( $link_expiry, 0 ); ?>><?php esc_html_e( 'Nie', 'pagepin' ); ?></option>
    513                             <option value="7" <?php selected( $link_expiry, 7 ); ?>><?php esc_html_e( '7 Tage', 'pagepin' ); ?></option>
    514                             <option value="30" <?php selected( $link_expiry, 30 ); ?>><?php esc_html_e( '30 Tage', 'pagepin' ); ?></option>
    515                             <option value="90" <?php selected( $link_expiry, 90 ); ?>><?php esc_html_e( '90 Tage', 'pagepin' ); ?></option>
     511                        <select name="collaborator_link_expiry" id="pagepin-collaborator-link-expiry" class="pagepin-wizard-input pagepin-wizard-conditional-select">
     512                            <option value="0" <?php selected( $link_expiry, 0 ); ?>><?php esc_html_e( 'Never', 'pagepin' ); ?></option>
     513                            <option value="7" <?php selected( $link_expiry, 7 ); ?>><?php esc_html_e( '7 Days', 'pagepin' ); ?></option>
     514                            <option value="30" <?php selected( $link_expiry, 30 ); ?>><?php esc_html_e( '30 Days', 'pagepin' ); ?></option>
     515                            <option value="90" <?php selected( $link_expiry, 90 ); ?>><?php esc_html_e( '90 Days', 'pagepin' ); ?></option>
    516516                        </select>
    517517                    </div>
     
    521521            <p class="pagepin-wizard-hint">
    522522                <span class="dashicons dashicons-info-outline"></span>
    523                 <?php esc_html_e( 'Sie können Collaborators per @email@adresse.de in Pinpoint-Threads einladen.', 'pagepin' ); ?>
     523                <?php esc_html_e( 'You can invite collaborators via @email in Pinpoint threads.', 'pagepin' ); ?>
    524524            </p>
    525525        </div>
     
    540540        ?>
    541541        <div class="pagepin-wizard-step-inner">
    542             <h2><?php esc_html_e( 'Feedback-Tags', 'pagepin' ); ?></h2>
     542            <h2><?php esc_html_e( 'Feedback Tags', 'pagepin' ); ?></h2>
    543543            <p class="pagepin-wizard-description">
    544                 <?php esc_html_e( 'Organisieren Sie Feedback und Pinpoints mit farbigen Labels.', 'pagepin' ); ?>
     544                <?php esc_html_e( 'Organize feedback and pinpoints with color-coded labels.', 'pagepin' ); ?>
    545545            </p>
    546546
     
    550550                        <input type="checkbox" name="enable_tags" value="1" <?php checked( $enable_tags ); ?> id="pagepin-enable-tags" />
    551551                        <span class="pagepin-wizard-toggle-slider"></span>
    552                         <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Tags aktivieren', 'pagepin' ); ?></span>
     552                        <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Enable tags', 'pagepin' ); ?></span>
    553553                    </label>
    554554                    <p class="pagepin-wizard-toggle-description">
    555                         <?php esc_html_e( 'Wenn aktiviert, können Sie Feedback und Pinpoints mit Tags organisieren.', 'pagepin' ); ?>
     555                        <?php esc_html_e( 'When enabled, you can organize feedback and pinpoints with tags.', 'pagepin' ); ?>
    556556                    </p>
    557557                </div>
    558558
    559559                <div class="pagepin-wizard-conditional" id="pagepin-tags-preview" style="<?php echo $enable_tags ? '' : esc_attr( 'display: none;' ); ?>">
    560                     <div style="display: flex; flex-wrap: wrap; gap: 8px; margin-top: 15px;">
    561                         <span class="pagepin-wizard-tag-pill" style="display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 13px; font-weight: 600; background: #ef4444; color: #ffffff;">
    562                             Bug
     560                    <div class="pagepin-wizard-tag-pills">
     561                        <span class="pagepin-wizard-tag-pill" style="background: #ef4444;">
     562                            <?php esc_html_e( 'Bug', 'pagepin' ); ?>
    563563                        </span>
    564                         <span class="pagepin-wizard-tag-pill" style="display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 13px; font-weight: 600; background: #ff6800; color: #ffffff;">
    565                             Design
     564                        <span class="pagepin-wizard-tag-pill" style="background: #ff6800;">
     565                            <?php esc_html_e( 'Design', 'pagepin' ); ?>
    566566                        </span>
    567                         <span class="pagepin-wizard-tag-pill" style="display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 13px; font-weight: 600; background: #3b82f6; color: #ffffff;">
    568                             Content
     567                        <span class="pagepin-wizard-tag-pill" style="background: #3b82f6;">
     568                            <?php esc_html_e( 'Content', 'pagepin' ); ?>
    569569                        </span>
    570                         <span class="pagepin-wizard-tag-pill" style="display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 13px; font-weight: 600; background: #f59e0b; color: #ffffff;">
    571                             Dringend
     570                        <span class="pagepin-wizard-tag-pill" style="background: #f59e0b;">
     571                            <?php esc_html_e( 'Urgent', 'pagepin' ); ?>
    572572                        </span>
    573573                    </div>
    574                     <p class="pagepin-wizard-toggle-description" style="margin-top: 10px;">
    575                         <?php esc_html_e( 'Diese Standard-Tags werden automatisch erstellt. Weitere Tags können Sie in den Einstellungen hinzufügen.', 'pagepin' ); ?>
     574                    <p class="pagepin-wizard-toggle-description">
     575                        <?php esc_html_e( 'These default tags are created automatically. You can add more tags in the settings.', 'pagepin' ); ?>
    576576                    </p>
    577577                </div>
     
    580580            <p class="pagepin-wizard-hint">
    581581                <span class="dashicons dashicons-info-outline"></span>
    582                 <?php esc_html_e( 'Tags können sowohl Pinshots als auch Pinpoints zugewiesen werden.', 'pagepin' ); ?>
     582                <?php esc_html_e( 'Tags can be assigned to both Pinshots and Pinpoints.', 'pagepin' ); ?>
    583583            </p>
    584584        </div>
  • pagepin/tags/1.0.3/assets/css/admin-wizard.css

    r3491645 r3494810  
    589589}
    590590
     591.pagepin-wizard-conditional h3 {
     592    color: var(--wizard-slate-50);
     593    margin-top: 20px;
     594    margin-bottom: 10px;
     595    font-size: 14px;
     596    font-weight: 600;
     597}
     598
     599.pagepin-wizard-conditional h3:first-child {
     600    margin-top: 0;
     601}
     602
     603.pagepin-wizard-conditional .pagepin-wizard-roles-grid {
     604    margin-bottom: 15px;
     605}
     606
     607.pagepin-wizard-conditional-field {
     608    margin-top: 15px;
     609}
     610
     611.pagepin-wizard-conditional-label {
     612    display: block;
     613    margin-bottom: 5px;
     614    font-weight: 600;
     615    font-size: 13px;
     616    color: var(--wizard-slate-200);
     617}
     618
     619.pagepin-wizard-conditional-select {
     620    width: 100%;
     621    max-width: 300px;
     622}
     623
     624.pagepin-wizard-tag-pills {
     625    display: flex;
     626    flex-wrap: wrap;
     627    gap: 8px;
     628    margin-top: 15px;
     629}
     630
     631.pagepin-wizard-tag-pill {
     632    display: inline-block;
     633    padding: 4px 12px;
     634    border-radius: 12px;
     635    font-size: 13px;
     636    font-weight: 600;
     637    color: #fff;
     638}
     639
    591640/* ===== COMPLETE STEP ===== */
    592641.pagepin-wizard-complete {
  • pagepin/tags/1.0.3/assets/css/pinpoint.css

    r3491645 r3494810  
    669669#pagepin-pinpoint-root .pp-thread-textarea {
    670670    width: 100%;
    671     min-height: 60px;
     671    min-height: 40px;
    672672    max-height: 120px;
    673     padding: 10px 12px;
     673    padding: 8px 12px;
     674    box-sizing: border-box;
    674675    background: var(--pp-color-bg-dark);
    675676    border: 1px solid var(--pp-color-border);
  • pagepin/tags/1.0.3/assets/js/admin-wizard.js

    r3491645 r3494810  
    632632                        $btn.removeClass('loading').prop('disabled', false);
    633633                        // eslint-disable-next-line no-alert
    634                         // eslint-disable-next-line no-alert
    635634                        alert(
    636635                            response.data.message || pagepinWizard.strings.error
  • pagepin/tags/1.0.3/assets/js/pinpoint.js

    r3491645 r3494810  
    18141814                    </div>
    18151815                    <div class="pp-thread-input">
    1816                         <textarea class="pp-thread-textarea" placeholder="${s.writeComment || 'Write your comment...'}" autofocus></textarea>
     1816                        <textarea class="pp-thread-textarea" rows="2" placeholder="${s.writeComment || 'Write your comment...'}" autofocus></textarea>
    18171817                        <div class="pp-thread-input-actions">
    18181818                            <span class="pp-thread-hint">${s.mentionHint || '@ to mention'}</span>
     
    21792179                            ? `
    21802180                        <div class="pp-thread-input">
    2181                             <textarea class="pp-thread-textarea" placeholder="${s.writeReply || 'Write a reply...'}"></textarea>
     2181                            <textarea class="pp-thread-textarea" rows="2" placeholder="${s.writeReply || 'Write a reply...'}"></textarea>
    21822182                            <div class="pp-thread-input-actions">
    21832183                                <span class="pp-thread-hint">${s.mentionHint || '@ to mention'}</span>
  • pagepin/tags/1.0.3/pagepin.php

    r3491645 r3494810  
    44 * Plugin URI: https://pagepin.io
    55 * Description: Visual feedback tool for WordPress - collect client feedback with screenshots and annotations.
    6  * Version: 1.0.2
     6 * Version: 1.0.3
    77 * Author: Patrick Schlesinger
    88 * Author URI: https://pagepin.io
     
    2525}
    2626
    27 define( 'PAGEPIN_VERSION', '1.0.2' );
     27define( 'PAGEPIN_VERSION', '1.0.3' );
    2828define( 'PAGEPIN_PLUGIN_FILE', __FILE__ );
    2929define( 'PAGEPIN_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
     
    835835
    836836        $localize_data = array(
    837             'ajaxurl'      => admin_url( 'admin-ajax.php' ),
    838             'nonce'        => wp_create_nonce( 'pagepin_pinpoint_nonce' ),
    839             'adminNonce'   => wp_create_nonce( 'pagepin_nonce' ),
    840             'enabled'      => true,
    841             'enableTags'   => ! empty( $options['enable_tags'] ),
    842             'canCreate'    => $can_create,
    843             'canView'      => $can_view,
    844             'canViewOwn'   => ! $is_collaborator && $can_create && ! $this->current_user_can_pinpoint( 'view' ),
    845             'canResolve'   => $is_collaborator ? false : $this->current_user_can_pinpoint( 'resolve' ),
    846             'currentUser'  => get_current_user_id(),
     837            'ajaxurl'     => admin_url( 'admin-ajax.php' ),
     838            'nonce'       => wp_create_nonce( 'pagepin_pinpoint_nonce' ),
     839            'adminNonce'  => wp_create_nonce( 'pagepin_nonce' ),
     840            'enabled'     => true,
     841            'enableTags'  => ! empty( $options['enable_tags'] ),
     842            'canCreate'   => $can_create,
     843            'canView'     => $can_view,
     844            'canViewOwn'  => ! $is_collaborator && $can_create && ! $this->current_user_can_pinpoint( 'view' ),
     845            'canResolve'  => $is_collaborator ? false : $this->current_user_can_pinpoint( 'resolve' ),
     846            'currentUser' => get_current_user_id(),
    847847        );
    848848
    849849        // Add collaborator context if active.
    850850        if ( $is_collaborator && $collaborator ) {
    851             $collab_db     = new PagePin_Collaborator_Database();
    852             $accessible    = $collab_db->get_accessible_pinpoints( $collaborator->id );
    853             $collab_nonce  = wp_create_nonce( 'pagepin_collaborator_nonce' );
     851            $collab_db    = new PagePin_Collaborator_Database();
     852            $accessible   = $collab_db->get_accessible_pinpoints( $collaborator->id );
     853            $collab_nonce = wp_create_nonce( 'pagepin_collaborator_nonce' );
    854854
    855855            $localize_data['isCollaborator']      = true;
    856             $localize_data['collaboratorName']     = $collaborator->display_name;
    857             $localize_data['collaboratorNonce']     = $collab_nonce;
    858             $localize_data['accessiblePinpoints']  = $accessible;
     856            $localize_data['collaboratorName']    = $collaborator->display_name;
     857            $localize_data['collaboratorNonce']   = $collab_nonce;
     858            $localize_data['accessiblePinpoints'] = $accessible;
    859859        } else {
    860860            $localize_data['isCollaborator']      = false;
    861             $localize_data['enableCollaborators']  = ! empty( $options['enable_collaborators'] );
    862             $localize_data['enableShareLinks']     = ! empty( $options['enable_share_links'] );
     861            $localize_data['enableCollaborators'] = ! empty( $options['enable_collaborators'] );
     862            $localize_data['enableShareLinks']    = ! empty( $options['enable_share_links'] );
    863863        }
    864864
    865865        $localize_data['strings'] = array(
    866                     'pinpoints'        => __( 'Pinpoints', 'pagepin' ),
    867                     'addPinpoint'      => __( 'Add Pinpoint', 'pagepin' ),
    868                     'toggle'           => __( 'Toggle sidebar', 'pagepin' ),
    869                     'filterOpen'       => __( 'Open', 'pagepin' ),
    870                     'filterResolved'   => __( 'Resolved', 'pagepin' ),
    871                     'filterAll'        => __( 'All', 'pagepin' ),
    872                     'noPinpoints'      => __( 'No pinpoints yet', 'pagepin' ),
    873                     'noPinpointsDesc'  => __( 'Click "Add Pinpoint" to start a discussion on any element.', 'pagepin' ),
    874                     'selectElement'    => __( 'Click on an element to add a pinpoint', 'pagepin' ),
    875                     'newPinpoint'      => __( 'New Pinpoint', 'pagepin' ),
    876                     'writeComment'     => __( 'Write your comment...', 'pagepin' ),
    877                     'writeReply'       => __( 'Write a reply...', 'pagepin' ),
    878                     'mentionHint'      => __( '@ to mention', 'pagepin' ),
    879                     'create'           => __( 'Create', 'pagepin' ),
    880                     'reply'            => __( 'Reply', 'pagepin' ),
    881                     'close'            => __( 'Close', 'pagepin' ),
    882                     'resolve'          => __( 'Resolve', 'pagepin' ),
    883                     'reopen'           => __( 'Reopen', 'pagepin' ),
    884                     'delete'           => __( 'Delete', 'pagepin' ),
    885                     'resolved'         => __( 'Resolved', 'pagepin' ),
    886                     'open'             => __( 'Open', 'pagepin' ),
    887                     'resolvedBy'       => __( 'Resolved', 'pagepin' ),
    888                     'noComments'       => __( 'No comments yet', 'pagepin' ),
    889                     'pinpointCreated'  => __( 'Pinpoint created successfully', 'pagepin' ),
    890                     'pinpointResolved' => __( 'Pinpoint resolved', 'pagepin' ),
    891                     'pinpointReopened' => __( 'Pinpoint reopened', 'pagepin' ),
    892                     'pinpointDeleted'  => __( 'Pinpoint deleted', 'pagepin' ),
    893                     'confirmDelete'    => __( 'Are you sure you want to delete this pinpoint?', 'pagepin' ),
    894                     'error'            => __( 'An error occurred', 'pagepin' ),
    895                     'loadError'        => __( 'Error loading pinpoints', 'pagepin' ),
    896                     'tryAgain'         => __( 'Please try refreshing the page.', 'pagepin' ),
    897                     'justNow'          => __( 'just now', 'pagepin' ),
    898                     'comment'          => __( 'comment', 'pagepin' ),
    899                     'comments'         => __( 'comments', 'pagepin' ),
    900                     'guest'               => __( 'Guest', 'pagepin' ),
    901                     'disconnect'          => __( 'Disconnect', 'pagepin' ),
    902                     'inviteEmail'         => __( 'Invite', 'pagepin' ),
    903                     'sharePinpoint'       => __( 'Share Pinpoint', 'pagepin' ),
    904                     'internalLink'        => __( 'Internal Link', 'pagepin' ),
    905                     'internalLinkHint'    => __( 'For logged-in team members — opens the pinpoint sidebar directly.', 'pagepin' ),
    906                     'shareExternally'     => __( 'Share Externally', 'pagepin' ),
    907                     'existingGuest'       => __( 'Existing guest:', 'pagepin' ),
    908                     'selectGuest'         => __( 'Select guest...', 'pagepin' ),
    909                     'grantAccess'         => __( 'Grant Access', 'pagepin' ),
    910                     'orCreateNewGuest'    => __( '— or create new guest —', 'pagepin' ),
    911                     'guestName'           => __( 'Guest name', 'pagepin' ),
    912                     'emailOptional'       => __( 'Email', 'pagepin' ),
    913                     'optional'            => __( 'optional', 'pagepin' ),
    914                     'inviteAndCreate'     => __( 'Invite & Create Link', 'pagepin' ),
    915                     'copy'                => __( 'Copy', 'pagepin' ),
    916                     'copied'              => __( 'Copied!', 'pagepin' ),
    917                     'accessGranted'       => __( 'Access granted', 'pagepin' ),
    918                     'accessRevoked'       => __( 'Access revoked', 'pagepin' ),
    919                     'nameRequired'        => __( 'Name is required', 'pagepin' ),
    920                     'failedToCreate'      => __( 'Failed to create', 'pagepin' ),
    921                     'failedToGrant'       => __( 'Failed to grant access', 'pagepin' ),
    922                     'failedToRevoke'      => __( 'Failed to revoke', 'pagepin' ),
    923                     'invitationSent'      => __( 'Invitation sent to', 'pagepin' ),
    924                     'shareLinkCreated'    => __( 'Share link created for', 'pagepin' ),
    925                     'activeExternalAccess' => __( 'Active External Access', 'pagepin' ),
    926                     'noEmail'             => __( 'no email', 'pagepin' ),
    927                     'revoke'              => __( 'Revoke', 'pagepin' ),
    928                     'copyLink'            => __( 'Copy Link', 'pagepin' ),
    929                     'name'                => __( 'Name:', 'pagepin' ),
     866            'pinpoints'            => __( 'Pinpoints', 'pagepin' ),
     867            'addPinpoint'          => __( 'Add Pinpoint', 'pagepin' ),
     868            'toggle'               => __( 'Toggle sidebar', 'pagepin' ),
     869            'filterOpen'           => __( 'Open', 'pagepin' ),
     870            'filterResolved'       => __( 'Resolved', 'pagepin' ),
     871            'filterAll'            => __( 'All', 'pagepin' ),
     872            'noPinpoints'          => __( 'No pinpoints yet', 'pagepin' ),
     873            'noPinpointsDesc'      => __( 'Click "Add Pinpoint" to start a discussion on any element.', 'pagepin' ),
     874            'selectElement'        => __( 'Click on an element to add a pinpoint', 'pagepin' ),
     875            'newPinpoint'          => __( 'New Pinpoint', 'pagepin' ),
     876            'writeComment'         => __( 'Write your comment...', 'pagepin' ),
     877            'writeReply'           => __( 'Write a reply...', 'pagepin' ),
     878            'mentionHint'          => __( '@ to mention', 'pagepin' ),
     879            'create'               => __( 'Create', 'pagepin' ),
     880            'reply'                => __( 'Reply', 'pagepin' ),
     881            'close'                => __( 'Close', 'pagepin' ),
     882            'resolve'              => __( 'Resolve', 'pagepin' ),
     883            'reopen'               => __( 'Reopen', 'pagepin' ),
     884            'delete'               => __( 'Delete', 'pagepin' ),
     885            'resolved'             => __( 'Resolved', 'pagepin' ),
     886            'open'                 => __( 'Open', 'pagepin' ),
     887            'resolvedBy'           => __( 'Resolved', 'pagepin' ),
     888            'noComments'           => __( 'No comments yet', 'pagepin' ),
     889            'pinpointCreated'      => __( 'Pinpoint created successfully', 'pagepin' ),
     890            'pinpointResolved'    => __( 'Pinpoint resolved', 'pagepin' ),
     891            'pinpointReopened'    => __( 'Pinpoint reopened', 'pagepin' ),
     892            'pinpointDeleted'      => __( 'Pinpoint deleted', 'pagepin' ),
     893            'confirmDelete'        => __( 'Are you sure you want to delete this pinpoint?', 'pagepin' ),
     894            'error'                => __( 'An error occurred', 'pagepin' ),
     895            'loadError'            => __( 'Error loading pinpoints', 'pagepin' ),
     896            'tryAgain'             => __( 'Please try refreshing the page.', 'pagepin' ),
     897            'justNow'              => __( 'just now', 'pagepin' ),
     898            'comment'              => __( 'comment', 'pagepin' ),
     899            'comments'             => __( 'comments', 'pagepin' ),
     900            'guest'                => __( 'Guest', 'pagepin' ),
     901            'disconnect'           => __( 'Disconnect', 'pagepin' ),
     902            'inviteEmail'          => __( 'Invite', 'pagepin' ),
     903            'sharePinpoint'        => __( 'Share Pinpoint', 'pagepin' ),
     904            'internalLink'         => __( 'Internal Link', 'pagepin' ),
     905            'internalLinkHint'     => __( 'For logged-in team members — opens the pinpoint sidebar directly.', 'pagepin' ),
     906            'shareExternally'      => __( 'Share Externally', 'pagepin' ),
     907            'existingGuest'        => __( 'Existing guest:', 'pagepin' ),
     908            'selectGuest'          => __( 'Select guest...', 'pagepin' ),
     909            'grantAccess'          => __( 'Grant Access', 'pagepin' ),
     910            'orCreateNewGuest'     => __( '— or create new guest —', 'pagepin' ),
     911            'guestName'            => __( 'Guest name', 'pagepin' ),
     912            'emailOptional'        => __( 'Email', 'pagepin' ),
     913            'optional'             => __( 'optional', 'pagepin' ),
     914            'inviteAndCreate'      => __( 'Invite & Create Link', 'pagepin' ),
     915            'copy'                 => __( 'Copy', 'pagepin' ),
     916            'copied'               => __( 'Copied!', 'pagepin' ),
     917            'accessGranted'        => __( 'Access granted', 'pagepin' ),
     918            'accessRevoked'        => __( 'Access revoked', 'pagepin' ),
     919            'nameRequired'         => __( 'Name is required', 'pagepin' ),
     920            'failedToCreate'       => __( 'Failed to create', 'pagepin' ),
     921            'failedToGrant'        => __( 'Failed to grant access', 'pagepin' ),
     922            'failedToRevoke'       => __( 'Failed to revoke', 'pagepin' ),
     923            'invitationSent'       => __( 'Invitation sent to', 'pagepin' ),
     924            'shareLinkCreated'     => __( 'Share link created for', 'pagepin' ),
     925            'activeExternalAccess' => __( 'Active External Access', 'pagepin' ),
     926            'noEmail'              => __( 'no email', 'pagepin' ),
     927            'revoke'               => __( 'Revoke', 'pagepin' ),
     928            'copyLink'             => __( 'Copy Link', 'pagepin' ),
     929            'name'                 => __( 'Name:', 'pagepin' ),
    930930        );
    931931
     
    11081108        set_transient( $rate_limit_key, $submission_count + 1, HOUR_IN_SECONDS );
    11091109
    1110         $screenshot_data = isset( $_POST['screenshot'] ) ? wp_unslash( $_POST['screenshot'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Base64 screenshot data, validated in save_screenshot().
     1110        $screenshot_data       = isset( $_POST['screenshot'] ) ? wp_unslash( $_POST['screenshot'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Base64 screenshot data, validated in save_screenshot().
    11111111        $feedback_data_json    = isset( $_POST['feedback_data'] ) ? wp_unslash( $_POST['feedback_data'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, validated after decode.
    11121112        $page_url              = isset( $_POST['page_url'] ) ? esc_url_raw( wp_unslash( $_POST['page_url'] ) ) : '';
     
    16561656                'adminUrl' => admin_url( 'admin.php?page=pagepin' ),
    16571657                'strings'  => array(
    1658                     'next'              => __( 'Next', 'pagepin' ),
    1659                     'finish'            => __( 'Finish Setup', 'pagepin' ),
    1660                     'error'             => __( 'An error occurred. Please try again.', 'pagepin' ),
    1661                     'emailRequired'     => __( 'Please enter at least one email address.', 'pagepin' ),
    1662                     'emailInvalid'      => __( 'Please enter a valid email address.', 'pagepin' ),
    1663                     'roleRequired'      => __( 'Please select at least one user role.', 'pagepin' ),
    1664                     'namePlaceholder'   => __( 'Name (optional)', 'pagepin' ),
    1665                     'emailPlaceholder'  => __( 'Email Address', 'pagepin' ),
    1666                     'remove'            => __( 'Remove', 'pagepin' ),
    1667                     'summaryRecipients' => __( 'Email Recipients', 'pagepin' ),
    1668                     'summaryRoles'      => __( 'User Roles', 'pagepin' ),
    1669                     'summaryPublic'     => __( 'Public Feedback', 'pagepin' ),
    1670                     'summaryPinpoint'       => __( 'Pinpoint', 'pagepin' ),
     1658                    'next'                 => __( 'Next', 'pagepin' ),
     1659                    'finish'               => __( 'Finish Setup', 'pagepin' ),
     1660                    'error'                => __( 'An error occurred. Please try again.', 'pagepin' ),
     1661                    'emailRequired'        => __( 'Please enter at least one email address.', 'pagepin' ),
     1662                    'emailInvalid'         => __( 'Please enter a valid email address.', 'pagepin' ),
     1663                    'roleRequired'         => __( 'Please select at least one user role.', 'pagepin' ),
     1664                    'namePlaceholder'      => __( 'Name (optional)', 'pagepin' ),
     1665                    'emailPlaceholder'     => __( 'Email Address', 'pagepin' ),
     1666                    'remove'               => __( 'Remove', 'pagepin' ),
     1667                    'summaryRecipients'    => __( 'Email Recipients', 'pagepin' ),
     1668                    'summaryRoles'         => __( 'User Roles', 'pagepin' ),
     1669                    'summaryPublic'        => __( 'Public Feedback', 'pagepin' ),
     1670                    'summaryPinpoint'      => __( 'Pinpoint', 'pagepin' ),
    16711671                    'summaryCollaborators' => __( 'Collaborators', 'pagepin' ),
    16721672                    'summaryTags'          => __( 'Tags', 'pagepin' ),
     
    17501750
    17511751        if ( isset( $form_data['collaborator_link_expiry'] ) ) {
    1752             $expiry         = absint( $form_data['collaborator_link_expiry'] );
    1753             $allowed_values = array( 0, 7, 30, 90 );
     1752            $expiry                              = absint( $form_data['collaborator_link_expiry'] );
     1753            $allowed_values                      = array( 0, 7, 30, 90 );
    17541754            $options['collaborator_link_expiry'] = in_array( $expiry, $allowed_values, true ) ? $expiry : 0;
    17551755        }
     
    24382438
    24392439            if ( isset( $input['collaborator_link_expiry'] ) ) {
    2440                 $expiry = absint( $input['collaborator_link_expiry'] );
    2441                 $allowed_values = array( 0, 7, 30, 90 );
     2440                $expiry                                = absint( $input['collaborator_link_expiry'] );
     2441                $allowed_values                        = array( 0, 7, 30, 90 );
    24422442                $sanitized['collaborator_link_expiry'] = in_array( $expiry, $allowed_values, true ) ? $expiry : 0;
    24432443            }
     
    26512651        }
    26522652
    2653         $screenshot_data = isset( $_POST['screenshot'] ) ? wp_unslash( $_POST['screenshot'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Base64 screenshot data, validated in save_screenshot().
     2653        $screenshot_data    = isset( $_POST['screenshot'] ) ? wp_unslash( $_POST['screenshot'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Base64 screenshot data, validated in save_screenshot().
    26542654        $feedback_data_json = isset( $_POST['feedback_data'] ) ? wp_unslash( $_POST['feedback_data'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, validated after decode.
    26552655        $page_url           = isset( $_POST['page_url'] ) ? esc_url_raw( wp_unslash( $_POST['page_url'] ) ) : '';
     
    28102810
    28112811        if ( ! empty( $mentions ) ) {
    2812             $email_handler = new PagePin_Pinpoint_Email();
    28132812            $pinpoint = $database->get( $pinpoint_id );
    28142813            if ( $pinpoint && ! empty( $pinpoint->comments ) ) {
    28152814                $comment_id = $pinpoint->comments[0]->id;
    2816                 $user_ids = array();
    2817                 foreach ( $mentions as $username ) {
    2818                     $user = get_user_by( 'login', $username );
    2819                     if ( $user ) {
    2820                         $user_ids[] = $user->ID;
    2821                     }
    2822                 }
    2823                 if ( ! empty( $user_ids ) ) {
    2824                     $email_handler->send_mention_notification( $pinpoint_id, $comment_id, $user_ids );
    2825                 }
     2815                $this->process_mentions( $mentions, $pinpoint_id, $comment_id );
    28262816            }
    28272817        }
     
    29102900        // Include share links if enabled.
    29112901        if ( ! empty( $options['enable_share_links'] ) ) {
    2912             $collab_db            = new PagePin_Collaborator_Database();
     2902            $collab_db             = new PagePin_Collaborator_Database();
    29132903            $pinpoint->share_links = $collab_db->get_share_links_for_pinpoint( $pinpoint_id );
    29142904        }
     
    29592949        }
    29602950
     2951        $share_links_created = $this->process_mentions( $mentions, $pinpoint_id, $comment_id );
     2952
    29612953        $email_handler = new PagePin_Pinpoint_Email();
    2962 
    2963         $share_links_created = array();
    2964 
    2965         if ( ! empty( $mentions ) ) {
    2966             $user_ids         = array();
    2967             $collaborator_ids = array();
    2968             $collab_db        = new PagePin_Collaborator_Database();
    2969             foreach ( $mentions as $mention ) {
    2970                 // Check if this is a share-link mention (!@name).
    2971                 if ( str_starts_with( $mention, '!' ) ) {
    2972                     $share_name = substr( $mention, 1 );
    2973                     if ( ! empty( $share_name ) ) {
    2974                         $share_url = $this->handle_share_link_mention( $share_name, $pinpoint_id );
    2975                         if ( $share_url ) {
    2976                             $share_links_created[] = array(
    2977                                 'name' => $share_name,
    2978                                 'url'  => $share_url,
    2979                             );
    2980                         }
    2981                     }
    2982                     continue;
    2983                 }
    2984                 // Check if this is a collaborator mention (collab_X).
    2985                 if ( str_starts_with( $mention, 'collab_' ) ) {
    2986                     $collab_id = absint( substr( $mention, 7 ) );
    2987                     if ( $collab_id > 0 ) {
    2988                         // Auto-grant access if collaborator doesn't have it yet.
    2989                         if ( ! $collab_db->has_access( $collab_id, $pinpoint_id ) ) {
    2990                             $collab_db->grant_access( $collab_id, $pinpoint_id, get_current_user_id() );
    2991                         }
    2992                         $collaborator_ids[] = $collab_id;
    2993                     }
    2994                     continue;
    2995                 }
    2996                 // Check if this is an email invite mention.
    2997                 if ( is_email( $mention ) ) {
    2998                     $this->handle_email_mention( $mention, $pinpoint_id );
    2999                     continue;
    3000                 }
    3001                 $user = get_user_by( 'login', $mention );
    3002                 if ( $user ) {
    3003                     $user_ids[] = $user->ID;
    3004                 }
    3005             }
    3006             if ( ! empty( $user_ids ) ) {
    3007                 $email_handler->send_mention_notification( $pinpoint_id, $comment_id, $user_ids );
    3008             }
    3009             if ( ! empty( $collaborator_ids ) ) {
    3010                 $email_handler->send_collaborator_mention_notification( $pinpoint_id, $comment_id, $collaborator_ids );
    3011             }
    3012         }
    3013 
    30142954        $email_handler->send_thread_notification( $pinpoint_id, $comment_id );
    30152955
     
    30382978        $display_name = ucfirst( $email_parts[0] );
    30392979        $this->handle_email_collaborator_creation( $email, $display_name, $pinpoint_id );
     2980    }
     2981
     2982    /**
     2983     * Process mentions from a comment: send notifications, grant access, create share links.
     2984     *
     2985     * @since 1.0.3
     2986     * @param array $mentions    Array of mention strings extracted from comment text.
     2987     * @param int   $pinpoint_id The pinpoint ID.
     2988     * @param int   $comment_id  The comment ID.
     2989     * @return array Share links created (array of name/url pairs).
     2990     */
     2991    private function process_mentions( $mentions, $pinpoint_id, $comment_id ) {
     2992        if ( empty( $mentions ) || ! is_array( $mentions ) ) {
     2993            return array();
     2994        }
     2995
     2996        $mentions            = array_unique( $mentions );
     2997        $user_ids            = array();
     2998        $collaborator_ids    = array();
     2999        $share_links_created = array();
     3000        $collab_db           = new PagePin_Collaborator_Database();
     3001        $current_user_id     = get_current_user_id();
     3002
     3003        foreach ( $mentions as $mention ) {
     3004            // Share-link mention (!@name).
     3005            if ( str_starts_with( $mention, '!' ) ) {
     3006                $share_name = substr( $mention, 1 );
     3007                if ( ! empty( $share_name ) ) {
     3008                    $share_url = $this->handle_share_link_mention( $share_name, $pinpoint_id );
     3009                    if ( $share_url ) {
     3010                        $share_links_created[] = array(
     3011                            'name' => $share_name,
     3012                            'url'  => $share_url,
     3013                        );
     3014                    }
     3015                }
     3016                continue;
     3017            }
     3018            // Collaborator mention (collab_X).
     3019            if ( str_starts_with( $mention, 'collab_' ) ) {
     3020                $collab_id = absint( substr( $mention, 7 ) );
     3021                if ( $collab_id > 0 ) {
     3022                    if ( ! $collab_db->has_access( $collab_id, $pinpoint_id ) ) {
     3023                        $collab_db->grant_access( $collab_id, $pinpoint_id, $current_user_id );
     3024                    }
     3025                    $collaborator_ids[] = $collab_id;
     3026                }
     3027                continue;
     3028            }
     3029            // Email invite mention.
     3030            if ( is_email( $mention ) ) {
     3031                $this->handle_email_mention( $mention, $pinpoint_id );
     3032                continue;
     3033            }
     3034            // WordPress user mention.
     3035            $user = get_user_by( 'login', $mention );
     3036            if ( $user && absint( $user->ID ) !== $current_user_id ) {
     3037                $user_ids[] = $user->ID;
     3038            }
     3039        }
     3040
     3041        $email_handler = new PagePin_Pinpoint_Email();
     3042        if ( ! empty( $user_ids ) ) {
     3043            $email_handler->send_mention_notification( $pinpoint_id, $comment_id, $user_ids );
     3044        }
     3045        if ( ! empty( $collaborator_ids ) ) {
     3046            $email_handler->send_collaborator_mention_notification( $pinpoint_id, $comment_id, $collaborator_ids );
     3047        }
     3048
     3049        return $share_links_created;
    30403050    }
    30413051
     
    34653475            $collab_db     = new PagePin_Collaborator_Database();
    34663476            $collaborators = $collab_db->search_collaborators( $search );
     3477            $guest_avatar  = get_avatar_url( 0, array( 'size' => 32 ) );
    34673478
    34683479            foreach ( $collaborators as $collab ) {
    3469                 $avatar_email = ! empty( $collab->email ) ? $collab->email : '';
    34703480                $display_info = ! empty( $collab->email ) ? $collab->email : __( 'Guest', 'pagepin' );
    34713481
     
    34753485                    'login'           => 'collab_' . $collab->id,
    34763486                    'display_login'   => $display_info,
    3477                     'avatar'          => get_avatar_url( $avatar_email, array( 'size' => 32 ) ),
     3487                    'avatar'          => $guest_avatar,
    34783488                    'is_collaborator' => true,
    34793489                );
     
    34883498                if ( ! $existing_user && ! $existing_collab ) {
    34893499                    $users[] = array(
    3490                         'id'          => 'invite_' . $search,
    3491                         'name'        => __( 'Invite', 'pagepin' ) . ': ' . $search,
    3492                         'login'       => $search,
    3493                         'avatar'      => get_avatar_url( $search, array( 'size' => 32 ) ),
    3494                         'is_invite'   => true,
     3500                        'id'        => 'invite_' . $search,
     3501                        'name'      => __( 'Invite', 'pagepin' ) . ': ' . $search,
     3502                        'login'     => $search,
     3503                        'avatar'    => get_avatar_url( $search, array( 'size' => 32 ) ),
     3504                        'is_invite' => true,
    34953505                    );
    34963506                }
     
    40894099            if ( ! isset( $options['enable_tags'] ) ) {
    40904100                $options['enable_tags'] = true;
    4091                 $changed               = true;
     4101                $changed                = true;
    40924102            }
    40934103            if ( ! isset( $options['admin_bar_enabled'] ) ) {
  • pagepin/tags/1.0.3/readme.txt

    r3494254 r3494810  
    66Tested up to: 6.9
    77Requires PHP: 8.1
    8 Stable tag: 1.0.2
     8Stable tag: 1.0.3
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    214214
    215215== Changelog ==
     216
     217= 1.0.3 =
     218* Improvement: Setup wizard fully localized in English with translation support
     219* Fix: Contrast issue with Pinpoint role headings in setup wizard
     220* Fix: Duplicate @mention notifications prevented
     221* Fix: Self-mention no longer triggers email notification
     222* Fix: Collaborator and email mentions now work in initial pinpoint comments
     223* Fix: Guest collaborators show generic avatar in mention autocomplete
     224* Fix: Pinpoint reply textarea sizing and overflow
     225* Improvement: Inline styles moved to CSS classes for better maintainability
     226* Improvement: Refined wizard step descriptions for clarity
    216227
    217228= 1.0.2 =
     
    248259== Upgrade Notice ==
    249260
     261= 1.0.3 =
     262Setup wizard fully English and translatable. Fixed duplicate mention notifications, self-mentions, and collaborator mentions in initial pinpoints.
     263
    250264= 1.0.2 =
    251265New collaboration features: Tag system, external collaborators via Magic Link, shareable feedback links, collaborator reuse, @mention notifications, and admin bar integration.
  • pagepin/trunk/admin/class-wizard.php

    r3491645 r3494810  
    3838    public function __construct() {
    3939        $this->steps = array(
    40             'welcome'    => array(
     40            'welcome'       => array(
    4141                'name'    => __( 'Welcome', 'pagepin' ),
    4242                'handler' => 'render_step_welcome',
    4343            ),
    44             'recipients' => array(
     44            'recipients'    => array(
    4545                'name'    => __( 'Recipients', 'pagepin' ),
    4646                'handler' => 'render_step_recipients',
    4747            ),
    48             'roles'      => array(
     48            'roles'         => array(
    4949                'name'    => __( 'Roles', 'pagepin' ),
    5050                'handler' => 'render_step_roles',
    5151            ),
    52             'public'     => array(
     52            'public'        => array(
    5353                'name'    => __( 'Public', 'pagepin' ),
    5454                'handler' => 'render_step_public',
     
    341341                    </label>
    342342                    <p class="pagepin-wizard-toggle-description">
    343                         <?php esc_html_e( 'When enabled, any visitor can submit feedback without logging in.', 'pagepin' ); ?>
     343                        <?php esc_html_e( 'When enabled, anyone can submit feedback without a WordPress account.', 'pagepin' ); ?>
    344344                    </p>
    345345                </div>
     
    384384            <h2><?php esc_html_e( 'Pinpoint Feature', 'pagepin' ); ?></h2>
    385385            <p class="pagepin-wizard-description">
    386                 <?php esc_html_e( 'Pinpoint allows your team to discuss specific elements on any page with threaded comments.', 'pagepin' ); ?>
     386                <?php esc_html_e( 'Pinpoint lets your team pin comments to specific page elements for focused, threaded discussions.', 'pagepin' ); ?>
    387387            </p>
    388388
     
    395395                    </label>
    396396                    <p class="pagepin-wizard-toggle-description">
    397                         <?php esc_html_e( 'When enabled, a sidebar will appear on frontend pages for element-based discussions.', 'pagepin' ); ?>
     397                        <?php esc_html_e( 'When enabled, users can pin threaded comments directly to page elements.', 'pagepin' ); ?>
    398398                    </p>
    399399                </div>
    400400
    401401                <div class="pagepin-wizard-conditional" id="pagepin-pinpoint-options" style="<?php echo $pinpoint_enabled ? '' : esc_attr( 'display: none;' ); ?>">
    402                     <h3 style="margin-top: 20px; margin-bottom: 10px; font-size: 14px; font-weight: 600;">
     402                    <h3>
    403403                        <?php esc_html_e( 'Who can create pinpoints?', 'pagepin' ); ?>
    404404                    </h3>
    405                     <div class="pagepin-wizard-roles-grid" style="margin-bottom: 15px;">
     405                    <div class="pagepin-wizard-roles-grid">
    406406                        <?php foreach ( $all_roles as $role_key => $role_name ) : ?>
    407407                            <?php $is_checked = in_array( $role_key, (array) $can_create, true ); ?>
     
    416416                    </div>
    417417
    418                     <h3 style="margin-top: 15px; margin-bottom: 10px; font-size: 14px; font-weight: 600;">
     418                    <h3>
    419419                        <?php esc_html_e( 'Who can view and comment?', 'pagepin' ); ?>
    420420                    </h3>
    421                     <div class="pagepin-wizard-roles-grid" style="margin-bottom: 15px;">
     421                    <div class="pagepin-wizard-roles-grid">
    422422                        <?php foreach ( $all_roles as $role_key => $role_name ) : ?>
    423423                            <?php $is_checked = in_array( $role_key, (array) $can_view, true ); ?>
     
    432432                    </div>
    433433
    434                     <h3 style="margin-top: 15px; margin-bottom: 10px; font-size: 14px; font-weight: 600;">
     434                    <h3>
    435435                        <?php esc_html_e( 'Who can resolve and delete?', 'pagepin' ); ?>
    436436                    </h3>
     
    452452            <p class="pagepin-wizard-hint">
    453453                <span class="dashicons dashicons-info-outline"></span>
    454                 <?php esc_html_e( 'Pinpoint is ideal for internal team discussions about design and content.', 'pagepin' ); ?>
     454                <?php esc_html_e( 'Pinpoint is ideal for team discussions about design, content, and layout decisions.', 'pagepin' ); ?>
    455455            </p>
    456456        </div>
     
    478478        ?>
    479479        <div class="pagepin-wizard-step-inner">
    480             <h2><?php esc_html_e( 'Externe Collaborators', 'pagepin' ); ?></h2>
     480            <h2><?php esc_html_e( 'External Collaborators', 'pagepin' ); ?></h2>
    481481            <p class="pagepin-wizard-description">
    482                 <?php esc_html_e( 'Laden Sie externe Personen per E-Mail-Link zur Zusammenarbeit ein — ohne WordPress-Account.', 'pagepin' ); ?>
     482                <?php esc_html_e( 'Invite external people via email link to collaborate — no WordPress account required.', 'pagepin' ); ?>
    483483            </p>
    484484
     
    488488                        <input type="checkbox" name="enable_collaborators" value="1" <?php checked( $enable_collaborators ); ?> id="pagepin-enable-collaborators" />
    489489                        <span class="pagepin-wizard-toggle-slider"></span>
    490                         <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Externe Collaborators aktivieren', 'pagepin' ); ?></span>
     490                        <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Enable external collaborators', 'pagepin' ); ?></span>
    491491                    </label>
    492492                    <p class="pagepin-wizard-toggle-description">
    493                         <?php esc_html_e( 'Wenn aktiviert, können externe Personen ohne WordPress-Account Feedback geben.', 'pagepin' ); ?>
     493                        <?php esc_html_e( 'When enabled, external people can provide feedback without a WordPress account.', 'pagepin' ); ?>
    494494                    </p>
    495495                </div>
     
    499499                        <input type="checkbox" name="enable_share_links" value="1" <?php checked( $enable_share_links ); ?> />
    500500                        <span class="pagepin-wizard-toggle-slider"></span>
    501                         <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Token-basierte Share-Links aktivieren', 'pagepin' ); ?></span>
     501                        <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Enable token-based share links', 'pagepin' ); ?></span>
    502502                    </label>
    503503                    <p class="pagepin-wizard-toggle-description">
    504                         <?php esc_html_e( 'Erzeugt eindeutige Links, die Sie an externe Personen weiterleiten können.', 'pagepin' ); ?>
     504                        <?php esc_html_e( 'Generates unique links that you can share with external people.', 'pagepin' ); ?>
    505505                    </p>
    506506
    507                     <div style="margin-top: 15px;">
    508                         <label for="pagepin-collaborator-link-expiry" style="display: block; margin-bottom: 5px; font-weight: 600; font-size: 13px;">
    509                             <?php esc_html_e( 'Link-Ablauf', 'pagepin' ); ?>
     507                    <div class="pagepin-wizard-conditional-field">
     508                        <label for="pagepin-collaborator-link-expiry" class="pagepin-wizard-conditional-label">
     509                            <?php esc_html_e( 'Link Expiration', 'pagepin' ); ?>
    510510                        </label>
    511                         <select name="collaborator_link_expiry" id="pagepin-collaborator-link-expiry" class="pagepin-wizard-input" style="width: 100%; max-width: 300px;">
    512                             <option value="0" <?php selected( $link_expiry, 0 ); ?>><?php esc_html_e( 'Nie', 'pagepin' ); ?></option>
    513                             <option value="7" <?php selected( $link_expiry, 7 ); ?>><?php esc_html_e( '7 Tage', 'pagepin' ); ?></option>
    514                             <option value="30" <?php selected( $link_expiry, 30 ); ?>><?php esc_html_e( '30 Tage', 'pagepin' ); ?></option>
    515                             <option value="90" <?php selected( $link_expiry, 90 ); ?>><?php esc_html_e( '90 Tage', 'pagepin' ); ?></option>
     511                        <select name="collaborator_link_expiry" id="pagepin-collaborator-link-expiry" class="pagepin-wizard-input pagepin-wizard-conditional-select">
     512                            <option value="0" <?php selected( $link_expiry, 0 ); ?>><?php esc_html_e( 'Never', 'pagepin' ); ?></option>
     513                            <option value="7" <?php selected( $link_expiry, 7 ); ?>><?php esc_html_e( '7 Days', 'pagepin' ); ?></option>
     514                            <option value="30" <?php selected( $link_expiry, 30 ); ?>><?php esc_html_e( '30 Days', 'pagepin' ); ?></option>
     515                            <option value="90" <?php selected( $link_expiry, 90 ); ?>><?php esc_html_e( '90 Days', 'pagepin' ); ?></option>
    516516                        </select>
    517517                    </div>
     
    521521            <p class="pagepin-wizard-hint">
    522522                <span class="dashicons dashicons-info-outline"></span>
    523                 <?php esc_html_e( 'Sie können Collaborators per @email@adresse.de in Pinpoint-Threads einladen.', 'pagepin' ); ?>
     523                <?php esc_html_e( 'You can invite collaborators via @email in Pinpoint threads.', 'pagepin' ); ?>
    524524            </p>
    525525        </div>
     
    540540        ?>
    541541        <div class="pagepin-wizard-step-inner">
    542             <h2><?php esc_html_e( 'Feedback-Tags', 'pagepin' ); ?></h2>
     542            <h2><?php esc_html_e( 'Feedback Tags', 'pagepin' ); ?></h2>
    543543            <p class="pagepin-wizard-description">
    544                 <?php esc_html_e( 'Organisieren Sie Feedback und Pinpoints mit farbigen Labels.', 'pagepin' ); ?>
     544                <?php esc_html_e( 'Organize feedback and pinpoints with color-coded labels.', 'pagepin' ); ?>
    545545            </p>
    546546
     
    550550                        <input type="checkbox" name="enable_tags" value="1" <?php checked( $enable_tags ); ?> id="pagepin-enable-tags" />
    551551                        <span class="pagepin-wizard-toggle-slider"></span>
    552                         <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Tags aktivieren', 'pagepin' ); ?></span>
     552                        <span class="pagepin-wizard-toggle-label"><?php esc_html_e( 'Enable tags', 'pagepin' ); ?></span>
    553553                    </label>
    554554                    <p class="pagepin-wizard-toggle-description">
    555                         <?php esc_html_e( 'Wenn aktiviert, können Sie Feedback und Pinpoints mit Tags organisieren.', 'pagepin' ); ?>
     555                        <?php esc_html_e( 'When enabled, you can organize feedback and pinpoints with tags.', 'pagepin' ); ?>
    556556                    </p>
    557557                </div>
    558558
    559559                <div class="pagepin-wizard-conditional" id="pagepin-tags-preview" style="<?php echo $enable_tags ? '' : esc_attr( 'display: none;' ); ?>">
    560                     <div style="display: flex; flex-wrap: wrap; gap: 8px; margin-top: 15px;">
    561                         <span class="pagepin-wizard-tag-pill" style="display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 13px; font-weight: 600; background: #ef4444; color: #ffffff;">
    562                             Bug
     560                    <div class="pagepin-wizard-tag-pills">
     561                        <span class="pagepin-wizard-tag-pill" style="background: #ef4444;">
     562                            <?php esc_html_e( 'Bug', 'pagepin' ); ?>
    563563                        </span>
    564                         <span class="pagepin-wizard-tag-pill" style="display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 13px; font-weight: 600; background: #ff6800; color: #ffffff;">
    565                             Design
     564                        <span class="pagepin-wizard-tag-pill" style="background: #ff6800;">
     565                            <?php esc_html_e( 'Design', 'pagepin' ); ?>
    566566                        </span>
    567                         <span class="pagepin-wizard-tag-pill" style="display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 13px; font-weight: 600; background: #3b82f6; color: #ffffff;">
    568                             Content
     567                        <span class="pagepin-wizard-tag-pill" style="background: #3b82f6;">
     568                            <?php esc_html_e( 'Content', 'pagepin' ); ?>
    569569                        </span>
    570                         <span class="pagepin-wizard-tag-pill" style="display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 13px; font-weight: 600; background: #f59e0b; color: #ffffff;">
    571                             Dringend
     570                        <span class="pagepin-wizard-tag-pill" style="background: #f59e0b;">
     571                            <?php esc_html_e( 'Urgent', 'pagepin' ); ?>
    572572                        </span>
    573573                    </div>
    574                     <p class="pagepin-wizard-toggle-description" style="margin-top: 10px;">
    575                         <?php esc_html_e( 'Diese Standard-Tags werden automatisch erstellt. Weitere Tags können Sie in den Einstellungen hinzufügen.', 'pagepin' ); ?>
     574                    <p class="pagepin-wizard-toggle-description">
     575                        <?php esc_html_e( 'These default tags are created automatically. You can add more tags in the settings.', 'pagepin' ); ?>
    576576                    </p>
    577577                </div>
     
    580580            <p class="pagepin-wizard-hint">
    581581                <span class="dashicons dashicons-info-outline"></span>
    582                 <?php esc_html_e( 'Tags können sowohl Pinshots als auch Pinpoints zugewiesen werden.', 'pagepin' ); ?>
     582                <?php esc_html_e( 'Tags can be assigned to both Pinshots and Pinpoints.', 'pagepin' ); ?>
    583583            </p>
    584584        </div>
  • pagepin/trunk/assets/css/admin-wizard.css

    r3491645 r3494810  
    589589}
    590590
     591.pagepin-wizard-conditional h3 {
     592    color: var(--wizard-slate-50);
     593    margin-top: 20px;
     594    margin-bottom: 10px;
     595    font-size: 14px;
     596    font-weight: 600;
     597}
     598
     599.pagepin-wizard-conditional h3:first-child {
     600    margin-top: 0;
     601}
     602
     603.pagepin-wizard-conditional .pagepin-wizard-roles-grid {
     604    margin-bottom: 15px;
     605}
     606
     607.pagepin-wizard-conditional-field {
     608    margin-top: 15px;
     609}
     610
     611.pagepin-wizard-conditional-label {
     612    display: block;
     613    margin-bottom: 5px;
     614    font-weight: 600;
     615    font-size: 13px;
     616    color: var(--wizard-slate-200);
     617}
     618
     619.pagepin-wizard-conditional-select {
     620    width: 100%;
     621    max-width: 300px;
     622}
     623
     624.pagepin-wizard-tag-pills {
     625    display: flex;
     626    flex-wrap: wrap;
     627    gap: 8px;
     628    margin-top: 15px;
     629}
     630
     631.pagepin-wizard-tag-pill {
     632    display: inline-block;
     633    padding: 4px 12px;
     634    border-radius: 12px;
     635    font-size: 13px;
     636    font-weight: 600;
     637    color: #fff;
     638}
     639
    591640/* ===== COMPLETE STEP ===== */
    592641.pagepin-wizard-complete {
  • pagepin/trunk/assets/css/pinpoint.css

    r3491645 r3494810  
    669669#pagepin-pinpoint-root .pp-thread-textarea {
    670670    width: 100%;
    671     min-height: 60px;
     671    min-height: 40px;
    672672    max-height: 120px;
    673     padding: 10px 12px;
     673    padding: 8px 12px;
     674    box-sizing: border-box;
    674675    background: var(--pp-color-bg-dark);
    675676    border: 1px solid var(--pp-color-border);
  • pagepin/trunk/assets/js/admin-wizard.js

    r3491645 r3494810  
    632632                        $btn.removeClass('loading').prop('disabled', false);
    633633                        // eslint-disable-next-line no-alert
    634                         // eslint-disable-next-line no-alert
    635634                        alert(
    636635                            response.data.message || pagepinWizard.strings.error
  • pagepin/trunk/assets/js/pinpoint.js

    r3491645 r3494810  
    18141814                    </div>
    18151815                    <div class="pp-thread-input">
    1816                         <textarea class="pp-thread-textarea" placeholder="${s.writeComment || 'Write your comment...'}" autofocus></textarea>
     1816                        <textarea class="pp-thread-textarea" rows="2" placeholder="${s.writeComment || 'Write your comment...'}" autofocus></textarea>
    18171817                        <div class="pp-thread-input-actions">
    18181818                            <span class="pp-thread-hint">${s.mentionHint || '@ to mention'}</span>
     
    21792179                            ? `
    21802180                        <div class="pp-thread-input">
    2181                             <textarea class="pp-thread-textarea" placeholder="${s.writeReply || 'Write a reply...'}"></textarea>
     2181                            <textarea class="pp-thread-textarea" rows="2" placeholder="${s.writeReply || 'Write a reply...'}"></textarea>
    21822182                            <div class="pp-thread-input-actions">
    21832183                                <span class="pp-thread-hint">${s.mentionHint || '@ to mention'}</span>
  • pagepin/trunk/pagepin.php

    r3491645 r3494810  
    44 * Plugin URI: https://pagepin.io
    55 * Description: Visual feedback tool for WordPress - collect client feedback with screenshots and annotations.
    6  * Version: 1.0.2
     6 * Version: 1.0.3
    77 * Author: Patrick Schlesinger
    88 * Author URI: https://pagepin.io
     
    2525}
    2626
    27 define( 'PAGEPIN_VERSION', '1.0.2' );
     27define( 'PAGEPIN_VERSION', '1.0.3' );
    2828define( 'PAGEPIN_PLUGIN_FILE', __FILE__ );
    2929define( 'PAGEPIN_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
     
    835835
    836836        $localize_data = array(
    837             'ajaxurl'      => admin_url( 'admin-ajax.php' ),
    838             'nonce'        => wp_create_nonce( 'pagepin_pinpoint_nonce' ),
    839             'adminNonce'   => wp_create_nonce( 'pagepin_nonce' ),
    840             'enabled'      => true,
    841             'enableTags'   => ! empty( $options['enable_tags'] ),
    842             'canCreate'    => $can_create,
    843             'canView'      => $can_view,
    844             'canViewOwn'   => ! $is_collaborator && $can_create && ! $this->current_user_can_pinpoint( 'view' ),
    845             'canResolve'   => $is_collaborator ? false : $this->current_user_can_pinpoint( 'resolve' ),
    846             'currentUser'  => get_current_user_id(),
     837            'ajaxurl'     => admin_url( 'admin-ajax.php' ),
     838            'nonce'       => wp_create_nonce( 'pagepin_pinpoint_nonce' ),
     839            'adminNonce'  => wp_create_nonce( 'pagepin_nonce' ),
     840            'enabled'     => true,
     841            'enableTags'  => ! empty( $options['enable_tags'] ),
     842            'canCreate'   => $can_create,
     843            'canView'     => $can_view,
     844            'canViewOwn'  => ! $is_collaborator && $can_create && ! $this->current_user_can_pinpoint( 'view' ),
     845            'canResolve'  => $is_collaborator ? false : $this->current_user_can_pinpoint( 'resolve' ),
     846            'currentUser' => get_current_user_id(),
    847847        );
    848848
    849849        // Add collaborator context if active.
    850850        if ( $is_collaborator && $collaborator ) {
    851             $collab_db     = new PagePin_Collaborator_Database();
    852             $accessible    = $collab_db->get_accessible_pinpoints( $collaborator->id );
    853             $collab_nonce  = wp_create_nonce( 'pagepin_collaborator_nonce' );
     851            $collab_db    = new PagePin_Collaborator_Database();
     852            $accessible   = $collab_db->get_accessible_pinpoints( $collaborator->id );
     853            $collab_nonce = wp_create_nonce( 'pagepin_collaborator_nonce' );
    854854
    855855            $localize_data['isCollaborator']      = true;
    856             $localize_data['collaboratorName']     = $collaborator->display_name;
    857             $localize_data['collaboratorNonce']     = $collab_nonce;
    858             $localize_data['accessiblePinpoints']  = $accessible;
     856            $localize_data['collaboratorName']    = $collaborator->display_name;
     857            $localize_data['collaboratorNonce']   = $collab_nonce;
     858            $localize_data['accessiblePinpoints'] = $accessible;
    859859        } else {
    860860            $localize_data['isCollaborator']      = false;
    861             $localize_data['enableCollaborators']  = ! empty( $options['enable_collaborators'] );
    862             $localize_data['enableShareLinks']     = ! empty( $options['enable_share_links'] );
     861            $localize_data['enableCollaborators'] = ! empty( $options['enable_collaborators'] );
     862            $localize_data['enableShareLinks']    = ! empty( $options['enable_share_links'] );
    863863        }
    864864
    865865        $localize_data['strings'] = array(
    866                     'pinpoints'        => __( 'Pinpoints', 'pagepin' ),
    867                     'addPinpoint'      => __( 'Add Pinpoint', 'pagepin' ),
    868                     'toggle'           => __( 'Toggle sidebar', 'pagepin' ),
    869                     'filterOpen'       => __( 'Open', 'pagepin' ),
    870                     'filterResolved'   => __( 'Resolved', 'pagepin' ),
    871                     'filterAll'        => __( 'All', 'pagepin' ),
    872                     'noPinpoints'      => __( 'No pinpoints yet', 'pagepin' ),
    873                     'noPinpointsDesc'  => __( 'Click "Add Pinpoint" to start a discussion on any element.', 'pagepin' ),
    874                     'selectElement'    => __( 'Click on an element to add a pinpoint', 'pagepin' ),
    875                     'newPinpoint'      => __( 'New Pinpoint', 'pagepin' ),
    876                     'writeComment'     => __( 'Write your comment...', 'pagepin' ),
    877                     'writeReply'       => __( 'Write a reply...', 'pagepin' ),
    878                     'mentionHint'      => __( '@ to mention', 'pagepin' ),
    879                     'create'           => __( 'Create', 'pagepin' ),
    880                     'reply'            => __( 'Reply', 'pagepin' ),
    881                     'close'            => __( 'Close', 'pagepin' ),
    882                     'resolve'          => __( 'Resolve', 'pagepin' ),
    883                     'reopen'           => __( 'Reopen', 'pagepin' ),
    884                     'delete'           => __( 'Delete', 'pagepin' ),
    885                     'resolved'         => __( 'Resolved', 'pagepin' ),
    886                     'open'             => __( 'Open', 'pagepin' ),
    887                     'resolvedBy'       => __( 'Resolved', 'pagepin' ),
    888                     'noComments'       => __( 'No comments yet', 'pagepin' ),
    889                     'pinpointCreated'  => __( 'Pinpoint created successfully', 'pagepin' ),
    890                     'pinpointResolved' => __( 'Pinpoint resolved', 'pagepin' ),
    891                     'pinpointReopened' => __( 'Pinpoint reopened', 'pagepin' ),
    892                     'pinpointDeleted'  => __( 'Pinpoint deleted', 'pagepin' ),
    893                     'confirmDelete'    => __( 'Are you sure you want to delete this pinpoint?', 'pagepin' ),
    894                     'error'            => __( 'An error occurred', 'pagepin' ),
    895                     'loadError'        => __( 'Error loading pinpoints', 'pagepin' ),
    896                     'tryAgain'         => __( 'Please try refreshing the page.', 'pagepin' ),
    897                     'justNow'          => __( 'just now', 'pagepin' ),
    898                     'comment'          => __( 'comment', 'pagepin' ),
    899                     'comments'         => __( 'comments', 'pagepin' ),
    900                     'guest'               => __( 'Guest', 'pagepin' ),
    901                     'disconnect'          => __( 'Disconnect', 'pagepin' ),
    902                     'inviteEmail'         => __( 'Invite', 'pagepin' ),
    903                     'sharePinpoint'       => __( 'Share Pinpoint', 'pagepin' ),
    904                     'internalLink'        => __( 'Internal Link', 'pagepin' ),
    905                     'internalLinkHint'    => __( 'For logged-in team members — opens the pinpoint sidebar directly.', 'pagepin' ),
    906                     'shareExternally'     => __( 'Share Externally', 'pagepin' ),
    907                     'existingGuest'       => __( 'Existing guest:', 'pagepin' ),
    908                     'selectGuest'         => __( 'Select guest...', 'pagepin' ),
    909                     'grantAccess'         => __( 'Grant Access', 'pagepin' ),
    910                     'orCreateNewGuest'    => __( '— or create new guest —', 'pagepin' ),
    911                     'guestName'           => __( 'Guest name', 'pagepin' ),
    912                     'emailOptional'       => __( 'Email', 'pagepin' ),
    913                     'optional'            => __( 'optional', 'pagepin' ),
    914                     'inviteAndCreate'     => __( 'Invite & Create Link', 'pagepin' ),
    915                     'copy'                => __( 'Copy', 'pagepin' ),
    916                     'copied'              => __( 'Copied!', 'pagepin' ),
    917                     'accessGranted'       => __( 'Access granted', 'pagepin' ),
    918                     'accessRevoked'       => __( 'Access revoked', 'pagepin' ),
    919                     'nameRequired'        => __( 'Name is required', 'pagepin' ),
    920                     'failedToCreate'      => __( 'Failed to create', 'pagepin' ),
    921                     'failedToGrant'       => __( 'Failed to grant access', 'pagepin' ),
    922                     'failedToRevoke'      => __( 'Failed to revoke', 'pagepin' ),
    923                     'invitationSent'      => __( 'Invitation sent to', 'pagepin' ),
    924                     'shareLinkCreated'    => __( 'Share link created for', 'pagepin' ),
    925                     'activeExternalAccess' => __( 'Active External Access', 'pagepin' ),
    926                     'noEmail'             => __( 'no email', 'pagepin' ),
    927                     'revoke'              => __( 'Revoke', 'pagepin' ),
    928                     'copyLink'            => __( 'Copy Link', 'pagepin' ),
    929                     'name'                => __( 'Name:', 'pagepin' ),
     866            'pinpoints'            => __( 'Pinpoints', 'pagepin' ),
     867            'addPinpoint'          => __( 'Add Pinpoint', 'pagepin' ),
     868            'toggle'               => __( 'Toggle sidebar', 'pagepin' ),
     869            'filterOpen'           => __( 'Open', 'pagepin' ),
     870            'filterResolved'       => __( 'Resolved', 'pagepin' ),
     871            'filterAll'            => __( 'All', 'pagepin' ),
     872            'noPinpoints'          => __( 'No pinpoints yet', 'pagepin' ),
     873            'noPinpointsDesc'      => __( 'Click "Add Pinpoint" to start a discussion on any element.', 'pagepin' ),
     874            'selectElement'        => __( 'Click on an element to add a pinpoint', 'pagepin' ),
     875            'newPinpoint'          => __( 'New Pinpoint', 'pagepin' ),
     876            'writeComment'         => __( 'Write your comment...', 'pagepin' ),
     877            'writeReply'           => __( 'Write a reply...', 'pagepin' ),
     878            'mentionHint'          => __( '@ to mention', 'pagepin' ),
     879            'create'               => __( 'Create', 'pagepin' ),
     880            'reply'                => __( 'Reply', 'pagepin' ),
     881            'close'                => __( 'Close', 'pagepin' ),
     882            'resolve'              => __( 'Resolve', 'pagepin' ),
     883            'reopen'               => __( 'Reopen', 'pagepin' ),
     884            'delete'               => __( 'Delete', 'pagepin' ),
     885            'resolved'             => __( 'Resolved', 'pagepin' ),
     886            'open'                 => __( 'Open', 'pagepin' ),
     887            'resolvedBy'           => __( 'Resolved', 'pagepin' ),
     888            'noComments'           => __( 'No comments yet', 'pagepin' ),
     889            'pinpointCreated'      => __( 'Pinpoint created successfully', 'pagepin' ),
     890            'pinpointResolved'    => __( 'Pinpoint resolved', 'pagepin' ),
     891            'pinpointReopened'    => __( 'Pinpoint reopened', 'pagepin' ),
     892            'pinpointDeleted'      => __( 'Pinpoint deleted', 'pagepin' ),
     893            'confirmDelete'        => __( 'Are you sure you want to delete this pinpoint?', 'pagepin' ),
     894            'error'                => __( 'An error occurred', 'pagepin' ),
     895            'loadError'            => __( 'Error loading pinpoints', 'pagepin' ),
     896            'tryAgain'             => __( 'Please try refreshing the page.', 'pagepin' ),
     897            'justNow'              => __( 'just now', 'pagepin' ),
     898            'comment'              => __( 'comment', 'pagepin' ),
     899            'comments'             => __( 'comments', 'pagepin' ),
     900            'guest'                => __( 'Guest', 'pagepin' ),
     901            'disconnect'           => __( 'Disconnect', 'pagepin' ),
     902            'inviteEmail'          => __( 'Invite', 'pagepin' ),
     903            'sharePinpoint'        => __( 'Share Pinpoint', 'pagepin' ),
     904            'internalLink'         => __( 'Internal Link', 'pagepin' ),
     905            'internalLinkHint'     => __( 'For logged-in team members — opens the pinpoint sidebar directly.', 'pagepin' ),
     906            'shareExternally'      => __( 'Share Externally', 'pagepin' ),
     907            'existingGuest'        => __( 'Existing guest:', 'pagepin' ),
     908            'selectGuest'          => __( 'Select guest...', 'pagepin' ),
     909            'grantAccess'          => __( 'Grant Access', 'pagepin' ),
     910            'orCreateNewGuest'     => __( '— or create new guest —', 'pagepin' ),
     911            'guestName'            => __( 'Guest name', 'pagepin' ),
     912            'emailOptional'        => __( 'Email', 'pagepin' ),
     913            'optional'             => __( 'optional', 'pagepin' ),
     914            'inviteAndCreate'      => __( 'Invite & Create Link', 'pagepin' ),
     915            'copy'                 => __( 'Copy', 'pagepin' ),
     916            'copied'               => __( 'Copied!', 'pagepin' ),
     917            'accessGranted'        => __( 'Access granted', 'pagepin' ),
     918            'accessRevoked'        => __( 'Access revoked', 'pagepin' ),
     919            'nameRequired'         => __( 'Name is required', 'pagepin' ),
     920            'failedToCreate'       => __( 'Failed to create', 'pagepin' ),
     921            'failedToGrant'        => __( 'Failed to grant access', 'pagepin' ),
     922            'failedToRevoke'       => __( 'Failed to revoke', 'pagepin' ),
     923            'invitationSent'       => __( 'Invitation sent to', 'pagepin' ),
     924            'shareLinkCreated'     => __( 'Share link created for', 'pagepin' ),
     925            'activeExternalAccess' => __( 'Active External Access', 'pagepin' ),
     926            'noEmail'              => __( 'no email', 'pagepin' ),
     927            'revoke'               => __( 'Revoke', 'pagepin' ),
     928            'copyLink'             => __( 'Copy Link', 'pagepin' ),
     929            'name'                 => __( 'Name:', 'pagepin' ),
    930930        );
    931931
     
    11081108        set_transient( $rate_limit_key, $submission_count + 1, HOUR_IN_SECONDS );
    11091109
    1110         $screenshot_data = isset( $_POST['screenshot'] ) ? wp_unslash( $_POST['screenshot'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Base64 screenshot data, validated in save_screenshot().
     1110        $screenshot_data       = isset( $_POST['screenshot'] ) ? wp_unslash( $_POST['screenshot'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Base64 screenshot data, validated in save_screenshot().
    11111111        $feedback_data_json    = isset( $_POST['feedback_data'] ) ? wp_unslash( $_POST['feedback_data'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, validated after decode.
    11121112        $page_url              = isset( $_POST['page_url'] ) ? esc_url_raw( wp_unslash( $_POST['page_url'] ) ) : '';
     
    16561656                'adminUrl' => admin_url( 'admin.php?page=pagepin' ),
    16571657                'strings'  => array(
    1658                     'next'              => __( 'Next', 'pagepin' ),
    1659                     'finish'            => __( 'Finish Setup', 'pagepin' ),
    1660                     'error'             => __( 'An error occurred. Please try again.', 'pagepin' ),
    1661                     'emailRequired'     => __( 'Please enter at least one email address.', 'pagepin' ),
    1662                     'emailInvalid'      => __( 'Please enter a valid email address.', 'pagepin' ),
    1663                     'roleRequired'      => __( 'Please select at least one user role.', 'pagepin' ),
    1664                     'namePlaceholder'   => __( 'Name (optional)', 'pagepin' ),
    1665                     'emailPlaceholder'  => __( 'Email Address', 'pagepin' ),
    1666                     'remove'            => __( 'Remove', 'pagepin' ),
    1667                     'summaryRecipients' => __( 'Email Recipients', 'pagepin' ),
    1668                     'summaryRoles'      => __( 'User Roles', 'pagepin' ),
    1669                     'summaryPublic'     => __( 'Public Feedback', 'pagepin' ),
    1670                     'summaryPinpoint'       => __( 'Pinpoint', 'pagepin' ),
     1658                    'next'                 => __( 'Next', 'pagepin' ),
     1659                    'finish'               => __( 'Finish Setup', 'pagepin' ),
     1660                    'error'                => __( 'An error occurred. Please try again.', 'pagepin' ),
     1661                    'emailRequired'        => __( 'Please enter at least one email address.', 'pagepin' ),
     1662                    'emailInvalid'         => __( 'Please enter a valid email address.', 'pagepin' ),
     1663                    'roleRequired'         => __( 'Please select at least one user role.', 'pagepin' ),
     1664                    'namePlaceholder'      => __( 'Name (optional)', 'pagepin' ),
     1665                    'emailPlaceholder'     => __( 'Email Address', 'pagepin' ),
     1666                    'remove'               => __( 'Remove', 'pagepin' ),
     1667                    'summaryRecipients'    => __( 'Email Recipients', 'pagepin' ),
     1668                    'summaryRoles'         => __( 'User Roles', 'pagepin' ),
     1669                    'summaryPublic'        => __( 'Public Feedback', 'pagepin' ),
     1670                    'summaryPinpoint'      => __( 'Pinpoint', 'pagepin' ),
    16711671                    'summaryCollaborators' => __( 'Collaborators', 'pagepin' ),
    16721672                    'summaryTags'          => __( 'Tags', 'pagepin' ),
     
    17501750
    17511751        if ( isset( $form_data['collaborator_link_expiry'] ) ) {
    1752             $expiry         = absint( $form_data['collaborator_link_expiry'] );
    1753             $allowed_values = array( 0, 7, 30, 90 );
     1752            $expiry                              = absint( $form_data['collaborator_link_expiry'] );
     1753            $allowed_values                      = array( 0, 7, 30, 90 );
    17541754            $options['collaborator_link_expiry'] = in_array( $expiry, $allowed_values, true ) ? $expiry : 0;
    17551755        }
     
    24382438
    24392439            if ( isset( $input['collaborator_link_expiry'] ) ) {
    2440                 $expiry = absint( $input['collaborator_link_expiry'] );
    2441                 $allowed_values = array( 0, 7, 30, 90 );
     2440                $expiry                                = absint( $input['collaborator_link_expiry'] );
     2441                $allowed_values                        = array( 0, 7, 30, 90 );
    24422442                $sanitized['collaborator_link_expiry'] = in_array( $expiry, $allowed_values, true ) ? $expiry : 0;
    24432443            }
     
    26512651        }
    26522652
    2653         $screenshot_data = isset( $_POST['screenshot'] ) ? wp_unslash( $_POST['screenshot'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Base64 screenshot data, validated in save_screenshot().
     2653        $screenshot_data    = isset( $_POST['screenshot'] ) ? wp_unslash( $_POST['screenshot'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Base64 screenshot data, validated in save_screenshot().
    26542654        $feedback_data_json = isset( $_POST['feedback_data'] ) ? wp_unslash( $_POST['feedback_data'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, validated after decode.
    26552655        $page_url           = isset( $_POST['page_url'] ) ? esc_url_raw( wp_unslash( $_POST['page_url'] ) ) : '';
     
    28102810
    28112811        if ( ! empty( $mentions ) ) {
    2812             $email_handler = new PagePin_Pinpoint_Email();
    28132812            $pinpoint = $database->get( $pinpoint_id );
    28142813            if ( $pinpoint && ! empty( $pinpoint->comments ) ) {
    28152814                $comment_id = $pinpoint->comments[0]->id;
    2816                 $user_ids = array();
    2817                 foreach ( $mentions as $username ) {
    2818                     $user = get_user_by( 'login', $username );
    2819                     if ( $user ) {
    2820                         $user_ids[] = $user->ID;
    2821                     }
    2822                 }
    2823                 if ( ! empty( $user_ids ) ) {
    2824                     $email_handler->send_mention_notification( $pinpoint_id, $comment_id, $user_ids );
    2825                 }
     2815                $this->process_mentions( $mentions, $pinpoint_id, $comment_id );
    28262816            }
    28272817        }
     
    29102900        // Include share links if enabled.
    29112901        if ( ! empty( $options['enable_share_links'] ) ) {
    2912             $collab_db            = new PagePin_Collaborator_Database();
     2902            $collab_db             = new PagePin_Collaborator_Database();
    29132903            $pinpoint->share_links = $collab_db->get_share_links_for_pinpoint( $pinpoint_id );
    29142904        }
     
    29592949        }
    29602950
     2951        $share_links_created = $this->process_mentions( $mentions, $pinpoint_id, $comment_id );
     2952
    29612953        $email_handler = new PagePin_Pinpoint_Email();
    2962 
    2963         $share_links_created = array();
    2964 
    2965         if ( ! empty( $mentions ) ) {
    2966             $user_ids         = array();
    2967             $collaborator_ids = array();
    2968             $collab_db        = new PagePin_Collaborator_Database();
    2969             foreach ( $mentions as $mention ) {
    2970                 // Check if this is a share-link mention (!@name).
    2971                 if ( str_starts_with( $mention, '!' ) ) {
    2972                     $share_name = substr( $mention, 1 );
    2973                     if ( ! empty( $share_name ) ) {
    2974                         $share_url = $this->handle_share_link_mention( $share_name, $pinpoint_id );
    2975                         if ( $share_url ) {
    2976                             $share_links_created[] = array(
    2977                                 'name' => $share_name,
    2978                                 'url'  => $share_url,
    2979                             );
    2980                         }
    2981                     }
    2982                     continue;
    2983                 }
    2984                 // Check if this is a collaborator mention (collab_X).
    2985                 if ( str_starts_with( $mention, 'collab_' ) ) {
    2986                     $collab_id = absint( substr( $mention, 7 ) );
    2987                     if ( $collab_id > 0 ) {
    2988                         // Auto-grant access if collaborator doesn't have it yet.
    2989                         if ( ! $collab_db->has_access( $collab_id, $pinpoint_id ) ) {
    2990                             $collab_db->grant_access( $collab_id, $pinpoint_id, get_current_user_id() );
    2991                         }
    2992                         $collaborator_ids[] = $collab_id;
    2993                     }
    2994                     continue;
    2995                 }
    2996                 // Check if this is an email invite mention.
    2997                 if ( is_email( $mention ) ) {
    2998                     $this->handle_email_mention( $mention, $pinpoint_id );
    2999                     continue;
    3000                 }
    3001                 $user = get_user_by( 'login', $mention );
    3002                 if ( $user ) {
    3003                     $user_ids[] = $user->ID;
    3004                 }
    3005             }
    3006             if ( ! empty( $user_ids ) ) {
    3007                 $email_handler->send_mention_notification( $pinpoint_id, $comment_id, $user_ids );
    3008             }
    3009             if ( ! empty( $collaborator_ids ) ) {
    3010                 $email_handler->send_collaborator_mention_notification( $pinpoint_id, $comment_id, $collaborator_ids );
    3011             }
    3012         }
    3013 
    30142954        $email_handler->send_thread_notification( $pinpoint_id, $comment_id );
    30152955
     
    30382978        $display_name = ucfirst( $email_parts[0] );
    30392979        $this->handle_email_collaborator_creation( $email, $display_name, $pinpoint_id );
     2980    }
     2981
     2982    /**
     2983     * Process mentions from a comment: send notifications, grant access, create share links.
     2984     *
     2985     * @since 1.0.3
     2986     * @param array $mentions    Array of mention strings extracted from comment text.
     2987     * @param int   $pinpoint_id The pinpoint ID.
     2988     * @param int   $comment_id  The comment ID.
     2989     * @return array Share links created (array of name/url pairs).
     2990     */
     2991    private function process_mentions( $mentions, $pinpoint_id, $comment_id ) {
     2992        if ( empty( $mentions ) || ! is_array( $mentions ) ) {
     2993            return array();
     2994        }
     2995
     2996        $mentions            = array_unique( $mentions );
     2997        $user_ids            = array();
     2998        $collaborator_ids    = array();
     2999        $share_links_created = array();
     3000        $collab_db           = new PagePin_Collaborator_Database();
     3001        $current_user_id     = get_current_user_id();
     3002
     3003        foreach ( $mentions as $mention ) {
     3004            // Share-link mention (!@name).
     3005            if ( str_starts_with( $mention, '!' ) ) {
     3006                $share_name = substr( $mention, 1 );
     3007                if ( ! empty( $share_name ) ) {
     3008                    $share_url = $this->handle_share_link_mention( $share_name, $pinpoint_id );
     3009                    if ( $share_url ) {
     3010                        $share_links_created[] = array(
     3011                            'name' => $share_name,
     3012                            'url'  => $share_url,
     3013                        );
     3014                    }
     3015                }
     3016                continue;
     3017            }
     3018            // Collaborator mention (collab_X).
     3019            if ( str_starts_with( $mention, 'collab_' ) ) {
     3020                $collab_id = absint( substr( $mention, 7 ) );
     3021                if ( $collab_id > 0 ) {
     3022                    if ( ! $collab_db->has_access( $collab_id, $pinpoint_id ) ) {
     3023                        $collab_db->grant_access( $collab_id, $pinpoint_id, $current_user_id );
     3024                    }
     3025                    $collaborator_ids[] = $collab_id;
     3026                }
     3027                continue;
     3028            }
     3029            // Email invite mention.
     3030            if ( is_email( $mention ) ) {
     3031                $this->handle_email_mention( $mention, $pinpoint_id );
     3032                continue;
     3033            }
     3034            // WordPress user mention.
     3035            $user = get_user_by( 'login', $mention );
     3036            if ( $user && absint( $user->ID ) !== $current_user_id ) {
     3037                $user_ids[] = $user->ID;
     3038            }
     3039        }
     3040
     3041        $email_handler = new PagePin_Pinpoint_Email();
     3042        if ( ! empty( $user_ids ) ) {
     3043            $email_handler->send_mention_notification( $pinpoint_id, $comment_id, $user_ids );
     3044        }
     3045        if ( ! empty( $collaborator_ids ) ) {
     3046            $email_handler->send_collaborator_mention_notification( $pinpoint_id, $comment_id, $collaborator_ids );
     3047        }
     3048
     3049        return $share_links_created;
    30403050    }
    30413051
     
    34653475            $collab_db     = new PagePin_Collaborator_Database();
    34663476            $collaborators = $collab_db->search_collaborators( $search );
     3477            $guest_avatar  = get_avatar_url( 0, array( 'size' => 32 ) );
    34673478
    34683479            foreach ( $collaborators as $collab ) {
    3469                 $avatar_email = ! empty( $collab->email ) ? $collab->email : '';
    34703480                $display_info = ! empty( $collab->email ) ? $collab->email : __( 'Guest', 'pagepin' );
    34713481
     
    34753485                    'login'           => 'collab_' . $collab->id,
    34763486                    'display_login'   => $display_info,
    3477                     'avatar'          => get_avatar_url( $avatar_email, array( 'size' => 32 ) ),
     3487                    'avatar'          => $guest_avatar,
    34783488                    'is_collaborator' => true,
    34793489                );
     
    34883498                if ( ! $existing_user && ! $existing_collab ) {
    34893499                    $users[] = array(
    3490                         'id'          => 'invite_' . $search,
    3491                         'name'        => __( 'Invite', 'pagepin' ) . ': ' . $search,
    3492                         'login'       => $search,
    3493                         'avatar'      => get_avatar_url( $search, array( 'size' => 32 ) ),
    3494                         'is_invite'   => true,
     3500                        'id'        => 'invite_' . $search,
     3501                        'name'      => __( 'Invite', 'pagepin' ) . ': ' . $search,
     3502                        'login'     => $search,
     3503                        'avatar'    => get_avatar_url( $search, array( 'size' => 32 ) ),
     3504                        'is_invite' => true,
    34953505                    );
    34963506                }
     
    40894099            if ( ! isset( $options['enable_tags'] ) ) {
    40904100                $options['enable_tags'] = true;
    4091                 $changed               = true;
     4101                $changed                = true;
    40924102            }
    40934103            if ( ! isset( $options['admin_bar_enabled'] ) ) {
  • pagepin/trunk/readme.txt

    r3494254 r3494810  
    66Tested up to: 6.9
    77Requires PHP: 8.1
    8 Stable tag: 1.0.2
     8Stable tag: 1.0.3
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    214214
    215215== Changelog ==
     216
     217= 1.0.3 =
     218* Improvement: Setup wizard fully localized in English with translation support
     219* Fix: Contrast issue with Pinpoint role headings in setup wizard
     220* Fix: Duplicate @mention notifications prevented
     221* Fix: Self-mention no longer triggers email notification
     222* Fix: Collaborator and email mentions now work in initial pinpoint comments
     223* Fix: Guest collaborators show generic avatar in mention autocomplete
     224* Fix: Pinpoint reply textarea sizing and overflow
     225* Improvement: Inline styles moved to CSS classes for better maintainability
     226* Improvement: Refined wizard step descriptions for clarity
    216227
    217228= 1.0.2 =
     
    248259== Upgrade Notice ==
    249260
     261= 1.0.3 =
     262Setup wizard fully English and translatable. Fixed duplicate mention notifications, self-mentions, and collaborator mentions in initial pinpoints.
     263
    250264= 1.0.2 =
    251265New collaboration features: Tag system, external collaborators via Magic Link, shareable feedback links, collaborator reuse, @mention notifications, and admin bar integration.
Note: See TracChangeset for help on using the changeset viewer.