refactor: Extract UI into dedicated control panel module
- Create InsertrControlPanel class for unified UI management - Separate business logic from presentation layer - Remove DOM manipulation from auth.js and editor.js - Add comprehensive CSS for status indicators and editing effects - Implement consistent kebab-case file naming - Add event-driven communication between core and UI layers UI Architecture: - Unified control panel with status indicator and action buttons - Color-coded status dots (gray/blue/green for visitor/auth/editing) - Professional editing hover effects with tooltips - Responsive design for mobile devices - Proper z-index and accessibility management Business Logic: - Pure auth.js focused on authentication state and OAuth flows - Pure editor.js focused on content editing workflow - Event emitters for state changes between modules - Clean separation of concerns and testable architecture
This commit is contained in:
4
go.mod
4
go.mod
@@ -15,7 +15,9 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/coreos/go-oidc/v3 v3.15.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
@@ -30,7 +32,9 @@ require (
|
|||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
|
golang.org/x/crypto v0.41.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
|
golang.org/x/oauth2 v0.31.0 // indirect
|
||||||
golang.org/x/sys v0.35.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -1,3 +1,5 @@
|
|||||||
|
github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg=
|
||||||
|
github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -11,6 +13,8 @@ github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
|||||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
@@ -71,10 +75,14 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
|||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
|
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
|
||||||
|
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
|||||||
@@ -85,22 +85,85 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* =================================================================
|
/* =================================================================
|
||||||
AUTHENTICATION CONTROLS
|
UNIFIED CONTROL PANEL
|
||||||
================================================================= */
|
================================================================= */
|
||||||
|
|
||||||
.insertr-auth-controls {
|
.insertr-control-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
z-index: var(--insertr-z-overlay);
|
z-index: var(--insertr-z-overlay);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: var(--insertr-spacing-sm);
|
||||||
font-family: var(--insertr-font-family);
|
font-family: var(--insertr-font-family);
|
||||||
font-size: var(--insertr-font-size-base);
|
font-size: var(--insertr-font-size-base);
|
||||||
|
max-width: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.insertr-auth-btn {
|
/* Status Section */
|
||||||
|
.insertr-status-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: var(--insertr-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-indicator {
|
||||||
|
background: var(--insertr-bg-primary);
|
||||||
|
color: var(--insertr-text-primary);
|
||||||
|
border: 1px solid var(--insertr-border-color);
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--insertr-spacing-xs);
|
||||||
|
font-size: var(--insertr-font-size-sm);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: var(--insertr-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-text {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
color: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: var(--insertr-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-visitor {
|
||||||
|
background: var(--insertr-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-authenticated {
|
||||||
|
background: var(--insertr-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-editing {
|
||||||
|
background: var(--insertr-success);
|
||||||
|
animation: insertr-pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes insertr-pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.6; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Buttons Section */
|
||||||
|
.insertr-action-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--insertr-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-action-btn {
|
||||||
background: var(--insertr-primary);
|
background: var(--insertr-primary);
|
||||||
color: var(--insertr-text-inverse);
|
color: var(--insertr-text-inverse);
|
||||||
border: none;
|
border: none;
|
||||||
@@ -117,21 +180,112 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: var(--insertr-transition);
|
transition: var(--insertr-transition);
|
||||||
line-height: var(--insertr-line-height);
|
line-height: var(--insertr-line-height);
|
||||||
min-width: 80px;
|
min-width: 120px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.insertr-auth-btn:hover {
|
.insertr-action-btn:hover {
|
||||||
background: var(--insertr-primary-hover);
|
background: var(--insertr-primary-hover);
|
||||||
color: var(--insertr-text-inverse);
|
color: var(--insertr-text-inverse);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.insertr-auth-btn:focus {
|
.insertr-action-btn:focus {
|
||||||
outline: 2px solid var(--insertr-primary);
|
outline: 2px solid var(--insertr-primary);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.insertr-action-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-action-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Type Variants */
|
||||||
|
.insertr-enhance-btn {
|
||||||
|
background: var(--insertr-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-enhance-btn:hover {
|
||||||
|
background: #138496;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-edit-btn.insertr-edit-active {
|
||||||
|
background: var(--insertr-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-edit-btn.insertr-edit-active:hover {
|
||||||
|
background: #1e7e34;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-auth-btn.insertr-authenticated {
|
||||||
|
background: var(--insertr-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-auth-btn.insertr-authenticated:hover {
|
||||||
|
background: var(--insertr-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
EDITING INDICATORS
|
||||||
|
Visual feedback for editable content
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.insertr-editing-hover {
|
||||||
|
outline: 2px dashed var(--insertr-primary);
|
||||||
|
outline-offset: 2px;
|
||||||
|
background: rgba(0, 123, 255, 0.05);
|
||||||
|
position: relative;
|
||||||
|
transition: var(--insertr-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-editing-hover::after {
|
||||||
|
content: '✏️ Click to edit';
|
||||||
|
position: absolute;
|
||||||
|
top: -30px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: var(--insertr-text-primary);
|
||||||
|
color: var(--insertr-text-inverse);
|
||||||
|
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
font-size: var(--insertr-font-size-sm);
|
||||||
|
font-family: var(--insertr-font-family);
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: var(--insertr-z-tooltip);
|
||||||
|
opacity: 0;
|
||||||
|
animation: insertr-tooltip-show 0.2s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes insertr-tooltip-show {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(-5px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide editing indicators when not in edit mode */
|
||||||
|
body:not(.insertr-edit-mode) .insertr-editing-hover {
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* =================================================================
|
/* =================================================================
|
||||||
MODAL OVERLAY & CONTAINER
|
MODAL OVERLAY & CONTAINER
|
||||||
================================================================= */
|
================================================================= */
|
||||||
@@ -517,9 +671,27 @@
|
|||||||
padding: var(--insertr-spacing-sm);
|
padding: var(--insertr-spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.insertr-auth-controls {
|
.insertr-control-panel {
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
|
max-width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-action-btn {
|
||||||
|
min-width: 100px;
|
||||||
|
font-size: var(--insertr-font-size-sm);
|
||||||
|
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-indicator {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 3px var(--insertr-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-editing-hover::after {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px var(--insertr-spacing-xs);
|
||||||
|
top: -25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.insertr-form-actions {
|
.insertr-form-actions {
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* InsertrAuth - Authentication and state management
|
* InsertrAuth - Authentication and state management
|
||||||
* Handles user authentication, edit mode, and visual state changes
|
* Pure business logic - no DOM manipulation or UI concerns
|
||||||
|
*
|
||||||
|
* Responsibilities:
|
||||||
|
* - Manage authentication state
|
||||||
|
* - Handle OAuth flows
|
||||||
|
* - Validate permissions
|
||||||
|
* - Emit state change events
|
||||||
*/
|
*/
|
||||||
export class InsertrAuth {
|
export class InsertrAuth {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
@@ -20,7 +26,33 @@ export class InsertrAuth {
|
|||||||
isAuthenticating: false
|
isAuthenticating: false
|
||||||
};
|
};
|
||||||
|
|
||||||
this.statusIndicator = null;
|
// Event listeners for state changes
|
||||||
|
this.listeners = {
|
||||||
|
stateChange: [],
|
||||||
|
authChange: [],
|
||||||
|
editModeChange: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event emitter methods
|
||||||
|
*/
|
||||||
|
on(event, callback) {
|
||||||
|
if (this.listeners[event]) {
|
||||||
|
this.listeners[event].push(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
off(event, callback) {
|
||||||
|
if (this.listeners[event]) {
|
||||||
|
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(event, data) {
|
||||||
|
if (this.listeners[event]) {
|
||||||
|
this.listeners[event].forEach(callback => callback(data));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,7 +60,6 @@ export class InsertrAuth {
|
|||||||
*/
|
*/
|
||||||
init() {
|
init() {
|
||||||
console.log('🔧 Insertr: Scanning for editor gates');
|
console.log('🔧 Insertr: Scanning for editor gates');
|
||||||
|
|
||||||
this.setupEditorGates();
|
this.setupEditorGates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,11 +73,6 @@ export class InsertrAuth {
|
|||||||
|
|
||||||
console.log('🔐 Initializing Insertr Editing System');
|
console.log('🔐 Initializing Insertr Editing System');
|
||||||
|
|
||||||
this.createAuthControls();
|
|
||||||
this.setupAuthenticationControls();
|
|
||||||
this.createStatusIndicator();
|
|
||||||
this.updateBodyClasses();
|
|
||||||
|
|
||||||
// Auto-enable edit mode after OAuth
|
// Auto-enable edit mode after OAuth
|
||||||
this.state.editMode = true;
|
this.state.editMode = true;
|
||||||
this.state.isInitialized = true;
|
this.state.isInitialized = true;
|
||||||
@@ -56,10 +82,10 @@ export class InsertrAuth {
|
|||||||
window.Insertr.startEditor();
|
window.Insertr.startEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateButtonStates();
|
// Emit state change for UI to update
|
||||||
this.updateStatusIndicator();
|
this.emitStateChange();
|
||||||
|
|
||||||
console.log('📱 Editing system active - Controls in bottom-right corner');
|
console.log('📱 Editing system active');
|
||||||
console.log('✏️ Edit mode enabled - Click elements to edit');
|
console.log('✏️ Edit mode enabled - Click elements to edit');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,9 +102,6 @@ export class InsertrAuth {
|
|||||||
|
|
||||||
console.log(`🚪 Found ${gates.length} editor gate(s)`);
|
console.log(`🚪 Found ${gates.length} editor gate(s)`);
|
||||||
|
|
||||||
// Add gate styles
|
|
||||||
this.addGateStyles();
|
|
||||||
|
|
||||||
gates.forEach((gate, index) => {
|
gates.forEach((gate, index) => {
|
||||||
// Store original text for later restoration
|
// Store original text for later restoration
|
||||||
if (!gate.hasAttribute('data-original-text')) {
|
if (!gate.hasAttribute('data-original-text')) {
|
||||||
@@ -90,7 +113,7 @@ export class InsertrAuth {
|
|||||||
this.handleGateClick(gate, index);
|
this.handleGateClick(gate, index);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add subtle styling to indicate it's clickable
|
// Add minimal styling to indicate it's clickable
|
||||||
gate.style.cursor = 'pointer';
|
gate.style.cursor = 'pointer';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -121,7 +144,7 @@ export class InsertrAuth {
|
|||||||
// Initialize full editing system
|
// Initialize full editing system
|
||||||
this.initializeFullSystem();
|
this.initializeFullSystem();
|
||||||
|
|
||||||
// Conditionally hide gates based on options
|
// Handle gate visibility based on options
|
||||||
if (this.options.hideGatesAfterAuth) {
|
if (this.options.hideGatesAfterAuth) {
|
||||||
this.hideAllGates();
|
this.hideAllGates();
|
||||||
} else {
|
} else {
|
||||||
@@ -198,47 +221,6 @@ export class InsertrAuth {
|
|||||||
console.log('🚪 Editor gates restored to original state');
|
console.log('🚪 Editor gates restored to original state');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create authentication control buttons (bottom-right positioned)
|
|
||||||
*/
|
|
||||||
createAuthControls() {
|
|
||||||
// Check if controls already exist
|
|
||||||
if (document.getElementById('insertr-auth-controls')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const controlsHtml = `
|
|
||||||
<div id="insertr-auth-controls" class="insertr-auth-controls">
|
|
||||||
<button id="insertr-auth-toggle" class="insertr-auth-btn">Login as Client</button>
|
|
||||||
<button id="insertr-edit-toggle" class="insertr-auth-btn" style="display: none;">Edit Mode: Off</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Add controls to page
|
|
||||||
document.body.insertAdjacentHTML('beforeend', controlsHtml);
|
|
||||||
|
|
||||||
// Add styles for controls
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup event listeners for authentication controls
|
|
||||||
*/
|
|
||||||
setupAuthenticationControls() {
|
|
||||||
const authToggle = document.getElementById('insertr-auth-toggle');
|
|
||||||
const editToggle = document.getElementById('insertr-edit-toggle');
|
|
||||||
|
|
||||||
if (authToggle) {
|
|
||||||
authToggle.addEventListener('click', () => this.toggleAuthentication());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editToggle) {
|
|
||||||
editToggle.addEventListener('click', () => this.toggleEditMode());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle authentication state
|
* Toggle authentication state
|
||||||
*/
|
*/
|
||||||
@@ -255,9 +237,7 @@ export class InsertrAuth {
|
|||||||
this.state.editMode = false;
|
this.state.editMode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateBodyClasses();
|
this.emitStateChange();
|
||||||
this.updateButtonStates();
|
|
||||||
this.updateStatusIndicator();
|
|
||||||
|
|
||||||
console.log(this.state.isAuthenticated
|
console.log(this.state.isAuthenticated
|
||||||
? '✅ Authenticated as Demo User'
|
? '✅ Authenticated as Demo User'
|
||||||
@@ -281,9 +261,7 @@ export class InsertrAuth {
|
|||||||
this.state.activeEditor = null;
|
this.state.activeEditor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateBodyClasses();
|
this.emitStateChange();
|
||||||
this.updateButtonStates();
|
|
||||||
this.updateStatusIndicator();
|
|
||||||
|
|
||||||
console.log(this.state.editMode
|
console.log(this.state.editMode
|
||||||
? '✏️ Edit mode ON - Click elements to edit'
|
? '✏️ Edit mode ON - Click elements to edit'
|
||||||
@@ -291,83 +269,18 @@ export class InsertrAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update body CSS classes based on authentication state
|
* Emit state change events for UI updates
|
||||||
*/
|
*/
|
||||||
updateBodyClasses() {
|
emitStateChange() {
|
||||||
document.body.classList.toggle('insertr-authenticated', this.state.isAuthenticated);
|
const stateData = {
|
||||||
document.body.classList.toggle('insertr-edit-mode', this.state.editMode);
|
isAuthenticated: this.state.isAuthenticated,
|
||||||
}
|
editMode: this.state.editMode,
|
||||||
|
currentUser: this.state.currentUser
|
||||||
/**
|
};
|
||||||
* Update button text and visibility
|
|
||||||
*/
|
|
||||||
updateButtonStates() {
|
|
||||||
const authBtn = document.getElementById('insertr-auth-toggle');
|
|
||||||
const editBtn = document.getElementById('insertr-edit-toggle');
|
|
||||||
|
|
||||||
if (authBtn) {
|
|
||||||
authBtn.textContent = this.state.isAuthenticated ? 'Logout' : 'Login as Client';
|
|
||||||
authBtn.className = `insertr-auth-btn ${this.state.isAuthenticated ? 'insertr-authenticated' : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editBtn) {
|
|
||||||
editBtn.style.display = this.state.isAuthenticated ? 'inline-block' : 'none';
|
|
||||||
editBtn.textContent = `Edit Mode: ${this.state.editMode ? 'On' : 'Off'}`;
|
|
||||||
editBtn.className = `insertr-auth-btn ${this.state.editMode ? 'insertr-edit-active' : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update enhance button visibility
|
|
||||||
this.updateEnhanceButtonVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create status indicator
|
|
||||||
*/
|
|
||||||
createStatusIndicator() {
|
|
||||||
// Check if already exists
|
|
||||||
if (document.getElementById('insertr-status')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusHtml = `
|
|
||||||
<div id="insertr-status-controls" class="insertr-status-controls">
|
|
||||||
<div id="insertr-status" class="insertr-status">
|
|
||||||
<div class="insertr-status-content">
|
|
||||||
<span class="insertr-status-text">Visitor Mode</span>
|
|
||||||
<span class="insertr-status-dot"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button id="insertr-enhance-btn" class="insertr-auth-btn" style="display: none;" title="Enhance files with latest content">🔄 Enhance</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
document.body.insertAdjacentHTML('beforeend', statusHtml);
|
|
||||||
this.statusIndicator = document.getElementById('insertr-status');
|
|
||||||
this.setupEnhanceButton();
|
|
||||||
this.updateStatusIndicator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update status indicator text and style
|
|
||||||
*/
|
|
||||||
updateStatusIndicator() {
|
|
||||||
const statusText = document.querySelector('.insertr-status-text');
|
|
||||||
const statusDot = document.querySelector('.insertr-status-dot');
|
|
||||||
|
|
||||||
if (!statusText || !statusDot) return;
|
|
||||||
|
|
||||||
if (!this.state.isAuthenticated) {
|
|
||||||
statusText.textContent = 'Visitor Mode';
|
|
||||||
statusDot.className = 'insertr-status-dot insertr-status-visitor';
|
|
||||||
} else if (this.state.editMode) {
|
|
||||||
statusText.textContent = 'Editing';
|
|
||||||
statusDot.className = 'insertr-status-dot insertr-status-editing';
|
|
||||||
} else {
|
|
||||||
statusText.textContent = 'Authenticated';
|
|
||||||
statusDot.className = 'insertr-status-dot insertr-status-authenticated';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
this.emit('stateChange', stateData);
|
||||||
|
this.emit('authChange', { isAuthenticated: this.state.isAuthenticated, user: this.state.currentUser });
|
||||||
|
this.emit('editModeChange', { editMode: this.state.editMode });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -391,34 +304,6 @@ export class InsertrAuth {
|
|||||||
return this.state.currentUser;
|
return this.state.currentUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add minimal styles for editor gates
|
|
||||||
*/
|
|
||||||
addGateStyles() {
|
|
||||||
const styles = `
|
|
||||||
.insertr-gate {
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.insertr-gate:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Optional: Hide gates when authenticated (only if hideGatesAfterAuth option is true) */
|
|
||||||
body.insertr-hide-gates .insertr-gate {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const styleSheet = document.createElement('style');
|
|
||||||
styleSheet.type = 'text/css';
|
|
||||||
styleSheet.innerHTML = styles;
|
|
||||||
document.head.appendChild(styleSheet);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OAuth integration placeholder
|
* OAuth integration placeholder
|
||||||
* In production, this would handle real OAuth flows
|
* In production, this would handle real OAuth flows
|
||||||
@@ -437,99 +322,44 @@ export class InsertrAuth {
|
|||||||
role: 'editor'
|
role: 'editor'
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateBodyClasses();
|
this.emitStateChange();
|
||||||
this.updateButtonStates();
|
|
||||||
this.updateStatusIndicator();
|
|
||||||
|
|
||||||
console.log('✅ OAuth authentication successful');
|
console.log('✅ OAuth authentication successful');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup enhance button functionality
|
* Validate if user has permission for specific action
|
||||||
*/
|
*/
|
||||||
setupEnhanceButton() {
|
hasPermission(action) {
|
||||||
const enhanceBtn = document.getElementById('insertr-enhance-btn');
|
if (!this.isAuthenticated()) return false;
|
||||||
if (!enhanceBtn) return;
|
|
||||||
|
const user = this.getCurrentUser();
|
||||||
|
if (!user) return false;
|
||||||
|
|
||||||
enhanceBtn.addEventListener('click', async () => {
|
// Simple role-based permissions
|
||||||
await this.enhanceFiles();
|
switch (action) {
|
||||||
});
|
case 'edit':
|
||||||
|
return ['admin', 'editor'].includes(user.role);
|
||||||
// Show enhance button only in development/authenticated mode
|
case 'enhance':
|
||||||
this.updateEnhanceButtonVisibility();
|
return ['admin'].includes(user.role);
|
||||||
}
|
case 'manage':
|
||||||
|
return user.role === 'admin';
|
||||||
/**
|
default:
|
||||||
* Update enhance button visibility based on authentication state
|
return false;
|
||||||
*/
|
|
||||||
updateEnhanceButtonVisibility() {
|
|
||||||
const enhanceBtn = document.getElementById('insertr-enhance-btn');
|
|
||||||
if (!enhanceBtn) return;
|
|
||||||
|
|
||||||
// Show enhance button when authenticated (indicates dev mode)
|
|
||||||
if (this.state.isAuthenticated) {
|
|
||||||
enhanceBtn.style.display = 'inline-block';
|
|
||||||
} else {
|
|
||||||
enhanceBtn.style.display = 'none';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger manual file enhancement
|
* Get authentication state snapshot
|
||||||
*/
|
*/
|
||||||
async enhanceFiles() {
|
getState() {
|
||||||
const enhanceBtn = document.getElementById('insertr-enhance-btn');
|
return {
|
||||||
if (!enhanceBtn) return;
|
isAuthenticated: this.state.isAuthenticated,
|
||||||
|
editMode: this.state.editMode,
|
||||||
// Get site ID from window context or configuration
|
currentUser: this.state.currentUser,
|
||||||
const siteId = window.insertrConfig?.siteId || this.options.siteId || 'demo';
|
isInitialized: this.state.isInitialized,
|
||||||
|
isAuthenticating: this.state.isAuthenticating
|
||||||
try {
|
};
|
||||||
// Show loading state
|
|
||||||
enhanceBtn.textContent = '⏳ Enhancing...';
|
|
||||||
enhanceBtn.disabled = true;
|
|
||||||
|
|
||||||
// Smart server detection for enhance API (same logic as ApiClient)
|
|
||||||
const isDevelopment = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
|
|
||||||
const enhanceUrl = isDevelopment
|
|
||||||
? `http://localhost:8080/api/enhance?site_id=${siteId}` // Development: separate API server
|
|
||||||
: `/api/enhance?site_id=${siteId}`; // Production: same-origin API
|
|
||||||
|
|
||||||
// Call enhance API
|
|
||||||
const response = await fetch(enhanceUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${this.state.currentUser?.token || 'mock-token'}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Enhancement failed: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
console.log('✅ Files enhanced successfully:', result);
|
|
||||||
|
|
||||||
// Show success state briefly
|
|
||||||
enhanceBtn.textContent = '✅ Enhanced!';
|
|
||||||
|
|
||||||
// Reset button after success (no page reload needed)
|
|
||||||
setTimeout(() => {
|
|
||||||
enhanceBtn.textContent = '🔄 Enhance';
|
|
||||||
enhanceBtn.disabled = false;
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Enhancement failed:', error);
|
|
||||||
enhanceBtn.textContent = '❌ Failed';
|
|
||||||
|
|
||||||
// Reset button after error
|
|
||||||
setTimeout(() => {
|
|
||||||
enhanceBtn.textContent = '🔄 Enhance';
|
|
||||||
enhanceBtn.disabled = false;
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,14 @@
|
|||||||
import { InsertrFormRenderer } from '../ui/form-renderer.js';
|
import { InsertrFormRenderer } from '../ui/form-renderer.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InsertrEditor - Visual editing functionality
|
* InsertrEditor - Content editing workflow and business logic
|
||||||
|
* Pure business logic - no visual styling or DOM manipulation
|
||||||
|
*
|
||||||
|
* Responsibilities:
|
||||||
|
* - Manage editing workflow
|
||||||
|
* - Handle content persistence
|
||||||
|
* - Coordinate with form renderer
|
||||||
|
* - Emit editing events
|
||||||
*/
|
*/
|
||||||
export class InsertrEditor {
|
export class InsertrEditor {
|
||||||
constructor(core, auth, apiClient, options = {}) {
|
constructor(core, auth, apiClient, options = {}) {
|
||||||
@@ -11,6 +18,34 @@ export class InsertrEditor {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
this.isActive = false;
|
this.isActive = false;
|
||||||
this.formRenderer = new InsertrFormRenderer(apiClient);
|
this.formRenderer = new InsertrFormRenderer(apiClient);
|
||||||
|
|
||||||
|
// Event listeners for mode changes
|
||||||
|
this.listeners = {
|
||||||
|
modeChange: [],
|
||||||
|
editStart: [],
|
||||||
|
editEnd: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event emitter methods
|
||||||
|
*/
|
||||||
|
on(event, callback) {
|
||||||
|
if (this.listeners[event]) {
|
||||||
|
this.listeners[event].push(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
off(event, callback) {
|
||||||
|
if (this.listeners[event]) {
|
||||||
|
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(event, data) {
|
||||||
|
if (this.listeners[event]) {
|
||||||
|
this.listeners[event].forEach(callback => callback(data));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
@@ -19,37 +54,23 @@ export class InsertrEditor {
|
|||||||
console.log('🚀 Starting Insertr Editor');
|
console.log('🚀 Starting Insertr Editor');
|
||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
|
|
||||||
|
// Initialize all enhanced elements for editing
|
||||||
|
|
||||||
// Initialize all enhanced elements
|
|
||||||
const elements = this.core.getAllElements();
|
const elements = this.core.getAllElements();
|
||||||
console.log(`📝 Found ${elements.length} editable elements`);
|
console.log(`📝 Found ${elements.length} editable elements`);
|
||||||
|
|
||||||
elements.forEach(meta => this.initializeElement(meta));
|
elements.forEach(meta => this.initializeElement(meta));
|
||||||
|
|
||||||
|
// Emit mode change event for UI updates
|
||||||
|
this.emit('modeChange', { isActive: this.isActive });
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeElement(meta) {
|
initializeElement(meta) {
|
||||||
const { element, contentId, contentType } = meta;
|
const { element } = meta;
|
||||||
|
|
||||||
// Add visual indicators
|
// Add click handler for editing
|
||||||
element.style.cursor = 'pointer';
|
|
||||||
element.style.position = 'relative';
|
|
||||||
|
|
||||||
// Add interaction handlers
|
|
||||||
this.addHoverEffects(element);
|
|
||||||
this.addClickHandler(element, meta);
|
this.addClickHandler(element, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
addHoverEffects(element) {
|
|
||||||
element.addEventListener('mouseenter', () => {
|
|
||||||
element.classList.add('insertr-editing-hover');
|
|
||||||
});
|
|
||||||
|
|
||||||
element.addEventListener('mouseleave', () => {
|
|
||||||
element.classList.remove('insertr-editing-hover');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addClickHandler(element, meta) {
|
addClickHandler(element, meta) {
|
||||||
element.addEventListener('click', (e) => {
|
element.addEventListener('click', (e) => {
|
||||||
// Only allow editing if authenticated and in edit mode
|
// Only allow editing if authenticated and in edit mode
|
||||||
|
|||||||
@@ -7,14 +7,18 @@ import { InsertrCore } from './core/insertr.js';
|
|||||||
import { InsertrEditor } from './core/editor.js';
|
import { InsertrEditor } from './core/editor.js';
|
||||||
import { InsertrAuth } from './core/auth.js';
|
import { InsertrAuth } from './core/auth.js';
|
||||||
import { ApiClient } from './core/api-client.js';
|
import { ApiClient } from './core/api-client.js';
|
||||||
|
import { InsertrControlPanel } from './ui/control-panel.js';
|
||||||
|
|
||||||
// Create global Insertr instance
|
// Create global Insertr instance
|
||||||
window.Insertr = {
|
window.Insertr = {
|
||||||
// Core functionality
|
// Core functionality (business logic)
|
||||||
core: null,
|
core: null,
|
||||||
editor: null,
|
editor: null,
|
||||||
auth: null,
|
auth: null,
|
||||||
apiClient: null,
|
apiClient: null,
|
||||||
|
|
||||||
|
// UI layer (presentation)
|
||||||
|
controlPanel: null,
|
||||||
|
|
||||||
// Initialize the library
|
// Initialize the library
|
||||||
init(options = {}) {
|
init(options = {}) {
|
||||||
@@ -23,10 +27,14 @@ window.Insertr = {
|
|||||||
// Load CSS first
|
// Load CSS first
|
||||||
this.loadStyles(options);
|
this.loadStyles(options);
|
||||||
|
|
||||||
|
// Initialize core business logic modules
|
||||||
this.core = new InsertrCore(options);
|
this.core = new InsertrCore(options);
|
||||||
this.auth = new InsertrAuth(options);
|
this.auth = new InsertrAuth(options);
|
||||||
this.apiClient = new ApiClient(options);
|
this.apiClient = new ApiClient(options);
|
||||||
this.editor = new InsertrEditor(this.core, this.auth, this.apiClient, options);
|
this.editor = new InsertrEditor(this.core, this.auth, this.apiClient, options);
|
||||||
|
|
||||||
|
// Initialize UI layer
|
||||||
|
this.controlPanel = new InsertrControlPanel(this.auth, this.editor, this.apiClient, options);
|
||||||
|
|
||||||
// Auto-initialize if DOM is ready
|
// Auto-initialize if DOM is ready
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
@@ -70,12 +78,17 @@ window.Insertr = {
|
|||||||
document.head.appendChild(link);
|
document.head.appendChild(link);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Start the system - only creates the minimal trigger
|
// Start the system - initializes auth gates and UI
|
||||||
start() {
|
start() {
|
||||||
if (this.auth) {
|
if (this.auth) {
|
||||||
this.auth.init(); // Creates footer trigger only
|
this.auth.init(); // Sets up editor gates
|
||||||
}
|
}
|
||||||
// Note: Editor is NOT started here, only when trigger is clicked
|
|
||||||
|
if (this.controlPanel) {
|
||||||
|
this.controlPanel.init(); // Creates unified control panel UI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Editor is NOT started here, only when authentication succeeds
|
||||||
},
|
},
|
||||||
|
|
||||||
// Start the full editor system (called when trigger is activated)
|
// Start the full editor system (called when trigger is activated)
|
||||||
@@ -83,6 +96,11 @@ window.Insertr = {
|
|||||||
if (this.editor && !this.editor.isActive) {
|
if (this.editor && !this.editor.isActive) {
|
||||||
this.editor.start();
|
this.editor.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add editing indicators when editor starts
|
||||||
|
if (this.controlPanel) {
|
||||||
|
this.controlPanel.addEditingIndicators();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Public API methods
|
// Public API methods
|
||||||
|
|||||||
@@ -85,22 +85,85 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* =================================================================
|
/* =================================================================
|
||||||
AUTHENTICATION CONTROLS
|
UNIFIED CONTROL PANEL
|
||||||
================================================================= */
|
================================================================= */
|
||||||
|
|
||||||
.insertr-auth-controls {
|
.insertr-control-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
z-index: var(--insertr-z-overlay);
|
z-index: var(--insertr-z-overlay);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: var(--insertr-spacing-sm);
|
||||||
font-family: var(--insertr-font-family);
|
font-family: var(--insertr-font-family);
|
||||||
font-size: var(--insertr-font-size-base);
|
font-size: var(--insertr-font-size-base);
|
||||||
|
max-width: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.insertr-auth-btn {
|
/* Status Section */
|
||||||
|
.insertr-status-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: var(--insertr-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-indicator {
|
||||||
|
background: var(--insertr-bg-primary);
|
||||||
|
color: var(--insertr-text-primary);
|
||||||
|
border: 1px solid var(--insertr-border-color);
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--insertr-spacing-xs);
|
||||||
|
font-size: var(--insertr-font-size-sm);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: var(--insertr-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-text {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
color: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: var(--insertr-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-visitor {
|
||||||
|
background: var(--insertr-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-authenticated {
|
||||||
|
background: var(--insertr-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-editing {
|
||||||
|
background: var(--insertr-success);
|
||||||
|
animation: insertr-pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes insertr-pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.6; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Buttons Section */
|
||||||
|
.insertr-action-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--insertr-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-action-btn {
|
||||||
background: var(--insertr-primary);
|
background: var(--insertr-primary);
|
||||||
color: var(--insertr-text-inverse);
|
color: var(--insertr-text-inverse);
|
||||||
border: none;
|
border: none;
|
||||||
@@ -117,21 +180,112 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: var(--insertr-transition);
|
transition: var(--insertr-transition);
|
||||||
line-height: var(--insertr-line-height);
|
line-height: var(--insertr-line-height);
|
||||||
min-width: 80px;
|
min-width: 120px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.insertr-auth-btn:hover {
|
.insertr-action-btn:hover {
|
||||||
background: var(--insertr-primary-hover);
|
background: var(--insertr-primary-hover);
|
||||||
color: var(--insertr-text-inverse);
|
color: var(--insertr-text-inverse);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.insertr-auth-btn:focus {
|
.insertr-action-btn:focus {
|
||||||
outline: 2px solid var(--insertr-primary);
|
outline: 2px solid var(--insertr-primary);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.insertr-action-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-action-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Type Variants */
|
||||||
|
.insertr-enhance-btn {
|
||||||
|
background: var(--insertr-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-enhance-btn:hover {
|
||||||
|
background: #138496;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-edit-btn.insertr-edit-active {
|
||||||
|
background: var(--insertr-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-edit-btn.insertr-edit-active:hover {
|
||||||
|
background: #1e7e34;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-auth-btn.insertr-authenticated {
|
||||||
|
background: var(--insertr-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-auth-btn.insertr-authenticated:hover {
|
||||||
|
background: var(--insertr-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
EDITING INDICATORS
|
||||||
|
Visual feedback for editable content
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.insertr-editing-hover {
|
||||||
|
outline: 2px dashed var(--insertr-primary);
|
||||||
|
outline-offset: 2px;
|
||||||
|
background: rgba(0, 123, 255, 0.05);
|
||||||
|
position: relative;
|
||||||
|
transition: var(--insertr-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-editing-hover::after {
|
||||||
|
content: '✏️ Click to edit';
|
||||||
|
position: absolute;
|
||||||
|
top: -30px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: var(--insertr-text-primary);
|
||||||
|
color: var(--insertr-text-inverse);
|
||||||
|
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
font-size: var(--insertr-font-size-sm);
|
||||||
|
font-family: var(--insertr-font-family);
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: var(--insertr-z-tooltip);
|
||||||
|
opacity: 0;
|
||||||
|
animation: insertr-tooltip-show 0.2s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes insertr-tooltip-show {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(-5px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide editing indicators when not in edit mode */
|
||||||
|
body:not(.insertr-edit-mode) .insertr-editing-hover {
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* =================================================================
|
/* =================================================================
|
||||||
MODAL OVERLAY & CONTAINER
|
MODAL OVERLAY & CONTAINER
|
||||||
================================================================= */
|
================================================================= */
|
||||||
@@ -517,9 +671,27 @@
|
|||||||
padding: var(--insertr-spacing-sm);
|
padding: var(--insertr-spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.insertr-auth-controls {
|
.insertr-control-panel {
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
|
max-width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-action-btn {
|
||||||
|
min-width: 100px;
|
||||||
|
font-size: var(--insertr-font-size-sm);
|
||||||
|
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-status-indicator {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 3px var(--insertr-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-editing-hover::after {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px var(--insertr-spacing-xs);
|
||||||
|
top: -25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.insertr-form-actions {
|
.insertr-form-actions {
|
||||||
|
|||||||
371
lib/src/ui/control-panel.js
Normal file
371
lib/src/ui/control-panel.js
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
/**
|
||||||
|
* InsertrControlPanel - Unified UI Controller
|
||||||
|
* Handles all presentation layer concerns for the insertr system
|
||||||
|
*
|
||||||
|
* Architecture:
|
||||||
|
* - Pure presentation layer - no business logic
|
||||||
|
* - Event-driven communication with core modules
|
||||||
|
* - Unified control panel design
|
||||||
|
* - Responsive and accessible
|
||||||
|
*/
|
||||||
|
export class InsertrControlPanel {
|
||||||
|
constructor(auth, editor, apiClient, options = {}) {
|
||||||
|
this.auth = auth;
|
||||||
|
this.editor = editor;
|
||||||
|
this.apiClient = apiClient;
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
// UI state
|
||||||
|
this.elements = {};
|
||||||
|
this.isInitialized = false;
|
||||||
|
|
||||||
|
// Bind methods for event listeners
|
||||||
|
this.handleAuthToggle = this.handleAuthToggle.bind(this);
|
||||||
|
this.handleEditToggle = this.handleEditToggle.bind(this);
|
||||||
|
this.handleEnhanceClick = this.handleEnhanceClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the control panel (called after DOM is ready)
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
if (this.isInitialized) return;
|
||||||
|
|
||||||
|
console.log('🎨 Initializing Insertr Control Panel');
|
||||||
|
this.createControlPanel();
|
||||||
|
this.setupEventListeners();
|
||||||
|
this.updateVisualState();
|
||||||
|
this.isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the unified control panel structure
|
||||||
|
*/
|
||||||
|
createControlPanel() {
|
||||||
|
// Check if control panel already exists
|
||||||
|
if (document.getElementById('insertr-control-panel')) {
|
||||||
|
this.cacheElements();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const controlPanelHtml = `
|
||||||
|
<div id="insertr-control-panel" class="insertr-control-panel">
|
||||||
|
<!-- Status Indicator Section -->
|
||||||
|
<div id="insertr-status-section" class="insertr-status-section">
|
||||||
|
<div id="insertr-status-indicator" class="insertr-status-indicator">
|
||||||
|
<span class="insertr-status-text">Visitor Mode</span>
|
||||||
|
<span class="insertr-status-dot insertr-status-visitor"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Buttons Section -->
|
||||||
|
<div id="insertr-action-section" class="insertr-action-section">
|
||||||
|
<button id="insertr-enhance-btn" class="insertr-action-btn insertr-enhance-btn"
|
||||||
|
style="display: none;"
|
||||||
|
title="Enhance files with latest content">
|
||||||
|
🔄 Enhance
|
||||||
|
</button>
|
||||||
|
<button id="insertr-edit-toggle" class="insertr-action-btn insertr-edit-btn"
|
||||||
|
style="display: none;">
|
||||||
|
Edit Mode: Off
|
||||||
|
</button>
|
||||||
|
<button id="insertr-auth-toggle" class="insertr-action-btn insertr-auth-btn">
|
||||||
|
Login as Client
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add control panel to page
|
||||||
|
document.body.insertAdjacentHTML('beforeend', controlPanelHtml);
|
||||||
|
this.cacheElements();
|
||||||
|
|
||||||
|
console.log('📱 Control panel created');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache DOM elements for performance
|
||||||
|
*/
|
||||||
|
cacheElements() {
|
||||||
|
this.elements = {
|
||||||
|
controlPanel: document.getElementById('insertr-control-panel'),
|
||||||
|
statusSection: document.getElementById('insertr-status-section'),
|
||||||
|
statusIndicator: document.getElementById('insertr-status-indicator'),
|
||||||
|
statusText: document.querySelector('.insertr-status-text'),
|
||||||
|
statusDot: document.querySelector('.insertr-status-dot'),
|
||||||
|
actionSection: document.getElementById('insertr-action-section'),
|
||||||
|
authToggle: document.getElementById('insertr-auth-toggle'),
|
||||||
|
editToggle: document.getElementById('insertr-edit-toggle'),
|
||||||
|
enhanceBtn: document.getElementById('insertr-enhance-btn')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup event listeners for all UI interactions
|
||||||
|
*/
|
||||||
|
setupEventListeners() {
|
||||||
|
if (this.elements.authToggle) {
|
||||||
|
this.elements.authToggle.addEventListener('click', this.handleAuthToggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.elements.editToggle) {
|
||||||
|
this.elements.editToggle.addEventListener('click', this.handleEditToggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.elements.enhanceBtn) {
|
||||||
|
this.elements.enhanceBtn.addEventListener('click', this.handleEnhanceClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for auth state changes
|
||||||
|
if (this.auth && typeof this.auth.on === 'function') {
|
||||||
|
this.auth.on('stateChange', () => this.updateVisualState());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for editor state changes
|
||||||
|
if (this.editor && typeof this.editor.on === 'function') {
|
||||||
|
this.editor.on('modeChange', () => this.updateVisualState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle authentication toggle click
|
||||||
|
*/
|
||||||
|
handleAuthToggle() {
|
||||||
|
if (this.auth && typeof this.auth.toggleAuthentication === 'function') {
|
||||||
|
this.auth.toggleAuthentication();
|
||||||
|
this.updateVisualState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle edit mode toggle click
|
||||||
|
*/
|
||||||
|
handleEditToggle() {
|
||||||
|
if (this.auth && typeof this.auth.toggleEditMode === 'function') {
|
||||||
|
this.auth.toggleEditMode();
|
||||||
|
this.updateVisualState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle enhance button click
|
||||||
|
*/
|
||||||
|
async handleEnhanceClick() {
|
||||||
|
if (!this.elements.enhanceBtn) return;
|
||||||
|
|
||||||
|
const enhanceBtn = this.elements.enhanceBtn;
|
||||||
|
const siteId = window.insertrConfig?.siteId || this.options.siteId || 'demo';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Show loading state
|
||||||
|
enhanceBtn.textContent = '⏳ Enhancing...';
|
||||||
|
enhanceBtn.disabled = true;
|
||||||
|
|
||||||
|
// Smart server detection for enhance API
|
||||||
|
const isDevelopment = window.location.hostname === 'localhost' ||
|
||||||
|
window.location.hostname === '127.0.0.1';
|
||||||
|
const enhanceUrl = isDevelopment
|
||||||
|
? `http://localhost:8080/api/enhance?site_id=${siteId}`
|
||||||
|
: `/api/enhance?site_id=${siteId}`;
|
||||||
|
|
||||||
|
// Call enhance API
|
||||||
|
const response = await fetch(enhanceUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${this.auth?.getCurrentUser()?.token || 'mock-token'}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Enhancement failed: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
console.log('✅ Files enhanced successfully:', result);
|
||||||
|
|
||||||
|
// Show success state briefly
|
||||||
|
enhanceBtn.textContent = '✅ Enhanced!';
|
||||||
|
|
||||||
|
// Reset button after success
|
||||||
|
setTimeout(() => {
|
||||||
|
enhanceBtn.textContent = '🔄 Enhance';
|
||||||
|
enhanceBtn.disabled = false;
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Enhancement failed:', error);
|
||||||
|
enhanceBtn.textContent = '❌ Failed';
|
||||||
|
|
||||||
|
// Reset button after error
|
||||||
|
setTimeout(() => {
|
||||||
|
enhanceBtn.textContent = '🔄 Enhance';
|
||||||
|
enhanceBtn.disabled = false;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all visual elements based on current state
|
||||||
|
*/
|
||||||
|
updateVisualState() {
|
||||||
|
if (!this.isInitialized || !this.elements.statusText) return;
|
||||||
|
|
||||||
|
const isAuthenticated = this.auth ? this.auth.isAuthenticated() : false;
|
||||||
|
const isEditMode = this.auth ? this.auth.isEditMode() : false;
|
||||||
|
|
||||||
|
// Update status indicator
|
||||||
|
this.updateStatusIndicator(isAuthenticated, isEditMode);
|
||||||
|
|
||||||
|
// Update button states
|
||||||
|
this.updateButtonStates(isAuthenticated, isEditMode);
|
||||||
|
|
||||||
|
// Update body classes for global styling
|
||||||
|
this.updateBodyClasses(isAuthenticated, isEditMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update status indicator text and visual state
|
||||||
|
*/
|
||||||
|
updateStatusIndicator(isAuthenticated, isEditMode) {
|
||||||
|
const { statusText, statusDot } = this.elements;
|
||||||
|
|
||||||
|
if (!statusText || !statusDot) return;
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
statusText.textContent = 'Visitor Mode';
|
||||||
|
statusDot.className = 'insertr-status-dot insertr-status-visitor';
|
||||||
|
} else if (isEditMode) {
|
||||||
|
statusText.textContent = 'Editing';
|
||||||
|
statusDot.className = 'insertr-status-dot insertr-status-editing';
|
||||||
|
} else {
|
||||||
|
statusText.textContent = 'Authenticated';
|
||||||
|
statusDot.className = 'insertr-status-dot insertr-status-authenticated';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update button text, visibility, and states
|
||||||
|
*/
|
||||||
|
updateButtonStates(isAuthenticated, isEditMode) {
|
||||||
|
const { authToggle, editToggle, enhanceBtn } = this.elements;
|
||||||
|
|
||||||
|
// Update auth toggle button
|
||||||
|
if (authToggle) {
|
||||||
|
authToggle.textContent = isAuthenticated ? 'Logout' : 'Login as Client';
|
||||||
|
authToggle.className = `insertr-action-btn insertr-auth-btn ${
|
||||||
|
isAuthenticated ? 'insertr-authenticated' : ''
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update edit mode toggle button
|
||||||
|
if (editToggle) {
|
||||||
|
editToggle.style.display = isAuthenticated ? 'inline-block' : 'none';
|
||||||
|
editToggle.textContent = `Edit Mode: ${isEditMode ? 'On' : 'Off'}`;
|
||||||
|
editToggle.className = `insertr-action-btn insertr-edit-btn ${
|
||||||
|
isEditMode ? 'insertr-edit-active' : ''
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update enhance button (dev-mode only)
|
||||||
|
if (enhanceBtn) {
|
||||||
|
enhanceBtn.style.display = isAuthenticated ? 'inline-block' : 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update body CSS classes for global styling hooks
|
||||||
|
*/
|
||||||
|
updateBodyClasses(isAuthenticated, isEditMode) {
|
||||||
|
document.body.classList.toggle('insertr-authenticated', isAuthenticated);
|
||||||
|
document.body.classList.toggle('insertr-edit-mode', isEditMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add editing indicators to content elements
|
||||||
|
*/
|
||||||
|
addEditingIndicators() {
|
||||||
|
if (!this.editor || !this.editor.core) return;
|
||||||
|
|
||||||
|
const elements = this.editor.core.getAllElements();
|
||||||
|
console.log(`🎨 Adding editing indicators to ${elements.length} elements`);
|
||||||
|
|
||||||
|
elements.forEach(meta => {
|
||||||
|
this.initializeElementVisuals(meta.element);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize visual editing indicators for a single element
|
||||||
|
*/
|
||||||
|
initializeElementVisuals(element) {
|
||||||
|
// Add visual indicators
|
||||||
|
element.style.cursor = 'pointer';
|
||||||
|
element.style.position = 'relative';
|
||||||
|
|
||||||
|
// Add hover effects
|
||||||
|
element.addEventListener('mouseenter', () => {
|
||||||
|
if (this.auth && this.auth.isAuthenticated() && this.auth.isEditMode()) {
|
||||||
|
element.classList.add('insertr-editing-hover');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
element.addEventListener('mouseleave', () => {
|
||||||
|
element.classList.remove('insertr-editing-hover');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove editing indicators (when edit mode is disabled)
|
||||||
|
*/
|
||||||
|
removeEditingIndicators() {
|
||||||
|
const elements = document.querySelectorAll('.insertr-editing-hover');
|
||||||
|
elements.forEach(element => {
|
||||||
|
element.classList.remove('insertr-editing-hover');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show status message to user
|
||||||
|
*/
|
||||||
|
showStatusMessage(message, type = 'info', duration = 3000) {
|
||||||
|
// Remove existing status message
|
||||||
|
const existing = document.getElementById('insertr-status-message');
|
||||||
|
if (existing) {
|
||||||
|
existing.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageHtml = `
|
||||||
|
<div id="insertr-status-message" class="insertr-status-message ${type}">
|
||||||
|
<p class="insertr-status-text">${message}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.insertAdjacentHTML('beforeend', messageHtml);
|
||||||
|
|
||||||
|
// Auto-remove after duration
|
||||||
|
setTimeout(() => {
|
||||||
|
const messageEl = document.getElementById('insertr-status-message');
|
||||||
|
if (messageEl) {
|
||||||
|
messageEl.remove();
|
||||||
|
}
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the control panel (cleanup)
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
if (this.elements.controlPanel) {
|
||||||
|
this.elements.controlPanel.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.elements = {};
|
||||||
|
this.isInitialized = false;
|
||||||
|
|
||||||
|
// Remove body classes
|
||||||
|
document.body.classList.remove('insertr-authenticated', 'insertr-edit-mode');
|
||||||
|
|
||||||
|
console.log('🗑️ Control panel destroyed');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* Editor - Handles all content types with markdown-first approach
|
* Editor - Handles all content types with markdown-first approach
|
||||||
*/
|
*/
|
||||||
import { markdownConverter } from '../utils/markdown.js';
|
import { markdownConverter } from '../utils/markdown.js';
|
||||||
import { Previewer } from './Previewer.js';
|
import { Previewer } from './previewer.js';
|
||||||
|
|
||||||
export class Editor {
|
export class Editor {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* InsertrFormRenderer - Form renderer using markdown-first approach
|
* InsertrFormRenderer - Form renderer using markdown-first approach
|
||||||
* Thin wrapper around the Editor system
|
* Thin wrapper around the Editor system
|
||||||
*/
|
*/
|
||||||
import { Editor } from './Editor.js';
|
import { Editor } from './editor.js';
|
||||||
|
|
||||||
export class InsertrFormRenderer {
|
export class InsertrFormRenderer {
|
||||||
constructor(apiClient = null) {
|
constructor(apiClient = null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user