@package-uploader/ui 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- :root{--color-primary: #2563eb;--color-primary-hover: #1d4ed8;--color-success: #10b981;--color-error: #ef4444;--color-warning: #f59e0b;--color-bg: #ffffff;--color-bg-secondary: #f9fafb;--color-border: #e5e7eb;--color-text: #111827;--color-text-muted: #6b7280;--radius: 8px;--radius-sm: 4px;--shadow: 0 1px 3px rgba(0, 0, 0, .1);--shadow-lg: 0 4px 6px rgba(0, 0, 0, .1)}*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;background:var(--color-bg-secondary);color:var(--color-text);line-height:1.5}.app{min-height:100vh;display:flex;flex-direction:column}.header{background:var(--color-bg);border-bottom:1px solid var(--color-border);padding:1rem 2rem;display:flex;align-items:center;justify-content:space-between}.header h1{font-size:1.25rem;font-weight:600}.nav{display:flex;gap:1rem}.nav a{color:var(--color-text-muted);text-decoration:none;padding:.5rem 1rem;border-radius:var(--radius);transition:all .2s}.nav a:hover,.nav a.active{color:var(--color-primary);background:#2563eb1a}.main{flex:1;padding:2rem;max-width:1200px;margin:0 auto;width:100%}.card{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1.5rem;box-shadow:var(--shadow)}.card+.card{margin-top:1rem}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.card-title{font-size:1.125rem;font-weight:600}.btn{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;border-radius:var(--radius);border:none;font-size:.875rem;font-weight:500;cursor:pointer;transition:all .2s}.btn-primary{background:var(--color-primary);color:#fff}.btn-primary:hover{background:var(--color-primary-hover)}.btn-secondary{background:var(--color-bg);border:1px solid var(--color-border);color:var(--color-text)}.btn-secondary:hover{background:var(--color-bg-secondary)}.btn:disabled{opacity:.5;cursor:not-allowed}.dropzone{border:2px dashed var(--color-border);border-radius:var(--radius);padding:3rem;text-align:center;cursor:pointer;transition:all .2s}.dropzone:hover,.dropzone.active{border-color:var(--color-primary);background:#2563eb0d}.dropzone p{color:var(--color-text-muted);margin-top:.5rem}.file-list{margin-top:1rem}.file-item{display:flex;align-items:center;justify-content:space-between;padding:.75rem;border:1px solid var(--color-border);border-radius:var(--radius);margin-bottom:.5rem}.file-item .name{font-weight:500}.file-item .size{color:var(--color-text-muted);font-size:.875rem}.file-item .status{display:flex;align-items:center;gap:.5rem}.status-pending{color:var(--color-text-muted)}.status-uploading{color:var(--color-primary)}.status-success{color:var(--color-success)}.status-error{color:var(--color-error)}.document-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem}.document-card{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;transition:all .2s}.document-card:hover{box-shadow:var(--shadow-lg)}.document-card .title{font-weight:600;margin-bottom:.25rem}.document-card .meta{color:var(--color-text-muted);font-size:.875rem;margin-bottom:.75rem}.document-card .actions{display:flex;gap:.5rem}.folder-tree{border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;max-height:400px;overflow-y:auto}.folder-item{padding:.5rem;cursor:pointer;border-radius:var(--radius);display:flex;align-items:center;gap:.5rem}.folder-item:hover{background:var(--color-bg-secondary)}.folder-item.selected{background:#2563eb1a;color:var(--color-primary)}.folder-item .icon{font-size:1.25rem}.modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.modal{background:var(--color-bg);border-radius:var(--radius);padding:1.5rem;max-width:500px;width:90%;max-height:80vh;overflow-y:auto}.modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.modal-title{font-size:1.125rem;font-weight:600}.modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:var(--color-text-muted)}.form-group{margin-bottom:1rem}.form-label{display:block;font-weight:500;margin-bottom:.25rem}.form-input,.form-select{width:100%;padding:.5rem;border:1px solid var(--color-border);border-radius:var(--radius);font-size:1rem}.form-input:focus,.form-select:focus{outline:none;border-color:var(--color-primary)}.spinner{width:20px;height:20px;border:2px solid var(--color-border);border-top-color:var(--color-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:3rem;color:var(--color-text-muted)}.empty-state h3{color:var(--color-text);margin-bottom:.5rem}.alert{padding:1rem;border-radius:var(--radius);margin-bottom:1rem}.alert-success{background:#10b9811a;border:1px solid var(--color-success);color:var(--color-success)}.alert-error{background:#ef44441a;border:1px solid var(--color-error);color:var(--color-error)}.alert-warning{background:#f59e0b1a;border:1px solid var(--color-warning);color:var(--color-warning)}.breadcrumb{display:flex;align-items:center;flex-wrap:wrap;gap:0;font-size:.875rem}.breadcrumb-item{background:none;border:none;color:var(--color-primary);cursor:pointer;padding:.25rem .5rem;border-radius:var(--radius);font-size:inherit}.breadcrumb-item:hover{background:#2563eb1a}.breadcrumb-current{color:var(--color-text);cursor:default}.breadcrumb-current:hover{background:transparent}.breadcrumb-separator{color:var(--color-text-muted);margin:0 .25rem}.browse-page{display:flex;flex-direction:column;gap:1rem}.toolbar{display:flex;justify-content:space-between;align-items:center;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:.75rem 1rem;gap:1rem;flex-wrap:wrap}.toolbar-left{display:flex;align-items:center;gap:.5rem;flex:1;min-width:200px}.toolbar-right{display:flex;align-items:center;gap:.5rem}.search-input{padding:.5rem .75rem;border:1px solid var(--color-border);border-radius:var(--radius);font-size:.875rem;width:200px}.search-input:focus{outline:none;border-color:var(--color-primary)}.content-area{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);overflow:hidden}.data-grid-container{display:flex;flex-direction:column}.data-grid{width:100%;border-collapse:collapse;font-size:.875rem}.data-grid thead{background:var(--color-bg-secondary);border-bottom:1px solid var(--color-border)}.data-grid th{text-align:left;padding:.75rem 1rem;font-weight:600;color:var(--color-text-muted);white-space:nowrap}.data-grid th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.data-grid th.sortable:hover{color:var(--color-text)}.data-grid td{padding:.75rem 1rem;border-bottom:1px solid var(--color-border);vertical-align:middle}.grid-row{transition:background .15s}.grid-row:hover{background:var(--color-bg-secondary)}.folder-row{cursor:pointer}.folder-row:hover{background:#2563eb0d}.col-checkbox{width:40px;text-align:center}.col-name{min-width:250px}.col-format,.col-type{width:120px}.col-updated,.col-created{width:140px}.col-actions{width:100px;text-align:right}.item-icon{margin-right:.5rem;font-size:1rem}.item-name{font-weight:500}.subfolder-count{color:var(--color-text-muted);font-size:.75rem;margin-left:.5rem}.btn-icon{background:none;border:none;padding:.25rem .5rem;cursor:pointer;font-size:1rem;opacity:.6;transition:opacity .15s}.btn-icon:hover{opacity:1}.btn-icon:disabled{opacity:.3;cursor:not-allowed}.btn-icon.pinned,.btn-delete:hover{opacity:1}.data-grid-footer{padding:.75rem 1rem;background:var(--color-bg-secondary);border-top:1px solid var(--color-border);font-size:.75rem;color:var(--color-text-muted)}.data-grid-loading,.data-grid-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:3rem;text-align:center;color:var(--color-text-muted);gap:.5rem}.data-grid-empty .empty-icon{font-size:3rem;margin-bottom:.5rem}.data-grid-empty h3{color:var(--color-text);margin:0}.data-grid-empty p{margin:.5rem 0 1rem}.folder-browser{border:1px solid var(--color-border);border-radius:var(--radius);overflow:hidden}.folder-browser-toolbar{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;background:var(--color-bg-secondary);border-bottom:1px solid var(--color-border)}.folder-browser-actions{display:flex;gap:.5rem}.folder-browser-list{max-height:300px;overflow-y:auto}.folder-browser-table{width:100%;border-collapse:collapse}.folder-browser-row{transition:background .15s}.folder-browser-row:hover{background:var(--color-bg-secondary)}.folder-browser-row.selected{background:#2563eb1a}.folder-browser-row td{padding:.5rem;border-bottom:1px solid var(--color-border);vertical-align:middle}.folder-browser-icon{width:40px;text-align:center;font-size:1.25rem}.folder-browser-name{cursor:pointer;font-weight:500}.folder-browser-name:hover{color:var(--color-primary)}.subfolder-indicator{color:var(--color-text-muted);font-size:.75rem;font-weight:400;margin-left:.5rem}.folder-browser-select{width:100px;text-align:center}.folder-browser-delete{width:50px;text-align:center}.folder-browser-loading,.folder-browser-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;color:var(--color-text-muted);gap:.75rem}.upload-page{display:flex;flex-direction:column;gap:1rem}.selected-folder-badge{background:#2563eb1a;color:var(--color-primary);padding:.25rem .75rem;border-radius:999px;font-size:.75rem;font-weight:500}.upload-actions{margin-top:1rem;display:flex;gap:.5rem;justify-content:flex-end}.btn-sm{padding:.25rem .5rem;font-size:.75rem}.document-row{cursor:pointer;transition:background .15s}.document-row:hover{background:var(--color-bg-secondary)}.document-row.expanded{background:#2563eb0d}.expand-icon{display:inline-block;width:1rem;margin-right:.25rem;transition:transform .2s;color:var(--color-text-muted)}.expand-icon.expanded{transform:rotate(90deg)}.expanded-row td{padding:0!important;background:var(--color-bg-secondary);border-bottom:2px solid var(--color-border)}.course-detail-panel{padding:1rem;display:flex;flex-direction:column;gap:1rem}.course-detail-loading,.course-detail-error{display:flex;align-items:center;justify-content:center;gap:.75rem;padding:2rem;color:var(--color-text-muted)}.course-detail-error{color:var(--color-error);flex-direction:column}.course-detail-tile{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem}.tile-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem;padding-bottom:.75rem;border-bottom:1px solid var(--color-border)}.tile-header h3{font-size:1rem;font-weight:600;margin:0}.btn-close{background:none;border:none;font-size:1.25rem;color:var(--color-text-muted);cursor:pointer;padding:.25rem;line-height:1;border-radius:var(--radius);transition:all .15s}.btn-close:hover{background:var(--color-bg-secondary);color:var(--color-text)}.detail-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:.75rem 1.5rem}.detail-item{display:flex;flex-direction:column;gap:.125rem}.detail-item.span-2{grid-column:span 2}.detail-label{font-size:.7rem;font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.025em}.detail-value{font-size:.875rem;color:var(--color-text)}.detail-value.mono{font-family:SF Mono,Monaco,Consolas,monospace;font-size:.75rem;background:var(--color-bg-secondary);padding:.125rem .375rem;border-radius:4px}.detail-value.description{line-height:1.4;max-height:4.2em;overflow:hidden;text-overflow:ellipsis}.thin-pack-row{display:grid;grid-template-columns:repeat(2,1fr);gap:1rem}.thin-pack-tile{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;border-left:4px solid}.thin-pack-tile.public{border-left-color:var(--color-success)}.thin-pack-tile.private{border-left-color:var(--color-primary)}.thin-pack-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:.75rem}.thin-pack-icon{font-size:1.5rem}.thin-pack-titles{flex:1}.thin-pack-title{font-size:.9rem;font-weight:600;margin:0}.thin-pack-subtitle{font-size:.75rem;color:var(--color-text-muted);margin:.125rem 0 0}.thin-pack-content{display:flex;flex-direction:column;gap:.5rem}.thin-pack-error{font-size:.75rem;color:var(--color-error);padding:.5rem;background:#ef44441a;border-radius:4px}.thin-pack-status{display:flex;align-items:center;gap:.5rem;font-size:.8rem}.thin-pack-status.exists{color:var(--color-success)}.thin-pack-status.exists .status-icon{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;background:var(--color-success);color:#fff;border-radius:50%;font-size:.7rem}.thin-pack-status.missing{color:var(--color-text-muted);font-style:italic}.thin-pack-meta{display:flex;gap:1rem;font-size:.7rem;color:var(--color-text-muted)}.thin-pack-actions{display:flex;gap:.5rem;margin-top:.25rem}.thin-pack-slug-input{display:flex;flex-direction:column;gap:.25rem}.thin-pack-slug-input label{font-size:.7rem;color:var(--color-text-muted)}.thin-pack-slug-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.thin-pack-slug-input input:focus{outline:none;border-color:var(--color-primary)}.thin-pack-slug-input input::placeholder{color:var(--color-text-muted);opacity:.6}.thin-pack-name-input{display:flex;flex-direction:column;gap:.25rem}.thin-pack-name-input label{font-size:.7rem;color:var(--color-text-muted)}.thin-pack-name-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.thin-pack-name-input input:focus{outline:none;border-color:var(--color-primary)}.thin-pack-name-input input::placeholder{color:var(--color-text-muted);opacity:.6}.token-preview{font-size:.65rem;color:var(--color-text-muted);font-family:monospace;word-break:break-all}.thin-pack-visibility{display:flex;flex-direction:column;gap:.25rem;padding:.5rem;background:var(--color-bg-tertiary);border-radius:4px}.visibility-toggle{display:flex;align-items:center;gap:.5rem;cursor:pointer}.visibility-toggle input[type=checkbox]{width:16px;height:16px;cursor:pointer}.toggle-label{font-size:.85rem;font-weight:500}.visibility-hint{font-size:.7rem;color:var(--color-text-muted);margin-left:24px}.thin-pack-tile.lms{border-left-color:var(--color-success)}.thin-pack-tile.shared{border-left-color:var(--color-primary)}.upload-modal{width:600px;max-width:90vw;max-height:90vh;display:flex;flex-direction:column}.upload-modal-content{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.upload-modal .dropzone{min-height:120px}.upload-file-list{display:flex;flex-direction:column;gap:.5rem}.upload-file-list h4{margin:0;font-size:.85rem;color:var(--color-text-muted)}.upload-file-item{display:flex;justify-content:space-between;align-items:center;padding:.5rem;background:var(--color-bg-secondary);border-radius:8px}.upload-file-info{display:flex;flex-direction:column;gap:.125rem}.upload-file-name{font-weight:500;font-size:.875rem}.upload-file-size{font-size:.75rem;color:var(--color-text-muted)}.upload-file-status{display:flex;align-items:center;gap:.5rem}.upload-progress-container{display:flex;align-items:center;gap:.75rem;min-width:150px}.upload-progress-bar{flex:1;height:8px;background:var(--color-border);border-radius:4px;overflow:hidden}.upload-progress-fill{height:100%;background:var(--color-primary);transition:width .15s ease-out}.upload-progress-text{font-size:.75rem;font-weight:500;color:var(--color-text-muted);min-width:36px;text-align:right}.status-processing{color:var(--color-primary);font-size:.875rem}.upload-link-options{background:var(--color-bg-secondary);border-radius:var(--radius);padding:1rem;display:flex;flex-direction:column;gap:.75rem}.upload-link-options h4{margin:0;font-size:.85rem;color:var(--color-text-muted)}.upload-link-option{display:flex;flex-direction:column;gap:.5rem}.upload-link-toggle{display:flex;align-items:center;gap:.5rem;cursor:pointer}.upload-link-toggle input[type=checkbox]{width:1rem;height:1rem;cursor:pointer}.upload-link-toggle span{font-size:.875rem}.upload-link-name-input{display:flex;flex-direction:column;gap:.25rem;padding-left:1.5rem}.upload-link-name-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.upload-link-name-input input:focus{outline:none;border-color:var(--color-primary)}.upload-link-name-input input::placeholder{color:var(--color-text-muted);opacity:.6}.slug-preview{font-size:.7rem;color:var(--color-text-muted);font-family:monospace}.modal-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:1rem;border-top:1px solid var(--color-border)}@media (max-width: 768px){.detail-grid{grid-template-columns:1fr}.detail-item.span-2{grid-column:span 1}.thin-pack-row{grid-template-columns:1fr}}.user-menu{display:flex;align-items:center;gap:.75rem;margin-left:auto}.user-info{display:flex;align-items:center;gap:.5rem}.user-avatar{width:2rem;height:2rem;border-radius:50%;background:var(--color-primary);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem}.user-name{font-size:.875rem;color:var(--color-text);max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.header{display:flex;align-items:center;gap:1.5rem}.header .nav{margin-right:auto}.course-structure-step{display:flex;flex-direction:column;gap:.5rem}.course-structure-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.25rem}.course-structure-header h4{margin:0;font-size:1rem;font-weight:600}.course-structure-meta{display:flex;align-items:center;gap:.5rem;font-size:.75rem;color:var(--color-text-muted)}.course-structure-badge{background:#2563eb1a;color:var(--color-primary);padding:.125rem .5rem;border-radius:999px;font-size:.7rem;font-weight:500}.tree-node{padding:.25rem 0}.tree-node-row{display:flex;align-items:center;gap:.375rem;min-height:1.5rem}.tree-node-icon{font-size:1rem;flex-shrink:0}.tree-node-label{font-weight:500;font-size:.875rem;flex:1}.tree-class-input{width:100%;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text);margin-top:.25rem}.tree-class-input:focus{outline:none;border-color:var(--color-primary)}.tree-class-input::placeholder{color:var(--color-text-muted);opacity:.6}.tree-class-input-sm{max-width:200px;margin-top:0;margin-left:auto;flex-shrink:0}.tree-course{background:var(--color-bg-secondary);border:1px solid var(--color-border);border-radius:var(--radius);padding:.5rem .75rem}.tree-lessons{display:flex;flex-direction:column;gap:.125rem;max-height:400px;overflow-y:auto;border:1px solid var(--color-border);border-radius:var(--radius);padding:.25rem}.tree-lesson{border-bottom:1px solid var(--color-border)}.tree-lesson:last-child{border-bottom:none}.tree-node-lesson{cursor:pointer;padding:.375rem .5rem;border-radius:var(--radius-sm);transition:background .15s}.tree-node-lesson:hover{background:var(--color-bg-secondary)}.tree-expand{font-size:.625rem;width:1rem;text-align:center;color:var(--color-text-muted);flex-shrink:0}.tree-lesson-num{color:var(--color-text-muted);font-weight:600;font-size:.75rem}.tree-node-indicator{font-size:.65rem;color:var(--color-primary);background:#2563eb1a;padding:.0625rem .375rem;border-radius:999px;white-space:nowrap;flex-shrink:0}.tree-lesson-content{padding:.25rem .5rem .5rem 1.5rem}.tree-lesson-class{margin-bottom:.375rem}.tree-node-block{display:flex;align-items:center;gap:.375rem;padding:.1875rem 0}.tree-node-block .tree-node-row{flex:1;min-width:0}.tree-block-pipe{font-family:monospace;color:var(--color-border);font-size:.75rem;flex-shrink:0}.tree-block-type{font-size:.75rem;font-weight:500;color:var(--color-text);white-space:nowrap}.tree-block-title{font-size:.7rem;color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px}.structure-parsing-indicator{display:flex;align-items:center;gap:.75rem;padding:.75rem;color:var(--color-primary);font-size:.875rem}.structure-detected-badge{background:#10b9811a;color:var(--color-success);border:1px solid var(--color-success);border-radius:var(--radius);padding:.5rem .75rem;font-size:.8rem}.structure-multi-note{font-size:.75rem;color:var(--color-text-muted);font-style:italic;padding:.25rem 0}.structure-summary-badge{background:#2563eb1a;color:var(--color-primary);border-radius:var(--radius);padding:.5rem .75rem;font-size:.8rem;margin-top:.25rem}.structure-section{display:flex;flex-direction:column}.structure-toggle-btn{display:flex;align-items:center;gap:.5rem;width:100%;padding:.625rem .75rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);cursor:pointer;font-size:.875rem;font-weight:500;color:var(--color-text);transition:all .2s}.structure-toggle-btn:hover:not(:disabled){border-color:var(--color-primary);background:#2563eb08}.structure-toggle-btn.open{border-color:var(--color-primary);border-bottom-left-radius:0;border-bottom-right-radius:0;background:#2563eb0d}.structure-toggle-btn.disabled{opacity:.5;cursor:not-allowed;color:var(--color-text-muted)}.structure-toggle-btn .spinner{width:16px;height:16px;flex-shrink:0}.structure-toggle-arrow{margin-left:auto;font-size:.625rem;color:var(--color-text-muted);transition:transform .2s}.structure-panel{border:1px solid var(--color-primary);border-top:none;border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius);padding:.75rem;background:var(--color-bg);animation:panelSlideDown .15s ease-out}@keyframes panelSlideDown{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.bgp-toggle-btn{margin-top:.5rem;display:inline-flex;align-items:center;gap:.375rem;background:var(--color-bg);border:1px solid var(--color-border);color:var(--color-text-muted);transition:all .15s}.bgp-toggle-btn:hover{border-color:var(--color-primary);color:var(--color-primary)}.bgp-toggle-btn.active{border-color:var(--color-primary);color:var(--color-primary);background:#2563eb0d}.bgp-toggle-count{background:var(--color-primary);color:#fff;font-size:.625rem;padding:.0625rem .375rem;border-radius:999px;font-weight:600}.block-grouping-panel{margin-top:.5rem;border:1px solid var(--color-border);border-radius:var(--radius);background:var(--color-bg);overflow:hidden}.bgp-header{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:var(--color-bg-secondary);border-bottom:1px solid var(--color-border)}.bgp-title{font-size:.8rem;font-weight:600;color:var(--color-text)}.bgp-count{font-size:.7rem;color:var(--color-primary);background:#2563eb1a;padding:.0625rem .375rem;border-radius:999px}.bgp-preview{padding:.5rem;display:flex;flex-direction:column;gap:.25rem;max-height:300px;overflow-y:auto}.bgp-block{display:flex;align-items:center;gap:.375rem;padding:.375rem .5rem;border-radius:var(--radius-sm);cursor:pointer;transition:all .15s;border:1px solid transparent;font-size:.75rem}.bgp-block:hover:not(.grouped){background:var(--color-bg-secondary);border-color:var(--color-border)}.bgp-block.selected{background:#2563eb14;border-color:var(--color-primary)}.bgp-block.grouped{cursor:default;opacity:.7}.bgp-block-color{width:4px;height:1.25rem;border-radius:2px;background:var(--block-color, #6b7280);flex-shrink:0}.bgp-block-label{font-weight:500;color:var(--color-text);white-space:nowrap}.bgp-block-title{color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.bgp-block-check{color:var(--color-primary);font-weight:600;flex-shrink:0;margin-left:auto}.bgp-group-wrapper{border:2px dashed var(--color-primary);border-radius:var(--radius);padding:.25rem;margin:.25rem 0;background:#2563eb05}.bgp-group-header{display:flex;align-items:center;gap:.5rem;padding:.25rem .5rem;font-size:.7rem;border-bottom:1px solid rgba(37,99,235,.15);margin-bottom:.25rem}.bgp-group-label{font-weight:600;color:var(--color-primary)}.bgp-group-class{color:var(--color-text-muted);font-family:monospace;font-size:.65rem}.bgp-btn{background:none;border:none;font-size:.65rem;cursor:pointer;padding:.125rem .375rem;border-radius:var(--radius-sm);transition:all .15s}.bgp-btn-edit{color:var(--color-text-muted);margin-left:auto}.bgp-btn-edit:hover{color:var(--color-primary);background:#2563eb1a}.bgp-btn-ungroup{color:var(--color-text-muted)}.bgp-btn-ungroup:hover{color:var(--color-error);background:#ef44441a}.bgp-btn-save{color:var(--color-success)}.bgp-btn-save:hover{background:#10b9811a}.bgp-btn-cancel{color:var(--color-text-muted)}.bgp-btn-cancel:hover{background:var(--color-bg-secondary)}.bgp-group-edit{display:flex;align-items:center;gap:.375rem;flex:1}.bgp-edit-input{padding:.1875rem .375rem;font-size:.7rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text);flex:1}.bgp-edit-input:focus{outline:none;border-color:var(--color-primary)}.bgp-edit-input-sm{max-width:120px}.bgp-create{padding:.5rem .75rem;border-top:1px solid var(--color-border);background:var(--color-bg-secondary);display:flex;flex-direction:column;gap:.375rem}.bgp-create-info{font-size:.75rem;font-weight:500;color:var(--color-text)}.bgp-validation-error{color:var(--color-error);font-weight:400}.bgp-create-inputs{display:flex;gap:.375rem}.bgp-create-class{flex:2;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.bgp-create-class:focus{outline:none;border-color:var(--color-primary)}.bgp-create-label{flex:1;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.bgp-create-label:focus{outline:none;border-color:var(--color-primary)}.bgp-create-actions{display:flex;gap:.375rem}.bgp-hint{padding:.75rem;font-size:.75rem;color:var(--color-text-muted);text-align:center;font-style:italic}
1
+ :root{--color-primary: #2563eb;--color-primary-hover: #1d4ed8;--color-success: #10b981;--color-error: #ef4444;--color-warning: #f59e0b;--color-bg: #ffffff;--color-bg-secondary: #f9fafb;--color-border: #e5e7eb;--color-text: #111827;--color-text-muted: #6b7280;--radius: 8px;--radius-sm: 4px;--shadow: 0 1px 3px rgba(0, 0, 0, .1);--shadow-lg: 0 4px 6px rgba(0, 0, 0, .1)}*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;background:var(--color-bg-secondary);color:var(--color-text);line-height:1.5}.app{min-height:100vh;display:flex;flex-direction:column}.header{background:var(--color-bg);border-bottom:1px solid var(--color-border);padding:1rem 2rem;display:flex;align-items:center;justify-content:space-between}.header h1{font-size:1.25rem;font-weight:600}.nav{display:flex;gap:1rem}.nav a{color:var(--color-text-muted);text-decoration:none;padding:.5rem 1rem;border-radius:var(--radius);transition:all .2s}.nav a:hover,.nav a.active{color:var(--color-primary);background:#2563eb1a}.main{flex:1;padding:2rem;max-width:1200px;margin:0 auto;width:100%}.card{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1.5rem;box-shadow:var(--shadow)}.card+.card{margin-top:1rem}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.card-title{font-size:1.125rem;font-weight:600}.btn{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;border-radius:var(--radius);border:none;font-size:.875rem;font-weight:500;cursor:pointer;transition:all .2s}.btn-primary{background:var(--color-primary);color:#fff}.btn-primary:hover{background:var(--color-primary-hover)}.btn-secondary{background:var(--color-bg);border:1px solid var(--color-border);color:var(--color-text)}.btn-secondary:hover{background:var(--color-bg-secondary)}.btn:disabled{opacity:.5;cursor:not-allowed}.dropzone{border:2px dashed var(--color-border);border-radius:var(--radius);padding:3rem;text-align:center;cursor:pointer;transition:all .2s}.dropzone:hover,.dropzone.active{border-color:var(--color-primary);background:#2563eb0d}.dropzone p{color:var(--color-text-muted);margin-top:.5rem}.file-list{margin-top:1rem}.file-item{display:flex;align-items:center;justify-content:space-between;padding:.75rem;border:1px solid var(--color-border);border-radius:var(--radius);margin-bottom:.5rem}.file-item .name{font-weight:500}.file-item .size{color:var(--color-text-muted);font-size:.875rem}.file-item .status{display:flex;align-items:center;gap:.5rem}.status-pending{color:var(--color-text-muted)}.status-uploading{color:var(--color-primary)}.status-success{color:var(--color-success)}.status-error{color:var(--color-error)}.document-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem}.document-card{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;transition:all .2s}.document-card:hover{box-shadow:var(--shadow-lg)}.document-card .title{font-weight:600;margin-bottom:.25rem}.document-card .meta{color:var(--color-text-muted);font-size:.875rem;margin-bottom:.75rem}.document-card .actions{display:flex;gap:.5rem}.folder-tree{border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;max-height:400px;overflow-y:auto}.folder-item{padding:.5rem;cursor:pointer;border-radius:var(--radius);display:flex;align-items:center;gap:.5rem}.folder-item:hover{background:var(--color-bg-secondary)}.folder-item.selected{background:#2563eb1a;color:var(--color-primary)}.folder-item .icon{font-size:1.25rem}.modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.modal{background:var(--color-bg);border-radius:var(--radius);padding:1.5rem;max-width:500px;width:90%;max-height:80vh;overflow-y:auto}.modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.modal-title{font-size:1.125rem;font-weight:600}.modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:var(--color-text-muted)}.form-group{margin-bottom:1rem}.form-label{display:block;font-weight:500;margin-bottom:.25rem}.form-input,.form-select{width:100%;padding:.5rem;border:1px solid var(--color-border);border-radius:var(--radius);font-size:1rem}.form-input:focus,.form-select:focus{outline:none;border-color:var(--color-primary)}.spinner{width:20px;height:20px;border:2px solid var(--color-border);border-top-color:var(--color-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:3rem;color:var(--color-text-muted)}.empty-state h3{color:var(--color-text);margin-bottom:.5rem}.alert{padding:1rem;border-radius:var(--radius);margin-bottom:1rem}.alert-success{background:#10b9811a;border:1px solid var(--color-success);color:var(--color-success)}.alert-error{background:#ef44441a;border:1px solid var(--color-error);color:var(--color-error)}.alert-warning{background:#f59e0b1a;border:1px solid var(--color-warning);color:var(--color-warning)}.breadcrumb{display:flex;align-items:center;flex-wrap:wrap;gap:0;font-size:.875rem}.breadcrumb-item{background:none;border:none;color:var(--color-primary);cursor:pointer;padding:.25rem .5rem;border-radius:var(--radius);font-size:inherit}.breadcrumb-item:hover{background:#2563eb1a}.breadcrumb-current{color:var(--color-text);cursor:default}.breadcrumb-current:hover{background:transparent}.breadcrumb-separator{color:var(--color-text-muted);margin:0 .25rem}.browse-page{display:flex;flex-direction:column;gap:1rem}.toolbar{display:flex;justify-content:space-between;align-items:center;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:.75rem 1rem;gap:1rem;flex-wrap:wrap}.toolbar-left{display:flex;align-items:center;gap:.5rem;flex:1;min-width:200px}.toolbar-right{display:flex;align-items:center;gap:.5rem}.search-input{padding:.5rem .75rem;border:1px solid var(--color-border);border-radius:var(--radius);font-size:.875rem;width:200px}.search-input:focus{outline:none;border-color:var(--color-primary)}.content-area{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);overflow:hidden}.data-grid-container{display:flex;flex-direction:column}.data-grid{width:100%;border-collapse:collapse;font-size:.875rem}.data-grid thead{background:var(--color-bg-secondary);border-bottom:1px solid var(--color-border)}.data-grid th{text-align:left;padding:.75rem 1rem;font-weight:600;color:var(--color-text-muted);white-space:nowrap}.data-grid th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.data-grid th.sortable:hover{color:var(--color-text)}.data-grid td{padding:.75rem 1rem;border-bottom:1px solid var(--color-border);vertical-align:middle}.grid-row{transition:background .15s}.grid-row:hover{background:var(--color-bg-secondary)}.folder-row{cursor:pointer}.folder-row:hover{background:#2563eb0d}.col-checkbox{width:40px;text-align:center}.col-name{min-width:250px}.col-format,.col-type{width:120px}.col-updated,.col-created{width:140px}.col-actions{width:100px;text-align:right}.item-icon{margin-right:.5rem;font-size:1rem}.item-name{font-weight:500}.subfolder-count{color:var(--color-text-muted);font-size:.75rem;margin-left:.5rem}.btn-icon{background:none;border:none;padding:.25rem .5rem;cursor:pointer;font-size:1rem;opacity:.6;transition:opacity .15s}.btn-icon:hover{opacity:1}.btn-icon:disabled{opacity:.3;cursor:not-allowed}.btn-icon.pinned,.btn-delete:hover{opacity:1}.data-grid-footer{padding:.75rem 1rem;background:var(--color-bg-secondary);border-top:1px solid var(--color-border);font-size:.75rem;color:var(--color-text-muted)}.data-grid-loading,.data-grid-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:3rem;text-align:center;color:var(--color-text-muted);gap:.5rem}.data-grid-empty .empty-icon{font-size:3rem;margin-bottom:.5rem}.data-grid-empty h3{color:var(--color-text);margin:0}.data-grid-empty p{margin:.5rem 0 1rem}.folder-browser{border:1px solid var(--color-border);border-radius:var(--radius);overflow:hidden}.folder-browser-toolbar{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;background:var(--color-bg-secondary);border-bottom:1px solid var(--color-border)}.folder-browser-actions{display:flex;gap:.5rem}.folder-browser-list{max-height:300px;overflow-y:auto}.folder-browser-table{width:100%;border-collapse:collapse}.folder-browser-row{transition:background .15s}.folder-browser-row:hover{background:var(--color-bg-secondary)}.folder-browser-row.selected{background:#2563eb1a}.folder-browser-row td{padding:.5rem;border-bottom:1px solid var(--color-border);vertical-align:middle}.folder-browser-icon{width:40px;text-align:center;font-size:1.25rem}.folder-browser-name{cursor:pointer;font-weight:500}.folder-browser-name:hover{color:var(--color-primary)}.subfolder-indicator{color:var(--color-text-muted);font-size:.75rem;font-weight:400;margin-left:.5rem}.folder-browser-select{width:100px;text-align:center}.folder-browser-delete{width:50px;text-align:center}.folder-browser-loading,.folder-browser-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;color:var(--color-text-muted);gap:.75rem}.upload-page{display:flex;flex-direction:column;gap:1rem}.selected-folder-badge{background:#2563eb1a;color:var(--color-primary);padding:.25rem .75rem;border-radius:999px;font-size:.75rem;font-weight:500}.upload-actions{margin-top:1rem;display:flex;gap:.5rem;justify-content:flex-end}.btn-sm{padding:.25rem .5rem;font-size:.75rem}.document-row{cursor:pointer;transition:background .15s}.document-row:hover{background:var(--color-bg-secondary)}.document-row.expanded{background:#2563eb0d}.expand-icon{display:inline-block;width:1rem;margin-right:.25rem;transition:transform .2s;color:var(--color-text-muted)}.expand-icon.expanded{transform:rotate(90deg)}.expanded-row td{padding:0!important;background:var(--color-bg-secondary);border-bottom:2px solid var(--color-border)}.course-detail-panel{padding:1rem;display:flex;flex-direction:column;gap:1rem}.course-detail-loading,.course-detail-error{display:flex;align-items:center;justify-content:center;gap:.75rem;padding:2rem;color:var(--color-text-muted)}.course-detail-error{color:var(--color-error);flex-direction:column}.course-detail-tile{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem}.tile-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem;padding-bottom:.75rem;border-bottom:1px solid var(--color-border)}.tile-header h3{font-size:1rem;font-weight:600;margin:0}.btn-close{background:none;border:none;font-size:1.25rem;color:var(--color-text-muted);cursor:pointer;padding:.25rem;line-height:1;border-radius:var(--radius);transition:all .15s}.btn-close:hover{background:var(--color-bg-secondary);color:var(--color-text)}.detail-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:.75rem 1.5rem}.detail-item{display:flex;flex-direction:column;gap:.125rem}.detail-item.span-2{grid-column:span 2}.detail-label{font-size:.7rem;font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.025em}.detail-value{font-size:.875rem;color:var(--color-text)}.detail-value.mono{font-family:SF Mono,Monaco,Consolas,monospace;font-size:.75rem;background:var(--color-bg-secondary);padding:.125rem .375rem;border-radius:4px}.detail-value.description{line-height:1.4;max-height:4.2em;overflow:hidden;text-overflow:ellipsis}.thin-pack-row{display:grid;grid-template-columns:repeat(2,1fr);gap:1rem}.thin-pack-tile{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;border-left:4px solid}.thin-pack-tile.public{border-left-color:var(--color-success)}.thin-pack-tile.private{border-left-color:var(--color-primary)}.thin-pack-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:.75rem}.thin-pack-icon{font-size:1.5rem}.thin-pack-titles{flex:1}.thin-pack-title{font-size:.9rem;font-weight:600;margin:0}.thin-pack-subtitle{font-size:.75rem;color:var(--color-text-muted);margin:.125rem 0 0}.thin-pack-content{display:flex;flex-direction:column;gap:.5rem}.thin-pack-error{font-size:.75rem;color:var(--color-error);padding:.5rem;background:#ef44441a;border-radius:4px}.thin-pack-status{display:flex;align-items:center;gap:.5rem;font-size:.8rem}.thin-pack-status.exists{color:var(--color-success)}.thin-pack-status.exists .status-icon{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;background:var(--color-success);color:#fff;border-radius:50%;font-size:.7rem}.thin-pack-status.missing{color:var(--color-text-muted);font-style:italic}.thin-pack-meta{display:flex;gap:1rem;font-size:.7rem;color:var(--color-text-muted)}.thin-pack-actions{display:flex;gap:.5rem;margin-top:.25rem}.thin-pack-slug-input{display:flex;flex-direction:column;gap:.25rem}.thin-pack-slug-input label{font-size:.7rem;color:var(--color-text-muted)}.thin-pack-slug-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.thin-pack-slug-input input:focus{outline:none;border-color:var(--color-primary)}.thin-pack-slug-input input::placeholder{color:var(--color-text-muted);opacity:.6}.thin-pack-name-input{display:flex;flex-direction:column;gap:.25rem}.thin-pack-name-input label{font-size:.7rem;color:var(--color-text-muted)}.thin-pack-name-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.thin-pack-name-input input:focus{outline:none;border-color:var(--color-primary)}.thin-pack-name-input input::placeholder{color:var(--color-text-muted);opacity:.6}.token-preview{font-size:.65rem;color:var(--color-text-muted);font-family:monospace;word-break:break-all}.thin-pack-visibility{display:flex;flex-direction:column;gap:.25rem;padding:.5rem;background:var(--color-bg-tertiary);border-radius:4px}.visibility-toggle{display:flex;align-items:center;gap:.5rem;cursor:pointer}.visibility-toggle input[type=checkbox]{width:16px;height:16px;cursor:pointer}.toggle-label{font-size:.85rem;font-weight:500}.visibility-hint{font-size:.7rem;color:var(--color-text-muted);margin-left:24px}.thin-pack-tile.lms{border-left-color:var(--color-success)}.thin-pack-tile.shared{border-left-color:var(--color-primary)}.upload-modal{width:600px;max-width:90vw;max-height:90vh;display:flex;flex-direction:column}.upload-modal-content{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.upload-modal .dropzone{min-height:120px}.upload-file-list{display:flex;flex-direction:column;gap:.5rem}.upload-file-list h4{margin:0;font-size:.85rem;color:var(--color-text-muted)}.upload-file-item{display:flex;justify-content:space-between;align-items:center;padding:.5rem;background:var(--color-bg-secondary);border-radius:8px}.upload-file-info{display:flex;flex-direction:column;gap:.125rem}.upload-file-name{font-weight:500;font-size:.875rem}.upload-file-size{font-size:.75rem;color:var(--color-text-muted)}.upload-file-status{display:flex;align-items:center;gap:.5rem}.upload-progress-container{display:flex;align-items:center;gap:.75rem;min-width:150px}.upload-progress-bar{flex:1;height:8px;background:var(--color-border);border-radius:4px;overflow:hidden}.upload-progress-fill{height:100%;background:var(--color-primary);transition:width .15s ease-out}.upload-progress-text{font-size:.75rem;font-weight:500;color:var(--color-text-muted);min-width:36px;text-align:right}.status-processing{color:var(--color-primary);font-size:.875rem}.upload-link-options{background:var(--color-bg-secondary);border-radius:var(--radius);padding:1rem;display:flex;flex-direction:column;gap:.75rem}.upload-link-options h4{margin:0;font-size:.85rem;color:var(--color-text-muted)}.upload-link-option{display:flex;flex-direction:column;gap:.5rem}.upload-link-toggle{display:flex;align-items:center;gap:.5rem;cursor:pointer}.upload-link-toggle input[type=checkbox]{width:1rem;height:1rem;cursor:pointer}.upload-link-toggle span{font-size:.875rem}.upload-link-name-input{display:flex;flex-direction:column;gap:.25rem;padding-left:1.5rem}.upload-link-name-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.upload-link-name-input input:focus{outline:none;border-color:var(--color-primary)}.upload-link-name-input input::placeholder{color:var(--color-text-muted);opacity:.6}.slug-preview{font-size:.7rem;color:var(--color-text-muted);font-family:monospace}.modal-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:1rem;border-top:1px solid var(--color-border)}@media (max-width: 768px){.detail-grid{grid-template-columns:1fr}.detail-item.span-2{grid-column:span 1}.thin-pack-row{grid-template-columns:1fr}}.user-menu{display:flex;align-items:center;gap:.75rem;margin-left:auto}.user-info{display:flex;align-items:center;gap:.5rem}.user-avatar{width:2rem;height:2rem;border-radius:50%;background:var(--color-primary);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem}.user-name{font-size:.875rem;color:var(--color-text);max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.header{display:flex;align-items:center;gap:1.5rem}.header .nav{margin-right:auto}.course-structure-step{display:flex;flex-direction:column;gap:.5rem}.course-structure-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.25rem}.course-structure-header h4{margin:0;font-size:1rem;font-weight:600}.course-structure-meta{display:flex;align-items:center;gap:.5rem;font-size:.75rem;color:var(--color-text-muted)}.course-structure-badge{background:#2563eb1a;color:var(--color-primary);padding:.125rem .5rem;border-radius:999px;font-size:.7rem;font-weight:500}.tree-node{padding:.25rem 0}.tree-node-row{display:flex;align-items:center;gap:.375rem;min-height:1.5rem}.tree-node-icon{font-size:1rem;flex-shrink:0}.tree-node-label{font-weight:500;font-size:.875rem;flex:1}.tree-class-input{width:100%;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text);margin-top:.25rem}.tree-class-input:focus{outline:none;border-color:var(--color-primary)}.tree-class-input::placeholder{color:var(--color-text-muted);opacity:.6}.tree-class-input-sm{max-width:200px;margin-top:0;margin-left:auto;flex-shrink:0}.tree-course{background:var(--color-bg-secondary);border:1px solid var(--color-border);border-radius:var(--radius);padding:.5rem .75rem}.tree-lessons{display:flex;flex-direction:column;gap:.125rem;max-height:400px;overflow-y:auto;border:1px solid var(--color-border);border-radius:var(--radius);padding:.25rem}.tree-lesson{border-bottom:1px solid var(--color-border)}.tree-lesson:last-child{border-bottom:none}.tree-node-lesson{cursor:pointer;padding:.375rem .5rem;border-radius:var(--radius-sm);transition:background .15s}.tree-node-lesson:hover{background:var(--color-bg-secondary)}.tree-expand{font-size:.625rem;width:1rem;text-align:center;color:var(--color-text-muted);flex-shrink:0}.tree-lesson-num{color:var(--color-text-muted);font-weight:600;font-size:.75rem}.tree-node-indicator{font-size:.65rem;color:var(--color-primary);background:#2563eb1a;padding:.0625rem .375rem;border-radius:999px;white-space:nowrap;flex-shrink:0}.tree-lesson-content{padding:.25rem .5rem .5rem 1.5rem}.tree-lesson-class{margin-bottom:.375rem}.tree-node-block{display:flex;align-items:center;gap:.375rem;padding:.1875rem 0}.tree-node-block .tree-node-row{flex:1;min-width:0}.tree-block-pipe{font-family:monospace;color:var(--color-border);font-size:.75rem;flex-shrink:0}.tree-block-type{font-size:.75rem;font-weight:500;color:var(--color-text);white-space:nowrap}.tree-block-title{font-size:.7rem;color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px}.structure-parsing-indicator{display:flex;align-items:center;gap:.75rem;padding:.75rem;color:var(--color-primary);font-size:.875rem}.structure-detected-badge{background:#10b9811a;color:var(--color-success);border:1px solid var(--color-success);border-radius:var(--radius);padding:.5rem .75rem;font-size:.8rem}.structure-multi-note{font-size:.75rem;color:var(--color-text-muted);font-style:italic;padding:.25rem 0}.structure-summary-badge{background:#2563eb1a;color:var(--color-primary);border-radius:var(--radius);padding:.5rem .75rem;font-size:.8rem;margin-top:.25rem}.structure-section{display:flex;flex-direction:column}.structure-toggle-btn{display:flex;align-items:center;gap:.5rem;width:100%;padding:.625rem .75rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);cursor:pointer;font-size:.875rem;font-weight:500;color:var(--color-text);transition:all .2s}.structure-toggle-btn:hover:not(:disabled){border-color:var(--color-primary);background:#2563eb08}.structure-toggle-btn.open{border-color:var(--color-primary);border-bottom-left-radius:0;border-bottom-right-radius:0;background:#2563eb0d}.structure-toggle-btn.disabled{opacity:.5;cursor:not-allowed;color:var(--color-text-muted)}.structure-toggle-btn .spinner{width:16px;height:16px;flex-shrink:0}.structure-toggle-arrow{margin-left:auto;font-size:.625rem;color:var(--color-text-muted);transition:transform .2s}.structure-panel{border:1px solid var(--color-primary);border-top:none;border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius);padding:.75rem;background:var(--color-bg);animation:panelSlideDown .15s ease-out}@keyframes panelSlideDown{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.block-cards{display:flex;flex-direction:column;gap:3px;padding:.375rem 0}.block-card{display:flex;flex-direction:column;gap:.25rem;padding:.5rem .625rem;border-radius:var(--radius-sm);border:1px solid var(--color-border);border-left:4px solid var(--block-accent, #6b7280);background:var(--color-bg);cursor:pointer;transition:all .15s}.block-card:hover:not(.grouped){border-color:var(--color-primary);border-left-color:var(--block-accent, #6b7280);background:#2563eb08}.block-card.selected{background:#2563eb14;border-color:var(--color-primary);border-left-color:var(--block-accent, #6b7280);box-shadow:0 0 0 1px #2563eb33}.block-card.grouped{cursor:default;opacity:.75;background:var(--color-bg-secondary)}.block-card-header{display:flex;align-items:center;gap:.5rem;min-height:1.25rem}.block-card-type{font-size:.8rem;font-weight:600;color:var(--color-text);white-space:nowrap}.block-card-title{font-size:.75rem;color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.block-card-check{color:var(--color-primary);font-weight:700;font-size:.875rem;flex-shrink:0;margin-left:auto}.block-card .tree-class-input-sm{margin-left:0;max-width:100%;margin-top:0}.block-group-container{border:2px dashed var(--color-primary);border-radius:var(--radius);padding:.375rem;margin:.25rem 0;background:#2563eb05}.block-group-container .block-card{border-left-width:3px}.bgp-group-header{display:flex;align-items:center;gap:.5rem;padding:.25rem .5rem;font-size:.7rem;border-bottom:1px solid rgba(37,99,235,.15);margin-bottom:.25rem}.bgp-group-label{font-weight:600;color:var(--color-primary)}.bgp-group-class{color:var(--color-text-muted);font-family:monospace;font-size:.65rem}.bgp-btn{background:none;border:none;font-size:.65rem;cursor:pointer;padding:.125rem .375rem;border-radius:var(--radius-sm);transition:all .15s}.bgp-btn-edit{color:var(--color-text-muted);margin-left:auto}.bgp-btn-edit:hover{color:var(--color-primary);background:#2563eb1a}.bgp-btn-ungroup{color:var(--color-text-muted)}.bgp-btn-ungroup:hover{color:var(--color-error);background:#ef44441a}.bgp-btn-save{color:var(--color-success)}.bgp-btn-save:hover{background:#10b9811a}.bgp-btn-cancel{color:var(--color-text-muted)}.bgp-btn-cancel:hover{background:var(--color-bg-secondary)}.bgp-group-edit{display:flex;align-items:center;gap:.375rem;flex:1}.bgp-edit-input{padding:.1875rem .375rem;font-size:.7rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text);flex:1}.bgp-edit-input:focus{outline:none;border-color:var(--color-primary)}.bgp-edit-input-sm{max-width:120px}.bgp-create{padding:.5rem .75rem;margin-top:.375rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg-secondary);display:flex;flex-direction:column;gap:.375rem}.bgp-create-info{font-size:.75rem;font-weight:500;color:var(--color-text)}.bgp-validation-error{color:var(--color-error);font-weight:400}.bgp-create-inputs{display:flex;gap:.375rem}.bgp-create-class{flex:2;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.bgp-create-class:focus{outline:none;border-color:var(--color-primary)}.bgp-create-label{flex:1;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.bgp-create-label:focus{outline:none;border-color:var(--color-primary)}.bgp-create-actions{display:flex;gap:.375rem}.bgp-hint{padding:.75rem;font-size:.75rem;color:var(--color-text-muted);text-align:center;font-style:italic}
package/dist/index.html CHANGED
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Package Uploader</title>
8
- <script type="module" crossorigin src="/assets/index-95fEc7BA.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-CcXisJMx.css">
8
+ <script type="module" crossorigin src="/assets/index-BZdhKDnY.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-BfowR104.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@package-uploader/ui",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "React UI for uploading and browsing courses on LMS platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,5 @@
1
1
  import { useState } from 'react';
2
- import BlockGroupingPanel from './BlockGroupingPanel';
3
- import type { BlockGroup } from '../hooks/useBlockGrouping';
2
+ import { useBlockGrouping, type BlockGroup } from '../hooks/useBlockGrouping';
4
3
 
5
4
  /** Block within a lesson */
6
5
  interface CourseStructureBlock {
@@ -40,51 +39,301 @@ interface CourseStructureStepProps {
40
39
  onChange: (mappings: CourseClassMappings) => void;
41
40
  }
42
41
 
43
- export default function CourseStructureStep({
44
- structure,
42
+ /** Color palette for block families */
43
+ function familyColor(family?: string): string {
44
+ switch (family) {
45
+ case 'text': return '#3b82f6';
46
+ case 'media': return '#22c55e';
47
+ case 'interactive': return '#a855f7';
48
+ case 'knowledge-check': return '#f59e0b';
49
+ case 'quote': return '#ec4899';
50
+ default: return '#6b7280';
51
+ }
52
+ }
53
+
54
+ function formatBlockLabel(block: CourseStructureBlock): string {
55
+ const parts = [block.type];
56
+ if (block.variant) parts.push(block.variant);
57
+ return parts.join(' / ');
58
+ }
59
+
60
+ // --- LessonNode: extracted so useBlockGrouping can be called per lesson ---
61
+
62
+ interface LessonNodeProps {
63
+ lesson: CourseStructureLesson;
64
+ lessonIdx: number;
65
+ classMappings: CourseClassMappings;
66
+ onSetLessonClass: (lessonId: string, value: string) => void;
67
+ onSetBlockClass: (blockId: string, value: string) => void;
68
+ onGroupsChange: (lessonId: string, groups: BlockGroup[]) => void;
69
+ }
70
+
71
+ function LessonNode({
72
+ lesson,
73
+ lessonIdx,
45
74
  classMappings,
46
- onChange,
47
- }: CourseStructureStepProps) {
48
- const [expandedLessons, setExpandedLessons] = useState<Set<string>>(new Set());
49
- const [groupingLessons, setGroupingLessons] = useState<Set<string>>(new Set());
50
-
51
- const toggleGrouping = (lessonId: string) => {
52
- setGroupingLessons((prev) => {
53
- const next = new Set(prev);
54
- if (next.has(lessonId)) {
55
- next.delete(lessonId);
56
- } else {
57
- next.add(lessonId);
58
- }
59
- return next;
60
- });
75
+ onSetLessonClass,
76
+ onSetBlockClass,
77
+ onGroupsChange,
78
+ }: LessonNodeProps) {
79
+ const [isExpanded, setIsExpanded] = useState(false);
80
+
81
+ // Group edit state (absorbed from BlockGroupingPanel)
82
+ const [newGroupClass, setNewGroupClass] = useState('');
83
+ const [newGroupLabel, setNewGroupLabel] = useState('');
84
+ const [editingGroupId, setEditingGroupId] = useState<string | null>(null);
85
+ const [editClass, setEditClass] = useState('');
86
+ const [editLabel, setEditLabel] = useState('');
87
+
88
+ const lessonGroups = (classMappings.blockGroups || []).filter(
89
+ (g) => g.lessonId === lesson.id
90
+ );
91
+
92
+ const lessonBlockIds = lesson.blocks.map((b) => b.id);
93
+
94
+ const {
95
+ selectedBlockIds,
96
+ toggleBlock,
97
+ clearSelection,
98
+ canCreateGroup,
99
+ validationError,
100
+ createGroup,
101
+ removeGroup,
102
+ updateGroup,
103
+ isBlockGrouped,
104
+ } = useBlockGrouping({
105
+ lessonBlockIds,
106
+ lessonId: lesson.id,
107
+ groups: lessonGroups,
108
+ onGroupsChange: (groups) => onGroupsChange(lesson.id, groups),
109
+ });
110
+
111
+ const lessonHasClass = !!classMappings.lessons?.[lesson.id];
112
+ const blocksWithClass = lesson.blocks.filter(
113
+ (b) => !!classMappings.blocks?.[b.id]
114
+ ).length;
115
+
116
+ // --- Group edit handlers ---
117
+ const handleCreateGroup = () => {
118
+ if (!canCreateGroup || !newGroupClass.trim()) return;
119
+ createGroup(newGroupClass, newGroupLabel || undefined);
120
+ setNewGroupClass('');
121
+ setNewGroupLabel('');
61
122
  };
62
123
 
63
- const getGroupsForLesson = (lessonId: string): BlockGroup[] => {
64
- return (classMappings.blockGroups || []).filter((g) => g.lessonId === lessonId);
124
+ const startEditGroup = (group: BlockGroup) => {
125
+ setEditingGroupId(group.id);
126
+ setEditClass(group.className);
127
+ setEditLabel(group.label || '');
65
128
  };
66
129
 
67
- const handleGroupsChange = (lessonId: string, lessonGroups: BlockGroup[]) => {
68
- const otherGroups = (classMappings.blockGroups || []).filter((g) => g.lessonId !== lessonId);
69
- const allGroups = [...otherGroups, ...lessonGroups];
70
- onChange({
71
- ...classMappings,
72
- blockGroups: allGroups.length > 0 ? allGroups : undefined,
73
- });
130
+ const saveEditGroup = () => {
131
+ if (editingGroupId) {
132
+ updateGroup(editingGroupId, { className: editClass, label: editLabel });
133
+ setEditingGroupId(null);
134
+ }
74
135
  };
75
136
 
76
- const toggleLesson = (lessonId: string) => {
77
- setExpandedLessons((prev) => {
78
- const next = new Set(prev);
79
- if (next.has(lessonId)) {
80
- next.delete(lessonId);
81
- } else {
82
- next.add(lessonId);
83
- }
84
- return next;
85
- });
137
+ // --- Block card rendering ---
138
+ const renderBlockCard = (block: CourseStructureBlock, isGrouped: boolean) => {
139
+ const isSelected = selectedBlockIds.has(block.id);
140
+ return (
141
+ <div
142
+ key={block.id}
143
+ className={`block-card${isSelected ? ' selected' : ''}${isGrouped ? ' grouped' : ''}`}
144
+ onClick={() => !isGrouped && toggleBlock(block.id)}
145
+ style={{ '--block-accent': familyColor(block.family) } as React.CSSProperties}
146
+ title={isGrouped ? 'Already in a group' : 'Click to select for grouping'}
147
+ >
148
+ <div className="block-card-header">
149
+ <span className="block-card-type">{formatBlockLabel(block)}</span>
150
+ {block.title && (
151
+ <span className="block-card-title" title={block.title}>
152
+ {block.title.length > 40 ? block.title.substring(0, 40) + '...' : block.title}
153
+ </span>
154
+ )}
155
+ {isSelected && <span className="block-card-check">&#10003;</span>}
156
+ </div>
157
+ <input
158
+ type="text"
159
+ className="tree-class-input tree-class-input-sm"
160
+ placeholder="class..."
161
+ value={classMappings.blocks?.[block.id] || ''}
162
+ onChange={(e) => onSetBlockClass(block.id, e.target.value)}
163
+ onClick={(e) => e.stopPropagation()}
164
+ />
165
+ </div>
166
+ );
86
167
  };
87
168
 
169
+ // --- Group header rendering ---
170
+ const renderGroupHeader = (group: BlockGroup) => {
171
+ if (editingGroupId === group.id) {
172
+ return (
173
+ <div className="bgp-group-header">
174
+ <div className="bgp-group-edit">
175
+ <input
176
+ type="text"
177
+ value={editClass}
178
+ onChange={(e) => setEditClass(e.target.value)}
179
+ placeholder="CSS class(es)"
180
+ className="bgp-edit-input"
181
+ />
182
+ <input
183
+ type="text"
184
+ value={editLabel}
185
+ onChange={(e) => setEditLabel(e.target.value)}
186
+ placeholder="Label (optional)"
187
+ className="bgp-edit-input bgp-edit-input-sm"
188
+ />
189
+ <button className="bgp-btn bgp-btn-save" onClick={saveEditGroup}>Save</button>
190
+ <button className="bgp-btn bgp-btn-cancel" onClick={() => setEditingGroupId(null)}>Cancel</button>
191
+ </div>
192
+ </div>
193
+ );
194
+ }
195
+
196
+ return (
197
+ <div className="bgp-group-header">
198
+ <span className="bgp-group-label">{group.label || group.className}</span>
199
+ <span className="bgp-group-class">.{group.className}</span>
200
+ <button className="bgp-btn bgp-btn-edit" onClick={() => startEditGroup(group)} title="Edit group">Edit</button>
201
+ <button className="bgp-btn bgp-btn-ungroup" onClick={() => removeGroup(group.id)} title="Remove group">Ungroup</button>
202
+ </div>
203
+ );
204
+ };
205
+
206
+ // --- Build block card list with inline group containers ---
207
+ const blockGroupMap = new Map<string, BlockGroup>();
208
+ for (const group of lessonGroups) {
209
+ for (const bid of group.blockIds) {
210
+ blockGroupMap.set(bid, group);
211
+ }
212
+ }
213
+
214
+ const renderedGroups = new Set<string>();
215
+ const blockItems: React.ReactNode[] = [];
216
+
217
+ for (const block of lesson.blocks) {
218
+ const group = blockGroupMap.get(block.id);
219
+ if (group) {
220
+ if (renderedGroups.has(group.id)) continue;
221
+ renderedGroups.add(group.id);
222
+ const groupBlocks = group.blockIds
223
+ .map((bid) => lesson.blocks.find((b) => b.id === bid))
224
+ .filter(Boolean) as CourseStructureBlock[];
225
+ blockItems.push(
226
+ <div key={`group-${group.id}`} className="block-group-container">
227
+ {renderGroupHeader(group)}
228
+ {groupBlocks.map((b) => renderBlockCard(b, true))}
229
+ </div>
230
+ );
231
+ } else {
232
+ blockItems.push(renderBlockCard(block, false));
233
+ }
234
+ }
235
+
236
+ const hasSelection = selectedBlockIds.size > 0;
237
+
238
+ return (
239
+ <div className="tree-lesson">
240
+ <div
241
+ className="tree-node tree-node-lesson"
242
+ onClick={() => setIsExpanded((prev) => !prev)}
243
+ >
244
+ <div className="tree-node-row">
245
+ <span className="tree-expand">{isExpanded ? '\u25BC' : '\u25B6'}</span>
246
+ <span className="tree-node-label">
247
+ <span className="tree-lesson-num">{lessonIdx + 1}.</span>
248
+ {' '}{lesson.title}
249
+ </span>
250
+ {(lessonHasClass || blocksWithClass > 0 || lessonGroups.length > 0) && !isExpanded && (
251
+ <span className="tree-node-indicator">
252
+ {[
253
+ lessonHasClass ? 'lesson' : '',
254
+ blocksWithClass > 0 ? `${blocksWithClass} block${blocksWithClass > 1 ? 's' : ''}` : '',
255
+ lessonGroups.length > 0 ? `${lessonGroups.length} group${lessonGroups.length > 1 ? 's' : ''}` : '',
256
+ ].filter(Boolean).join(' + ')}
257
+ </span>
258
+ )}
259
+ </div>
260
+ </div>
261
+
262
+ {isExpanded && (
263
+ <div className="tree-lesson-content">
264
+ <div className="tree-lesson-class">
265
+ <input
266
+ type="text"
267
+ className="tree-class-input"
268
+ placeholder="Lesson-level class..."
269
+ value={classMappings.lessons?.[lesson.id] || ''}
270
+ onChange={(e) => onSetLessonClass(lesson.id, e.target.value)}
271
+ onClick={(e) => e.stopPropagation()}
272
+ />
273
+ </div>
274
+
275
+ {/* Visual block cards */}
276
+ <div className="block-cards">
277
+ {blockItems}
278
+ </div>
279
+
280
+ {/* Create group controls */}
281
+ {hasSelection && (
282
+ <div className="bgp-create">
283
+ <div className="bgp-create-info">
284
+ {selectedBlockIds.size} block{selectedBlockIds.size !== 1 ? 's' : ''} selected
285
+ {validationError && <span className="bgp-validation-error"> — {validationError}</span>}
286
+ </div>
287
+ <div className="bgp-create-inputs">
288
+ <input
289
+ type="text"
290
+ value={newGroupClass}
291
+ onChange={(e) => setNewGroupClass(e.target.value)}
292
+ placeholder="CSS class(es) for wrapper..."
293
+ className="bgp-create-class"
294
+ onKeyDown={(e) => e.key === 'Enter' && handleCreateGroup()}
295
+ />
296
+ <input
297
+ type="text"
298
+ value={newGroupLabel}
299
+ onChange={(e) => setNewGroupLabel(e.target.value)}
300
+ placeholder="Label (optional)"
301
+ className="bgp-create-label"
302
+ />
303
+ </div>
304
+ <div className="bgp-create-actions">
305
+ <button
306
+ className="btn btn-sm btn-primary"
307
+ onClick={handleCreateGroup}
308
+ disabled={!canCreateGroup || !newGroupClass.trim()}
309
+ >
310
+ Create Group
311
+ </button>
312
+ <button className="btn btn-sm btn-secondary" onClick={clearSelection}>
313
+ Cancel
314
+ </button>
315
+ </div>
316
+ </div>
317
+ )}
318
+
319
+ {!hasSelection && lesson.blocks.length >= 2 && lessonGroups.length === 0 && (
320
+ <div className="bgp-hint">
321
+ Click adjacent blocks to select them, then create a group.
322
+ </div>
323
+ )}
324
+ </div>
325
+ )}
326
+ </div>
327
+ );
328
+ }
329
+
330
+ // --- Main component ---
331
+
332
+ export default function CourseStructureStep({
333
+ structure,
334
+ classMappings,
335
+ onChange,
336
+ }: CourseStructureStepProps) {
88
337
  const setCourseClass = (value: string) => {
89
338
  onChange({ ...classMappings, course: value || undefined });
90
339
  };
@@ -109,6 +358,15 @@ export default function CourseStructureStep({
109
358
  onChange({ ...classMappings, blocks: Object.keys(blocks).length > 0 ? blocks : undefined });
110
359
  };
111
360
 
361
+ const handleGroupsChange = (lessonId: string, lessonGroups: BlockGroup[]) => {
362
+ const otherGroups = (classMappings.blockGroups || []).filter((g) => g.lessonId !== lessonId);
363
+ const allGroups = [...otherGroups, ...lessonGroups];
364
+ onChange({
365
+ ...classMappings,
366
+ blockGroups: allGroups.length > 0 ? allGroups : undefined,
367
+ });
368
+ };
369
+
112
370
  const clearAll = () => {
113
371
  onChange({});
114
372
  };
@@ -123,12 +381,6 @@ export default function CourseStructureStep({
123
381
 
124
382
  const blockCount = structure.lessons.reduce((sum, l) => sum + l.blocks.length, 0);
125
383
 
126
- const formatBlockLabel = (block: CourseStructureBlock): string => {
127
- const parts = [block.type];
128
- if (block.variant) parts.push(block.variant);
129
- return parts.join(' / ');
130
- };
131
-
132
384
  return (
133
385
  <div className="course-structure-step">
134
386
  <div className="course-structure-header">
@@ -165,102 +417,17 @@ export default function CourseStructureStep({
165
417
 
166
418
  {/* Lessons */}
167
419
  <div className="tree-lessons">
168
- {structure.lessons.map((lesson, lessonIdx) => {
169
- const isExpanded = expandedLessons.has(lesson.id);
170
- const isGrouping = groupingLessons.has(lesson.id);
171
- const lessonHasClass = !!classMappings.lessons?.[lesson.id];
172
- const blocksWithClass = lesson.blocks.filter(
173
- (b) => !!classMappings.blocks?.[b.id]
174
- ).length;
175
- const lessonGroups = getGroupsForLesson(lesson.id);
176
-
177
- return (
178
- <div key={lesson.id} className="tree-lesson">
179
- <div
180
- className="tree-node tree-node-lesson"
181
- onClick={() => toggleLesson(lesson.id)}
182
- >
183
- <div className="tree-node-row">
184
- <span className="tree-expand">{isExpanded ? '\u25BC' : '\u25B6'}</span>
185
- <span className="tree-node-label">
186
- <span className="tree-lesson-num">{lessonIdx + 1}.</span>
187
- {' '}{lesson.title}
188
- </span>
189
- {(lessonHasClass || blocksWithClass > 0) && !isExpanded && (
190
- <span className="tree-node-indicator">
191
- {lessonHasClass && blocksWithClass > 0
192
- ? `lesson + ${blocksWithClass} block${blocksWithClass > 1 ? 's' : ''}`
193
- : lessonHasClass
194
- ? 'lesson'
195
- : `${blocksWithClass} block${blocksWithClass > 1 ? 's' : ''}`}
196
- </span>
197
- )}
198
- </div>
199
- </div>
200
-
201
- {isExpanded && (
202
- <div className="tree-lesson-content">
203
- <div className="tree-lesson-class">
204
- <input
205
- type="text"
206
- className="tree-class-input"
207
- placeholder="Lesson-level class..."
208
- value={classMappings.lessons?.[lesson.id] || ''}
209
- onChange={(e) => setLessonClass(lesson.id, e.target.value)}
210
- onClick={(e) => e.stopPropagation()}
211
- />
212
- </div>
213
-
214
- {lesson.blocks.map((block) => (
215
- <div key={block.id} className="tree-node tree-node-block">
216
- <div className="tree-node-row">
217
- <span className="tree-block-pipe">|--</span>
218
- <span className="tree-block-type">{formatBlockLabel(block)}</span>
219
- {block.title && (
220
- <span className="tree-block-title" title={block.title}>
221
- {block.title.length > 40
222
- ? block.title.substring(0, 40) + '...'
223
- : block.title}
224
- </span>
225
- )}
226
- </div>
227
- <input
228
- type="text"
229
- className="tree-class-input tree-class-input-sm"
230
- placeholder="class..."
231
- value={classMappings.blocks?.[block.id] || ''}
232
- onChange={(e) => setBlockClass(block.id, e.target.value)}
233
- />
234
- </div>
235
- ))}
236
-
237
- {/* Group Blocks button */}
238
- {lesson.blocks.length >= 2 && (
239
- <button
240
- className={`btn btn-sm bgp-toggle-btn${isGrouping ? ' active' : ''}`}
241
- onClick={() => toggleGrouping(lesson.id)}
242
- >
243
- {isGrouping ? 'Hide Grouping' : 'Group Blocks'}
244
- {lessonGroups.length > 0 && !isGrouping && (
245
- <span className="bgp-toggle-count">{lessonGroups.length}</span>
246
- )}
247
- </button>
248
- )}
249
-
250
- {/* Block Grouping Panel */}
251
- {isGrouping && (
252
- <BlockGroupingPanel
253
- lessonId={lesson.id}
254
- blocks={lesson.blocks}
255
- groups={lessonGroups}
256
- onGroupsChange={(groups) => handleGroupsChange(lesson.id, groups)}
257
- />
258
- )}
259
- </div>
260
- )}
261
- </div>
262
- );
263
- })}
420
+ {structure.lessons.map((lesson, lessonIdx) => (
421
+ <LessonNode
422
+ key={lesson.id}
423
+ lesson={lesson}
424
+ lessonIdx={lessonIdx}
425
+ classMappings={classMappings}
426
+ onSetLessonClass={setLessonClass}
427
+ onSetBlockClass={setBlockClass}
428
+ onGroupsChange={handleGroupsChange}
429
+ />
430
+ ))}
264
431
  </div>
265
432
  </div>
266
433
  );