prev-cli 0.7.2 → 0.8.0

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.
package/dist/cli.js CHANGED
@@ -139,8 +139,8 @@ function fileToRoute(file) {
139
139
  async function scanPages(rootDir) {
140
140
  const files = await fg.glob("**/*.{md,mdx}", {
141
141
  cwd: rootDir,
142
- ignore: ["node_modules/**", "dist/**", ".cache/**"],
143
- dot: true
142
+ ignore: ["node_modules/**", "dist/**", ".cache/**", ".*/**"],
143
+ dot: false
144
144
  });
145
145
  const routeMap = new Map;
146
146
  for (const file of files) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prev-cli",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Transform MDX directories into beautiful documentation websites",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect } from 'react'
1
+ import React, { useState, useEffect, useRef, createContext, useContext } from 'react'
2
2
  import { Link, useLocation } from '@tanstack/react-router'
3
3
  import type { PageTree } from 'fumadocs-core/server'
4
4
 
@@ -7,6 +7,77 @@ interface LayoutProps {
7
7
  children: React.ReactNode
8
8
  }
9
9
 
10
+ type TreeItem = PageTree.Item | PageTree.Folder
11
+
12
+ // Get unique ID for a tree item
13
+ function getItemId(item: TreeItem): string {
14
+ return item.type === 'folder' ? `folder:${item.name}` : item.url
15
+ }
16
+
17
+ // Storage key for a specific path (root or folder)
18
+ function getStorageKey(path: string): string {
19
+ return `sidebar-order:${path}`
20
+ }
21
+
22
+ // Apply saved order to items for a specific path
23
+ // Handles: new items (appended), removed items (skipped), stale storage (cleaned)
24
+ function applyOrder(items: TreeItem[], path: string): TreeItem[] {
25
+ if (typeof window === 'undefined') return items
26
+
27
+ const saved = localStorage.getItem(getStorageKey(path))
28
+ if (!saved) return items
29
+
30
+ const order: string[] = JSON.parse(saved)
31
+ if (order.length === 0) return items
32
+
33
+ const itemMap = new Map(items.map(item => [getItemId(item), item]))
34
+ const ordered: TreeItem[] = []
35
+
36
+ // Add items in saved order (skip removed ones)
37
+ for (const id of order) {
38
+ const item = itemMap.get(id)
39
+ if (item) {
40
+ ordered.push(item)
41
+ itemMap.delete(id)
42
+ }
43
+ }
44
+
45
+ // Add remaining items (new ones not in saved order)
46
+ for (const item of itemMap.values()) {
47
+ ordered.push(item)
48
+ }
49
+
50
+ // Clean up stale entries: if order changed, update storage
51
+ const currentIds = ordered.map(getItemId)
52
+ const hasChanges = order.length !== currentIds.length ||
53
+ order.some((id, i) => currentIds[i] !== id)
54
+
55
+ if (hasChanges) {
56
+ localStorage.setItem(getStorageKey(path), JSON.stringify(currentIds))
57
+ }
58
+
59
+ return ordered
60
+ }
61
+
62
+ // Save order for a specific path
63
+ function saveOrder(items: TreeItem[], path: string): void {
64
+ const order = items.map(getItemId)
65
+ localStorage.setItem(getStorageKey(path), JSON.stringify(order))
66
+ }
67
+
68
+ // Drag context for nested components
69
+ interface DragContextType {
70
+ dragPath: string | null
71
+ dragIndex: number | null
72
+ dragOverPath: string | null
73
+ dragOverIndex: number | null
74
+ onDragStart: (path: string, index: number) => void
75
+ onDragOver: (path: string, index: number) => void
76
+ onDragEnd: () => void
77
+ }
78
+
79
+ const DragContext = createContext<DragContextType | null>(null)
80
+
10
81
  function SidebarToggle({ collapsed, onToggle }: { collapsed: boolean; onToggle: () => void }) {
11
82
  return (
12
83
  <button
@@ -41,23 +112,55 @@ function CollapsedFolderIcon({ item, onClick }: { item: PageTree.Folder; onClick
41
112
  )
42
113
  }
43
114
 
44
- interface SidebarItemProps {
45
- item: PageTree.Item | PageTree.Folder
115
+ interface DraggableSidebarItemProps {
116
+ item: TreeItem
117
+ index: number
118
+ path: string
46
119
  depth?: number
47
120
  }
48
121
 
49
- function SidebarItem({ item, depth = 0 }: SidebarItemProps) {
122
+ function DraggableSidebarItem({ item, index, path, depth = 0 }: DraggableSidebarItemProps) {
50
123
  const location = useLocation()
51
124
  const [isOpen, setIsOpen] = useState(true)
125
+ const dragCtx = useContext(DragContext)
126
+
127
+ const handleDragStart = (e: React.DragEvent) => {
128
+ e.stopPropagation()
129
+ e.dataTransfer.effectAllowed = 'move'
130
+ dragCtx?.onDragStart(path, index)
131
+ }
132
+
133
+ const handleDragOver = (e: React.DragEvent) => {
134
+ e.preventDefault()
135
+ e.stopPropagation()
136
+ e.dataTransfer.dropEffect = 'move'
137
+ dragCtx?.onDragOver(path, index)
138
+ }
139
+
140
+ const handleDragEnd = (e: React.DragEvent) => {
141
+ e.stopPropagation()
142
+ dragCtx?.onDragEnd()
143
+ }
144
+
145
+ const isDropTarget = dragCtx?.dragOverPath === path && dragCtx?.dragOverIndex === index
52
146
 
53
147
  if (item.type === 'folder') {
148
+ const folderPath = `${path}/${item.name}`
149
+
54
150
  return (
55
- <div className="sidebar-folder">
151
+ <div
152
+ className={`sidebar-folder ${isDropTarget ? 'drop-target' : ''}`}
153
+ draggable
154
+ onDragStart={handleDragStart}
155
+ onDragOver={handleDragOver}
156
+ onDragEnd={handleDragEnd}
157
+ >
56
158
  <button
57
159
  className="sidebar-folder-toggle"
58
160
  onClick={() => setIsOpen(!isOpen)}
59
161
  style={{ paddingLeft: `${depth * 12 + 8}px` }}
60
162
  >
163
+ <span className="drag-handle">⠿</span>
61
164
  <span className={`folder-icon ${isOpen ? 'open' : ''}`}>
62
165
  <svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
63
166
  <path d="M4.5 2L8.5 6L4.5 10" stroke="currentColor" strokeWidth="1.5" fill="none"/>
@@ -66,11 +169,11 @@ function SidebarItem({ item, depth = 0 }: SidebarItemProps) {
66
169
  {item.name}
67
170
  </button>
68
171
  {isOpen && (
69
- <div className="sidebar-folder-children">
70
- {item.children.map((child, i) => (
71
- <SidebarItem key={i} item={child} depth={depth + 1} />
72
- ))}
73
- </div>
172
+ <DraggableFolderChildren
173
+ items={item.children}
174
+ path={folderPath}
175
+ depth={depth + 1}
176
+ />
74
177
  )}
75
178
  </div>
76
179
  )
@@ -80,13 +183,81 @@ function SidebarItem({ item, depth = 0 }: SidebarItemProps) {
80
183
  (item.url === '/' && location.pathname === '/')
81
184
 
82
185
  return (
83
- <Link
84
- to={item.url}
85
- className={`sidebar-link ${isActive ? 'active' : ''}`}
86
- style={{ paddingLeft: `${depth * 12 + 16}px` }}
186
+ <div
187
+ className={`sidebar-item-wrapper ${isDropTarget ? 'drop-target' : ''}`}
188
+ draggable
189
+ onDragStart={handleDragStart}
190
+ onDragOver={handleDragOver}
191
+ onDragEnd={handleDragEnd}
87
192
  >
88
- {item.name}
89
- </Link>
193
+ <Link
194
+ to={item.url}
195
+ className={`sidebar-link ${isActive ? 'active' : ''}`}
196
+ style={{ paddingLeft: `${depth * 12 + 16}px` }}
197
+ >
198
+ <span className="drag-handle">⠿</span>
199
+ {item.name}
200
+ </Link>
201
+ </div>
202
+ )
203
+ }
204
+
205
+ // Draggable folder children with their own order state
206
+ function DraggableFolderChildren({ items, path, depth }: { items: TreeItem[]; path: string; depth: number }) {
207
+ const [orderedItems, setOrderedItems] = useState<TreeItem[]>(() => applyOrder(items, path))
208
+ const dragCtx = useContext(DragContext)
209
+
210
+ // Update when items change
211
+ useEffect(() => {
212
+ setOrderedItems(applyOrder(items, path))
213
+ }, [items, path])
214
+
215
+ // Handle reorder when drag ends in this folder
216
+ useEffect(() => {
217
+ if (
218
+ dragCtx?.dragPath === path &&
219
+ dragCtx?.dragOverPath === path &&
220
+ dragCtx?.dragIndex !== null &&
221
+ dragCtx?.dragOverIndex !== null &&
222
+ dragCtx?.dragIndex !== dragCtx?.dragOverIndex
223
+ ) {
224
+ // Will be handled by parent's onDragEnd
225
+ }
226
+ }, [dragCtx, path])
227
+
228
+ // Listen for successful drops in this path
229
+ const prevDragCtx = useRef(dragCtx)
230
+ useEffect(() => {
231
+ const prev = prevDragCtx.current
232
+ if (
233
+ prev?.dragPath === path &&
234
+ prev?.dragOverPath === path &&
235
+ prev?.dragIndex !== null &&
236
+ prev?.dragOverIndex !== null &&
237
+ prev?.dragIndex !== prev?.dragOverIndex &&
238
+ dragCtx?.dragPath === null // drag ended
239
+ ) {
240
+ const newItems = [...orderedItems]
241
+ const [removed] = newItems.splice(prev.dragIndex, 1)
242
+ newItems.splice(prev.dragOverIndex, 0, removed)
243
+ setOrderedItems(newItems)
244
+ saveOrder(newItems, path)
245
+ }
246
+ prevDragCtx.current = dragCtx
247
+ }, [dragCtx, path, orderedItems])
248
+
249
+ return (
250
+ <div className="sidebar-folder-children">
251
+ {orderedItems.map((child, i) => (
252
+ <DraggableSidebarItem
253
+ key={getItemId(child)}
254
+ item={child}
255
+ index={i}
256
+ path={path}
257
+ depth={depth}
258
+ />
259
+ ))}
260
+ </div>
90
261
  )
91
262
  }
92
263
 
@@ -121,6 +292,41 @@ function ThemeToggle() {
121
292
  )
122
293
  }
123
294
 
295
+ function WidthToggle() {
296
+ const [isFullWidth, setIsFullWidth] = useState(() => {
297
+ if (typeof window !== 'undefined') {
298
+ return localStorage.getItem('content-full-width') === 'true'
299
+ }
300
+ return false
301
+ })
302
+
303
+ useEffect(() => {
304
+ document.documentElement.classList.toggle('full-width', isFullWidth)
305
+ }, [isFullWidth])
306
+
307
+ const toggle = () => {
308
+ const newFullWidth = !isFullWidth
309
+ setIsFullWidth(newFullWidth)
310
+ localStorage.setItem('content-full-width', String(newFullWidth))
311
+ }
312
+
313
+ return (
314
+ <button className="theme-toggle" onClick={toggle} aria-label="Toggle content width">
315
+ {isFullWidth ? (
316
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
317
+ <path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"/>
318
+ </svg>
319
+ ) : (
320
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
321
+ <path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/>
322
+ </svg>
323
+ )}
324
+ </button>
325
+ )
326
+ }
327
+
328
+ const ROOT_PATH = 'root'
329
+
124
330
  export function Layout({ tree, children }: LayoutProps) {
125
331
  const [collapsed, setCollapsed] = useState(() => {
126
332
  if (typeof window !== 'undefined') {
@@ -129,6 +335,13 @@ export function Layout({ tree, children }: LayoutProps) {
129
335
  return false
130
336
  })
131
337
 
338
+ const [orderedItems, setOrderedItems] = useState<TreeItem[]>(() => applyOrder(tree.children, ROOT_PATH))
339
+
340
+ const [dragPath, setDragPath] = useState<string | null>(null)
341
+ const [dragIndex, setDragIndex] = useState<number | null>(null)
342
+ const [dragOverPath, setDragOverPath] = useState<string | null>(null)
343
+ const [dragOverIndex, setDragOverIndex] = useState<number | null>(null)
344
+
132
345
  // Initialize theme from localStorage
133
346
  useEffect(() => {
134
347
  const saved = localStorage.getItem('theme')
@@ -137,6 +350,11 @@ export function Layout({ tree, children }: LayoutProps) {
137
350
  document.documentElement.classList.toggle('dark', isDark)
138
351
  }, [])
139
352
 
353
+ // Update ordered items when tree changes
354
+ useEffect(() => {
355
+ setOrderedItems(applyOrder(tree.children, ROOT_PATH))
356
+ }, [tree.children])
357
+
140
358
  // Persist collapsed state
141
359
  useEffect(() => {
142
360
  localStorage.setItem('sidebar-collapsed', String(collapsed))
@@ -144,43 +362,96 @@ export function Layout({ tree, children }: LayoutProps) {
144
362
 
145
363
  const toggleCollapsed = () => setCollapsed(!collapsed)
146
364
 
365
+ const handleDragStart = (path: string, index: number) => {
366
+ setDragPath(path)
367
+ setDragIndex(index)
368
+ }
369
+
370
+ const handleDragOver = (path: string, index: number) => {
371
+ // Only allow dropping in the same path (no cross-folder drops)
372
+ if (dragPath === path && dragIndex !== index) {
373
+ setDragOverPath(path)
374
+ setDragOverIndex(index)
375
+ }
376
+ }
377
+
378
+ const handleDragEnd = () => {
379
+ // Handle root-level reordering
380
+ if (
381
+ dragPath === ROOT_PATH &&
382
+ dragOverPath === ROOT_PATH &&
383
+ dragIndex !== null &&
384
+ dragOverIndex !== null &&
385
+ dragIndex !== dragOverIndex
386
+ ) {
387
+ const newItems = [...orderedItems]
388
+ const [removed] = newItems.splice(dragIndex, 1)
389
+ newItems.splice(dragOverIndex, 0, removed)
390
+ setOrderedItems(newItems)
391
+ saveOrder(newItems, ROOT_PATH)
392
+ }
393
+
394
+ setDragPath(null)
395
+ setDragIndex(null)
396
+ setDragOverPath(null)
397
+ setDragOverIndex(null)
398
+ }
399
+
147
400
  // Get top-level folders for collapsed rail
148
- const topFolders = tree.children.filter(
401
+ const topFolders = orderedItems.filter(
149
402
  (item): item is PageTree.Folder => item.type === 'folder'
150
403
  )
151
404
 
405
+ const dragContextValue: DragContextType = {
406
+ dragPath,
407
+ dragIndex,
408
+ dragOverPath,
409
+ dragOverIndex,
410
+ onDragStart: handleDragStart,
411
+ onDragOver: handleDragOver,
412
+ onDragEnd: handleDragEnd,
413
+ }
414
+
152
415
  return (
153
- <div className={`prev-layout ${collapsed ? 'sidebar-collapsed' : ''}`}>
154
- <aside className="prev-sidebar">
155
- <div className="sidebar-header">
156
- <SidebarToggle collapsed={collapsed} onToggle={toggleCollapsed} />
157
- </div>
158
- {collapsed ? (
159
- <div className="sidebar-rail">
160
- {topFolders.map((folder, i) => (
161
- <CollapsedFolderIcon
162
- key={i}
163
- item={folder}
164
- onClick={toggleCollapsed}
165
- />
166
- ))}
416
+ <DragContext.Provider value={dragContextValue}>
417
+ <div className={`prev-layout ${collapsed ? 'sidebar-collapsed' : ''}`}>
418
+ <aside className="prev-sidebar">
419
+ <div className="sidebar-header">
420
+ <SidebarToggle collapsed={collapsed} onToggle={toggleCollapsed} />
167
421
  </div>
168
- ) : (
169
- <>
170
- <nav className="sidebar-nav">
171
- {tree.children.map((item, i) => (
172
- <SidebarItem key={i} item={item} />
422
+ {collapsed ? (
423
+ <div className="sidebar-rail">
424
+ {topFolders.map((folder, i) => (
425
+ <CollapsedFolderIcon
426
+ key={i}
427
+ item={folder}
428
+ onClick={toggleCollapsed}
429
+ />
173
430
  ))}
174
- </nav>
175
- <div className="sidebar-footer">
176
- <ThemeToggle />
177
431
  </div>
178
- </>
179
- )}
180
- </aside>
181
- <main className="prev-main">
182
- {children}
183
- </main>
184
- </div>
432
+ ) : (
433
+ <>
434
+ <nav className="sidebar-nav">
435
+ {orderedItems.map((item, i) => (
436
+ <DraggableSidebarItem
437
+ key={getItemId(item)}
438
+ item={item}
439
+ index={i}
440
+ path={ROOT_PATH}
441
+ />
442
+ ))}
443
+ </nav>
444
+ <div className="sidebar-footer">
445
+ <WidthToggle />
446
+ <ThemeToggle />
447
+ </div>
448
+ </>
449
+ )}
450
+ </aside>
451
+ <main className="prev-main">
452
+ {children}
453
+ </main>
454
+ </div>
455
+ </DragContext.Provider>
185
456
  )
186
457
  }
@@ -4,8 +4,6 @@ type FrontmatterValue = string | number | boolean | string[] | unknown
4
4
 
5
5
  interface MetadataBlockProps {
6
6
  frontmatter: Record<string, FrontmatterValue>
7
- title?: string
8
- description?: string
9
7
  }
10
8
 
11
9
  // Fields to skip (shown elsewhere or internal)
@@ -102,7 +100,7 @@ function renderValue(key: string, value: FrontmatterValue): React.ReactNode {
102
100
  }
103
101
  }
104
102
 
105
- export function MetadataBlock({ frontmatter, title, description }: MetadataBlockProps) {
103
+ export function MetadataBlock({ frontmatter }: MetadataBlockProps) {
106
104
  // Filter out skipped fields and empty values
107
105
  const fields = Object.entries(frontmatter).filter(
108
106
  ([key, value]) => !SKIP_FIELDS.has(key) && value !== undefined && value !== null && value !== ''
@@ -111,36 +109,27 @@ export function MetadataBlock({ frontmatter, title, description }: MetadataBlock
111
109
  // Check for draft status
112
110
  const isDraft = frontmatter.draft === true
113
111
 
114
- // Only show title/description if they came from frontmatter (not extracted from H1)
115
- const hasExplicitTitle = 'title' in frontmatter && frontmatter.title
116
- const hasExplicitDescription = 'description' in frontmatter && frontmatter.description
117
-
118
- // If no explicit metadata and no other fields, don't render the block
119
- if (fields.length === 0 && !hasExplicitTitle && !hasExplicitDescription && !isDraft) {
112
+ // If no fields to show, don't render
113
+ if (fields.length === 0 && !isDraft) {
120
114
  return null
121
115
  }
122
116
 
123
117
  return (
124
118
  <div className="metadata-block">
125
- {hasExplicitTitle && <h1 className="metadata-title">{title}</h1>}
126
- {hasExplicitDescription && <p className="metadata-description">{description}</p>}
127
-
128
- {(fields.length > 0 || isDraft) && (
129
- <div className="metadata-fields">
130
- {isDraft && (
131
- <span className="metadata-field metadata-draft">
132
- <span className="metadata-chip is-draft">Draft</span>
133
- </span>
134
- )}
135
- {fields.filter(([key]) => key !== 'draft').map(([key, value]) => (
136
- <span key={key} className="metadata-field">
137
- {FIELD_ICONS[key] && <span className="metadata-icon">{FIELD_ICONS[key]}</span>}
138
- <span className="metadata-key">{key}:</span>
139
- {renderValue(key, value)}
140
- </span>
141
- ))}
142
- </div>
143
- )}
119
+ <div className="metadata-fields">
120
+ {isDraft && (
121
+ <span className="metadata-field metadata-draft">
122
+ <span className="metadata-chip is-draft">Draft</span>
123
+ </span>
124
+ )}
125
+ {fields.filter(([key]) => key !== 'draft').map(([key, value]) => (
126
+ <span key={key} className="metadata-field">
127
+ {FIELD_ICONS[key] && <span className="metadata-icon">{FIELD_ICONS[key]}</span>}
128
+ <span className="metadata-key">{key}:</span>
129
+ {renderValue(key, value)}
130
+ </span>
131
+ ))}
132
+ </div>
144
133
  </div>
145
134
  )
146
135
  }
@@ -74,11 +74,7 @@ function PageWrapper({ Component, meta }: { Component: React.ComponentType; meta
74
74
  return (
75
75
  <>
76
76
  {meta.frontmatter && Object.keys(meta.frontmatter).length > 0 && (
77
- <MetadataBlock
78
- frontmatter={meta.frontmatter}
79
- title={meta.title}
80
- description={meta.description}
81
- />
77
+ <MetadataBlock frontmatter={meta.frontmatter} />
82
78
  )}
83
79
  <Component />
84
80
  </>
@@ -165,6 +165,8 @@ body {
165
165
  .sidebar-footer {
166
166
  padding: 1rem;
167
167
  border-top: 1px solid var(--fd-border);
168
+ display: flex;
169
+ gap: 0.5rem;
168
170
  }
169
171
 
170
172
  .theme-toggle {
@@ -597,25 +599,7 @@ body {
597
599
  =========================================== */
598
600
 
599
601
  .metadata-block {
600
- margin-bottom: 2rem;
601
- padding-bottom: 1.5rem;
602
- border-bottom: 1px solid var(--fd-border);
603
- }
604
-
605
- .metadata-title {
606
- font-size: 2.25rem;
607
- font-weight: 700;
608
- margin: 0 0 0.5rem 0;
609
- line-height: 1.2;
610
- letter-spacing: -0.025em;
611
- color: var(--fd-foreground);
612
- }
613
-
614
- .metadata-description {
615
- font-size: 1.125rem;
616
- color: var(--fd-muted-foreground);
617
- margin: 0 0 1rem 0;
618
- line-height: 1.5;
602
+ margin-bottom: 1.5rem;
619
603
  }
620
604
 
621
605
  .metadata-fields {
@@ -697,3 +681,54 @@ body {
697
681
  .metadata-number {
698
682
  font-variant-numeric: tabular-nums;
699
683
  }
684
+
685
+ /* ===========================================
686
+ Full Width Mode
687
+ =========================================== */
688
+
689
+ .full-width .prev-content {
690
+ max-width: none;
691
+ }
692
+
693
+ /* ===========================================
694
+ Drag and Drop Sidebar
695
+ =========================================== */
696
+
697
+ .sidebar-item-wrapper {
698
+ position: relative;
699
+ }
700
+
701
+ .drag-handle {
702
+ opacity: 0;
703
+ cursor: grab;
704
+ color: var(--fd-muted-foreground);
705
+ font-size: 0.75rem;
706
+ margin-right: 0.25rem;
707
+ transition: opacity 0.15s ease;
708
+ }
709
+
710
+ .sidebar-folder:hover .drag-handle,
711
+ .sidebar-item-wrapper:hover .drag-handle,
712
+ .sidebar-link:hover .drag-handle {
713
+ opacity: 0.5;
714
+ }
715
+
716
+ .drag-handle:hover {
717
+ opacity: 1 !important;
718
+ }
719
+
720
+ .sidebar-folder.drop-target,
721
+ .sidebar-item-wrapper.drop-target {
722
+ background: var(--fd-accent);
723
+ border-radius: 0.375rem;
724
+ }
725
+
726
+ .sidebar-folder[draggable="true"],
727
+ .sidebar-item-wrapper[draggable="true"] {
728
+ cursor: grab;
729
+ }
730
+
731
+ .sidebar-folder[draggable="true"]:active,
732
+ .sidebar-item-wrapper[draggable="true"]:active {
733
+ cursor: grabbing;
734
+ }