release(v1.9.3): unify folder icons across tree & strip, add “paper” lines, live color sync, and vendor-aware release

This commit is contained in:
Ryan
2025-11-11 00:09:15 -05:00
committed by GitHub
parent a031fc99c2
commit dbdf760d4d
7 changed files with 1817 additions and 1311 deletions

View File

@@ -21,15 +21,10 @@ permissions:
jobs:
release:
runs-on: ubuntu-latest
# Only run on:
# - push (master + version.js path filter already enforces that)
# - manual dispatch
if: |
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch'
# Duplicate safety; also step "Skip if tag exists" will no-op if already released.
concurrency:
group: release-${{ github.event_name }}-${{ github.run_id }}
cancel-in-progress: false
@@ -46,17 +41,14 @@ jobs:
else
REF_IN="master"
fi
# Resolve to a commit sha (allow branches or shas)
if git ls-remote --exit-code --heads https://github.com/${{ github.repository }}.git "$REF_IN" >/dev/null 2>&1; then
REF="$REF_IN"
else
# Accept SHAs too; well let checkout validate
REF="$REF_IN"
fi
else
REF="${{ github.sha }}"
fi
echo "ref=$REF" >> "$GITHUB_OUTPUT"
echo "Using ref=$REF"
@@ -75,7 +67,6 @@ jobs:
if [[ -n "${{ github.event.inputs.version || '' }}" ]]; then
VER="${{ github.event.inputs.version }}"
else
# Parse APP_VERSION from public/js/version.js (expects vX.Y.Z)
if [[ ! -f public/js/version.js ]]; then
echo "public/js/version.js not found; cannot auto-detect version." >&2
exit 1
@@ -86,7 +77,6 @@ jobs:
exit 1
fi
fi
echo "version=$VER" >> "$GITHUB_OUTPUT"
echo "Detected version: $VER"
@@ -124,20 +114,67 @@ jobs:
./ staging/
bash ./scripts/stamp-assets.sh "${VER}" "$(pwd)/staging"
- name: Verify placeholders removed
# --- PHP + Composer for vendor/ (production) ---
- name: Setup PHP
if: steps.tagcheck.outputs.exists == 'false'
id: php
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer:v2
extensions: mbstring, json, curl, dom, fileinfo, openssl, zip
coverage: none
ini-values: memory_limit=-1
- name: Cache Composer downloads
if: steps.tagcheck.outputs.exists == 'false'
uses: actions/cache@v4
with:
path: |
~/.composer/cache
~/.cache/composer
key: composer-${{ runner.os }}-php-${{ steps.php.outputs.php-version }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
composer-${{ runner.os }}-php-${{ steps.php.outputs.php-version }}-
- name: Install PHP dependencies into staging
if: steps.tagcheck.outputs.exists == 'false'
env:
COMPOSER_MEMORY_LIMIT: -1
shell: bash
run: |
set -euo pipefail
pushd staging >/dev/null
if [[ -f composer.json ]]; then
composer install \
--no-dev \
--prefer-dist \
--no-interaction \
--no-progress \
--optimize-autoloader \
--classmap-authoritative
test -f vendor/autoload.php || (echo "Composer install did not produce vendor/autoload.php" >&2; exit 1)
else
echo "No composer.json in staging; skipping vendor install."
fi
popd >/dev/null
# --- end Composer ---
- name: Verify placeholders removed (skip vendor/)
if: steps.tagcheck.outputs.exists == 'false'
shell: bash
run: |
set -euo pipefail
ROOT="$(pwd)/staging"
if grep -R -n -E "{{APP_QVER}}|{{APP_VER}}" "$ROOT" \
--exclude-dir=vendor --exclude-dir=vendor-bin \
--include='*.html' --include='*.php' --include='*.css' --include='*.js' 2>/dev/null; then
echo "Unreplaced placeholders found in staging." >&2
exit 1
fi
echo "OK: No unreplaced placeholders."
- name: Zip artifact
- name: Zip artifact (includes vendor/)
if: steps.tagcheck.outputs.exists == 'false'
shell: bash
run: |

View File

@@ -1,5 +1,42 @@
# Changelog
## Changes 11/11/2025 (v1.9.3)
release(v1.9.3): unify folder icons across tree & strip, add “paper” lines, live color sync, and vendor-aware release
- UI / Icons
- Replace Material icon in folder strip with shared `folderSVG()` and export it for reuse. Adds clipPaths, subtle gradients, and `shape-rendering: geometricPrecision` to eliminate the tiny seam.
- Add ruled “paper” lines and blue handwriting dashes; CSS for `.paper-line` and `.paper-ink` included.
- Match strokes between tree (24px) and strip (48px) so both look identical; round joins/caps to avoid nicks.
- Polish folder strip layout & hover: tighter spacing, centered icon+label, improved wrapping.
- Folder color & non-empty detection
- Live color sync: after saving a color we dispatch `folderColorChanged`; strip repaints and tree refreshes.
- Async strip icon: paint immediately, then flip to “paper” if the folder has contents. HSL helpers compute front/back/stroke shades.
- FileList strip
- Render subfolders with `<span class="folder-svg">` + name, wire context menu actions (move, color, share, etc.), and attach icons for each tile.
- Exports & helpers
- Export `openColorFolderModal(...)` and `openMoveFolderUI(...)` for the strip and toolbar; use `refreshFolderIcon(...)` after ops to keep icons current.
- AppCore
- Update file upload DnD relay hook to `#fileList` (id rename).
- CSS tweaks
- Bring tree icon stroke/paint rules in line with the strip, add scribble styles, and adjust margins/spacing.
- CI/CD (release)
- Build PHP dependencies during release: setup PHP 8.3 + Composer, cache downloads, install into `staging/vendor/`, exclude `vendor/` from placeholder checks, and ship artifact including `vendor/`.
- Changelog highlights
- Sharper, seam-free folder SVGs shared across tree & strip, with paper lines + handwriting accents.
- Real-time folder color propagation between views.
- Folder strip switched to SVG tiles with better layout + context actions.
- Release pipeline now produces a ready-to-run zip that includes `vendor/`.
---
## Changes 11/10/2025 (v1.9.2)
release(v1.9.2): Upload modal + DnD relay from file list (with robust synthetic-drop fallback)

View File

@@ -739,11 +739,167 @@ body {
width: 100% !important;
}#fileList table tr:nth-child(even) {
background-color: transparent;
}#fileList table tr:hover {
background-color: #e0e0e0;
}.dark-mode #fileList table tr:hover {
background-color: #444;
}#fileListTitle {
}
/* --- File list rows: match folder-tree hover/selected --- */
:root {
--filr-row-hover-bg: rgba(122,179,255,.14);
--filr-row-selected-bg: rgba(122,179,255,.24);
}
/* Let cell corners round like a pill */
#fileList table {
border-collapse: separate;
border-spacing: 0;
}
/* ===== Reset any conflicting backgrounds (Bootstrap etc.) inside #fileList only ===== */
#fileList table tbody tr,
#fileList table tbody tr > td {
background-color: transparent !important;
}
/* Kill Bootstrap hover/zebra just for this table */
#fileList table.table-hover tbody tr:hover > * { background-color: transparent !important; }
#fileList table.table-striped > tbody > tr:nth-of-type(odd) > * { background-color: transparent !important; }
/* ===== Our look, scoped to the table we tagged in JS ===== */
#fileList table.filr-table tbody tr,
#fileList table.filr-table tbody td {
transition: background-color .12s ease;
}
/* Hover (when not selected) */
#fileList table.filr-table tbody tr:hover:not(.selected, .row-selected, .selected-row, .is-selected) > td {
background: var(--filr-row-hover-bg) !important;
}
/* Selected (support a few legacy class names just in case) */
#fileList table.filr-table tbody tr.selected > td,
#fileList table.filr-table tbody tr.row-selected > td,
#fileList table.filr-table tbody tr.selected-row > td,
#fileList table.filr-table tbody tr.is-selected > td {
background: var(--filr-row-selected-bg) !important;
box-shadow: inset 0 0 0 1px rgba(122,179,255,.45);
}
/* Rounded “pill” ends on hover/selected */
#fileList table.filr-table tbody tr:hover:not(.selected, .row-selected, .selected-row, .is-selected) > td:first-child,
#fileList table.filr-table tbody tr.selected > td:first-child,
#fileList table.filr-table tbody tr.row-selected > td:first-child,
#fileList table.filr-table tbody tr.selected-row > td:first-child,
#fileList table.filr-table tbody tr.is-selected > td:first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
#fileList table.filr-table tbody tr:hover:not(.selected, .row-selected, .selected-row, .is-selected) > td:last-child,
#fileList table.filr-table tbody tr.selected > td:last-child,
#fileList table.filr-table tbody tr.row-selected > td:last-child,
#fileList table.filr-table tbody tr.selected-row > td:last-child,
#fileList table.filr-table tbody tr.is-selected > td:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
/* Keyboard focus visibility */
#fileList table.filr-table tbody tr:focus-within > td {
outline: 2px solid #8ab4f8;
outline-offset: -2px;
border-top-left-radius: 8px; border-bottom-left-radius: 8px;
border-top-right-radius: 8px; border-bottom-right-radius: 8px;
}
.dark-mode #fileList table.filr-table tbody tr:hover:not(.selected, .row-selected, .selected-row, .is-selected) > td {
background: var(--filr-row-hover-bg) !important;
}
.dark-mode #fileList table.filr-table tbody tr.selected > td,
.dark-mode #fileList table.filr-table tbody tr.row-selected > td,
.dark-mode #fileList table.filr-table tbody tr.selected-row > td,
.dark-mode #fileList table.filr-table tbody tr.is-selected > td {
background: var(--filr-row-selected-bg) !important;
}
#fileList table.filr-table {
--bs-table-hover-color: inherit;
--bs-table-striped-color: inherit;
}
#fileList table.table-hover tbody tr:hover,
#fileList table.table-hover tbody tr:hover > td {
color: inherit !important;
}
.dark-mode #fileList table.filr-table tbody td a,
.dark-mode #fileList table.filr-table tbody td a:hover {
color: inherit !important;
}
:root{
--filr-row-outline: rgba(122,179,255,.45);
--filr-row-outline-hover: rgba(122,179,255,.35);
}
#fileList table.filr-table > :not(caption) > * > * { border: 0 !important; }
#fileList table.filr-table td,
#fileList table.filr-table th { box-shadow: none !important; }
#fileList table.filr-table tbody tr.selected > td,
#fileList table.filr-table tbody tr.row-selected > td,
#fileList table.filr-table tbody tr.selected-row > td,
#fileList table.filr-table tbody tr.is-selected > td {
background: var(--filr-row-selected-bg) !important;
box-shadow:
inset 0 1px 0 var(--filr-row-outline),
inset 0 -1px 0 var(--filr-row-outline);
}
#fileList table.filr-table tbody tr.selected > td:first-child,
#fileList table.filr-table tbody tr.row-selected > td:first-child,
#fileList table.filr-table tbody tr.selected-row > td:first-child,
#fileList table.filr-table tbody tr.is-selected > td:first-child {
box-shadow:
inset 1px 0 0 var(--filr-row-outline),
inset 0 1px 0 var(--filr-row-outline),
inset 0 -1px 0 var(--filr-row-outline);
border-top-left-radius: 8px; border-bottom-left-radius: 8px;
}
#fileList table.filr-table tbody tr.selected > td:last-child,
#fileList table.filr-table tbody tr.row-selected > td:last-child,
#fileList table.filr-table tbody tr.selected-row > td:last-child,
#fileList table.filr-table tbody tr.is-selected > td:last-child {
box-shadow:
inset -1px 0 0 var(--filr-row-outline),
inset 0 1px 0 var(--filr-row-outline),
inset 0 -1px 0 var(--filr-row-outline);
border-top-right-radius: 8px; border-bottom-right-radius: 8px;
}
#fileList table.filr-table tbody tr:hover:not(.selected, .row-selected, .selected-row, .is-selected) > td {
background: var(--filr-row-hover-bg) !important;
box-shadow:
inset 0 1px 0 var(--filr-row-outline-hover),
inset 0 -1px 0 var(--filr-row-outline-hover);
}
#fileList table.filr-table tbody tr:hover:not(.selected, .row-selected, .selected-row, .is-selected) > td:first-child {
box-shadow:
inset 1px 0 0 var(--filr-row-outline-hover),
inset 0 1px 0 var(--filr-row-outline-hover),
inset 0 -1px 0 var(--filr-row-outline-hover);
border-top-left-radius: 8px; border-bottom-left-radius: 8px;
}
#fileList table.filr-table tbody tr:hover:not(.selected, .row-selected, .selected-row, .is-selected) > td:last-child {
box-shadow:
inset -1px 0 0 var(--filr-row-outline-hover),
inset 0 1px 0 var(--filr-row-outline-hover),
inset 0 -1px 0 var(--filr-row-outline-hover);
border-top-right-radius: 8px; border-bottom-right-radius: 8px;
}
#fileList table.filr-table tbody tr:focus-within > td { outline: none; }
#fileList table.filr-table tbody tr:focus-within > td:first-child,
#fileList table.filr-table tbody tr:focus-within > td:last-child {
outline: 2px solid #8ab4f8; outline-offset: -2px;
}
#fileListTitle {
white-space: normal !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
@@ -1000,7 +1156,7 @@ body {
#fileListTitle {
font-size: 1.8em;
margin-top: 10px;
margin-bottom: 15px;
margin-bottom: 10px;
}.file-list-actions {
display: flex;
flex-wrap: wrap;
@@ -1185,7 +1341,7 @@ body {
}.folder-tree.expanded {
display: block;
}.folder-item {
margin: 4px 0;
margin: 2px 0;
display: block;
}.folder-toggle {
cursor: pointer;
@@ -1432,8 +1588,6 @@ body {
}.dark-mode table {
background-color: #2c2c2c;
color: #e0e0e0;
}.dark-mode table tr:hover {
background-color: #444;
}.dark-mode #uploadProgressContainer .progress {
background-color: #333;
}.dark-mode #uploadProgressContainer .progress-bar {
@@ -1876,34 +2030,74 @@ body {
font-weight: 500;
vertical-align: middle;
white-space: nowrap;
}.folder-strip-container {
display: flex;
}
.folder-strip-container {
display: flex;
padding-top: 0px !important;
flex-wrap: wrap;
gap: 12px;
padding: 8px 0;
}.folder-strip-container .folder-item {
display: flex;
gap: 10px 14px;
align-content: flex-start; /* multi-line wrap stays top-aligned */
padding: 6px 4px;
}
.folder-strip-container .folder-item {
display: flex;
padding-top: 0px !important;
flex-direction: column;
align-items: center;
cursor: pointer;
width: 80px;
align-items: center; /* horizontal (cross-axis) center */
justify-content: center; /* vertical (main-axis) center */
min-width: 0;
gap: 2px !important;
padding: 6px 8px;
box-sizing: border-box;
border-radius: 10px;
color: inherit;
font-size: 0.85em;
}.folder-strip-container .folder-item i.material-icons {
font-size: 28px;
margin-bottom: 4px;
}.folder-strip-container .folder-name {
transition: transform .12s ease, box-shadow .12s ease, background-color .12s ease;
}
.folder-strip-container .folder-item .folder-svg {
line-height: 0;
margin-bottom: 0px;
}
.folder-strip-container .folder-item .folder-svg svg {
width: 48px;
height: 48px;
display: block;
}
.folder-strip-container .folder-name {
display: block;
width: 100%;
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
hyphens: auto;
text-align: center;
white-space: normal;
word-break: break-word;
max-width: 80px;
margin-top: 4px;
}.folder-strip-container .folder-item i.material-icons {
color: currentColor;
}.folder-strip-container .folder-item:hover {
background-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}:root {
overflow: visible;
text-overflow: clip;
line-height: 1.2;
}
.folder-strip-container .folder-item:hover {
transform: translateY(-1px) scale(1.04);
background-color: rgba(0, 0, 0, 0.04); /* light mode */
box-shadow: 0 4px 10px rgba(0, 0, 0, .15);
}
/* Dark mode hover */
body.dark-mode .folder-strip-container .folder-item:hover {
background-color: rgba(255, 255, 255, 0.06);
box-shadow: 0 6px 16px rgba(0, 0, 0, .45);
}
/* Optional: keyboard focus */
.folder-strip-container .folder-item:focus-visible {
outline: 2px solid #8ab4f8;
outline-offset: 2px;
}
:root {
--perm-caret: #444;
}/* light */
.dark-mode {
@@ -2005,170 +2199,217 @@ body {
#downloadProgressBarOuter { height: 10px; }
/* ===== FileRise Folder Tree: unified, crisp, aligned ===== */
/* Knobs (size, spacing, colors) */
/* ===== Folder Tree theme + structure ===== */
#folderTreeContainer {
/* Colors (used in BOTH themes) */
--filr-folder-front: #f6b84e; /* front/lip */
--filr-folder-back: #ffd36e; /* back body */
--filr-folder-stroke:#a87312; /* outline */
--filr-paper-fill: #ffffff; /* paper */
--filr-paper-stroke: #b2c2db; /* paper edges/lines */
/* Size & spacing */
--row-h: 28px; /* row height */
--twisty: 24px; /* chevron hit-area size */
--twisty-gap: -5px; /* gap between chevron and row content */
--icon-size: 24px; /* 2226 look good */
--icon-gap: 6px; /* space between icon and label */
--indent: 10px; /* subtree indent */
}
/* Keep the same yellow/orange in dark mode; boost paper contrast a touch */
.dark-mode #folderTreeContainer {
/* Theme vars (light mode defaults) */
--filr-folder-front: #f6b84e;
--filr-folder-back: #ffd36e;
--filr-folder-stroke:#a87312;
--filr-paper-fill: #ffffff;
--filr-paper-stroke: #d0def7; /* brighter so it pops on dark */
--filr-paper-stroke: #9fb3d6; /* slightly darker for sharper paper */
/* Sizes */
--row-h: 28px;
--twisty: 24px;
--twisty-gap: -5px;
--icon-size: 24px;
--icon-gap: 6px;
--indent: 10px;
}
#folderTreeContainer .folder-item { position: static; padding-left: 0; }
/* visible “row” for each node */
#folderTreeContainer .folder-item { position: static; padding-left: 0; }
#folderTreeContainer .folder-item > .folder-tree { margin-left: var(--indent); }
#folderTreeContainer .folder-row {
position: relative;
display: flex;
align-items: center;
height: var(--row-h);
min-height: var(--row-h);
height: auto;
padding-left: calc(var(--twisty) + var(--twisty-gap));
}
/* children indent */
#folderTreeContainer .folder-item > .folder-tree { margin-left: var(--indent); }
/* ---------- Chevron toggle (twisty) ---------- */
#folderTreeContainer .folder-row > button.folder-toggle {
/* Chevron + spacer (centered vertically) */
#folderTreeContainer .folder-row > button.folder-toggle,
#folderTreeContainer .folder-row > .folder-spacer {
position: absolute; left: 0; top: 50%; transform: translateY(-50%);
width: var(--twisty); height: var(--twisty);
display: inline-flex; align-items: center; justify-content: center;
border: 1px solid transparent; border-radius: 6px;
background: transparent; cursor: pointer;
}
#folderTreeContainer .folder-row > button.folder-toggle::before {
content: "▸"; /* closed */
font-size: calc(var(--twisty) * 0.8);
line-height: 1;
content: "▸"; font-size: calc(var(--twisty) * 0.8); line-height: 1;
}
#folderTreeContainer li[role="treeitem"][aria-expanded="true"]
> .folder-row > button.folder-toggle::before { content: "▾"; }
/* root row (it's a <div>) */
> .folder-row > button.folder-toggle::before,
#rootRow[aria-expanded="true"] > button.folder-toggle::before { content: "▾"; }
#folderTreeContainer .folder-row > button.folder-toggle:hover {
border-color: color-mix(in srgb, #7ab3ff 35%, transparent);
}
/* spacer for leaves so labels align with parents that have a button */
#folderTreeContainer .folder-row > .folder-spacer {
position: absolute; left: 0; top: 50%; transform: translateY(-50%);
width: var(--twisty); height: var(--twisty); display: inline-block;
}
/* Row "pill" that hugs content and wraps */
#folderTreeContainer .folder-option {
display: inline-flex;
flex: 0 1 auto; /* don't stretch full width */
align-items: center;
height: var(--row-h);
line-height: 1.2; /* avoids baseline weirdness */
gap: var(--icon-gap);
height: auto;
min-height: var(--row-h);
padding: 0 8px;
border-radius: 8px;
line-height: 1.2;
user-select: none;
white-space: nowrap;
max-width: 100%;
gap: var(--icon-gap);
white-space: normal; /* allow wrapping */
}
#folderTreeContainer .folder-label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transform: translateY(0.5px); /* tiny optical nudge for text */
}
/* ---------- Icon box (size & alignment) ---------- */
#folderTreeContainer .folder-icon {
flex: 0 0 var(--icon-size);
width: var(--icon-size);
height: var(--icon-size);
display: inline-flex;
align-items: center;
justify-content: center;
transform: translateY(0.5px); /* tiny optical nudge for SVG */
}
#folderTreeContainer .folder-icon svg {
width: 100%;
height: 100%;
display: block;
shape-rendering: geometricPrecision;
}
/* ---------- Crisp colors & strokes for the SVG parts ---------- */
#folderTreeContainer .folder-icon .paper {
fill: var(--filr-paper-fill);
stroke: var(--filr-paper-stroke);
stroke-width: 1.5; /* thick so it reads at 24px */
paint-order: stroke fill;
}
#folderTreeContainer .folder-icon .paper-fold {
fill: var(--filr-paper-stroke);
}
#folderTreeContainer .folder-icon .paper-line {
stroke: var(--filr-paper-stroke);
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.95;
}
/* subtle highlight along lip to add depth */
#folderTreeContainer .folder-icon .lip-highlight {
stroke: #ffffff;
stroke-opacity: .35;
stroke-width: 0.9;
fill: none;
vector-effect: non-scaling-stroke;
}
/* ---------- Hover / Selected ---------- */
#folderTreeContainer .folder-option:hover {
background: rgba(122,179,255,.14);
}
#folderTreeContainer .folder-option.selected {
background: rgba(122,179,255,.24);
box-shadow: inset 0 0 0 1px rgba(122,179,255,.45);
}
/* variables will be set inline per .folder-option when user colors a folder */
/* Label must be shrinkable so wrapping works */
#folderTreeContainer .folder-label {
flex: 1 1 120px;
min-width: 0;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
line-height: 1.2;
}
/* Icon box */
#folderTreeContainer .folder-icon {
flex: 0 0 var(--icon-size);
width: var(--icon-size); height: var(--icon-size);
display: inline-flex; align-items: center; justify-content: center;
}
#folderTreeContainer .folder-icon svg {
width: 100%; height: 100%; display: block;
shape-rendering: geometricPrecision;
}
/* Make folder tree outline match folder strip */
#folderTreeContainer .folder-icon .folder-front,
#folderTreeContainer .folder-icon .folder-back {
fill: currentColor;
stroke: var(--filr-folder-stroke);
stroke-width: 1.1;
stroke-width: .5;
paint-order: fill stroke;
stroke-linejoin: round;
stroke-linecap: round;
vector-effect: non-scaling-stroke;
paint-order: stroke fill;
}
#folderTreeContainer .folder-icon .folder-front { color: var(--filr-folder-front); }
#folderTreeContainer .folder-icon .folder-back { color: var(--filr-folder-back); }
#folderTreeContainer .folder-icon .paper {
fill: var(--filr-paper-fill);
stroke: var(--filr-paper-stroke);
stroke-width: 1.5;
paint-order: stroke fill;
}
#folderTreeContainer .folder-icon .paper-fold { fill: var(--filr-paper-stroke); }
#folderTreeContainer .folder-icon .paper-line {
stroke: var(--filr-paper-stroke); stroke-width: 1.5;
stroke-linecap: round; fill: none; opacity: .95;
}
#folderTreeContainer .folder-icon .lip-highlight {
stroke: #ffffff; stroke-opacity: .35; stroke-width: .9;
fill: none; vector-effect: non-scaling-stroke;
}
#folderTreeContainer .folder-icon,
#folderTreeContainer .folder-label { transform: none !important; }
/* ===== File List Strip color the shared folderSVG() ===== */
.folder-strip-container .folder-svg svg {
display: block;
shape-rendering: crispEdges;
}
.folder-strip-container .folder-item {
/* defaults — overridden per-tile via inline CSS vars set in JS */
--filr-folder-front: #f6b84e;
--filr-folder-back: #ffd36e;
--filr-folder-stroke: #a87312;
}
.folder-strip-container .folder-svg .folder-front,
.folder-strip-container .folder-svg .folder-back {
fill: currentColor;
stroke: var(--filr-folder-stroke);
stroke-width: .5;
paint-order: fill stroke;
stroke-linejoin: round;
stroke-linecap: round;
}
.folder-strip-container .folder-svg .folder-front { color: var(--filr-folder-front); }
.folder-strip-container .folder-svg .folder-back { color: var(--filr-folder-back); }
.folder-strip-container .folder-svg .paper {
fill: #fff;
stroke: #b2c2db; /* light mode */
stroke-width: 1;
vector-effect: non-scaling-stroke;
}
.folder-strip-container .folder-svg .paper-fold { fill: #b2c2db; }
.folder-strip-container .folder-svg .paper-line {
stroke: #b2c2db; stroke-width: 1; stroke-linecap: round; fill: none;
vector-effect: non-scaling-stroke;
}
.folder-strip-container .folder-svg .lip-highlight {
stroke: rgba(255,255,255,.45); stroke-width: .8; fill: none;
vector-effect: non-scaling-stroke;
}
#folderTreeContainer .folder-icon .folder-front,
#folderTreeContainer .folder-icon .folder-back,
.folder-strip-container .folder-svg .folder-front,
.folder-strip-container .folder-svg .folder-back,
#folderTreeContainer .folder-icon .lip-highlight,
.folder-strip-container .folder-svg .lip-highlight {
stroke-linejoin: round;
stroke-linecap: round;
}
/* Make sure were not forcing crispEdges anywhere */
.folder-strip-container .folder-svg svg,
#folderTreeContainer .folder-icon svg { shape-rendering: geometricPrecision !important; }
@media (max-resolution: 1.5dppx) {
#folderTreeContainer .folder-icon .folder-front,
#folderTreeContainer .folder-icon .folder-back { stroke-width: .6; }
}
/* Scribble (the handwriting line) */
#folderTreeContainer .folder-icon .paper-ink,
.folder-strip-container .folder-svg .paper-ink {
stroke: #4da3ff;
stroke-width: .9;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
opacity: .85;
paint-order: normal;
}
/* tree @ 24px icon */
#folderTreeContainer .folder-icon .folder-front,
#folderTreeContainer .folder-icon .folder-back,
#folderTreeContainer .folder-icon .paper-line,
#folderTreeContainer .folder-icon .paper-ink,
#folderTreeContainer .folder-icon .lip-highlight { stroke-width: .6px; }
/* strip @ 48px icon (2× bigger), halve stroke width to look the same */
.folder-strip-container .folder-svg .folder-front,
.folder-strip-container .folder-svg .folder-back,
.folder-strip-container .folder-svg .paper-line,
.folder-strip-container .folder-svg .paper-ink,
.folder-strip-container .folder-svg .lip-highlight { stroke-width: 1.1px; }

View File

@@ -100,7 +100,7 @@ export function initializeApp() {
// Hook DnD relay from fileList area into upload area
const fileListArea = document.getElementById('fileListContainer');
const fileListArea = document.getElementById('fileList');
if (fileListArea) {
let hoverTimer = null;

View File

@@ -156,7 +156,7 @@ export function buildSearchAndPaginationControls({ currentPage, totalPages, sear
export function buildFileTableHeader(sortOrder) {
return `
<table class="table">
<table class="table filr-table table-hover table-striped">
<thead>
<tr>
<th class="checkbox-col"><input type="checkbox" id="selectAll"></th>
@@ -283,9 +283,9 @@ export function updateRowHighlight(checkbox) {
const row = checkbox.closest('tr');
if (!row) return;
if (checkbox.checked) {
row.classList.add('row-selected');
row.classList.add('row-selected', 'selected');
} else {
row.classList.remove('row-selected');
row.classList.remove('row-selected', 'selected');
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -367,12 +367,18 @@ async function saveFolderColor(folder, colorHexOrEmpty) {
if (!res.ok || data.error) throw new Error(data.error || `HTTP ${res.status}`);
// update local map & apply
if (data.color) window.folderColorMap[folder] = data.color;
else delete window.folderColorMap[folder];
applyFolderColorToOption(folder, data.color || '');
return data;
else delete window.folderColorMap[folder];
applyFolderColorToOption(folder, data.color || '');
// notify other views (fileListView's strip)
window.dispatchEvent(new CustomEvent('folderColorChanged', {
detail: { folder, color: data.color || '' }
}));
return data;
}
function openColorFolderModal(folder) {
export function openColorFolderModal(folder) {
const existing = window.folderColorMap[folder] || '';
const defaultHex = existing || '#f6b84e';
@@ -559,39 +565,86 @@ const _nonEmptyCache = new Map();
Folder icon (SVG + fetch + cache)
----------------------*/
// Crisp emoji-like folder (empty / with paper)
function folderSVG(kind = 'empty') {
// shared by folder tree + folder strip
export function folderSVG(kind = 'empty') {
const gid = 'g' + Math.random().toString(36).slice(2, 8);
// tweak these
const PAPER_SHIFT_Y = -1.2; // move paper up (negative = up)
const INK_SHIFT_Y = -0.8; // extra lift for the blue lines
return `
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<!-- Angled back body -->
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"
style="display:block;shape-rendering:geometricPrecision">
<defs>
<clipPath id="${gid}-clipBack"><path d="M3.5 7.5 H10.5 L12.5 9.5 H20.5
C21.6 9.5 22.5 10.4 22.5 11.5 V19.5
C22.5 20.6 21.6 21.5 20.5 21.5 H5.5
C4.4 21.5 3.5 20.6 3.5 19.5 V9.5
C3.5 8.4 4.4 7.5 5.5 7.5 Z"/></clipPath>
<clipPath id="${gid}-clipFront"><path d="M2.5 10.5 H11.5 L13.5 8.5 H20.5
C21.6 8.5 22.5 9.4 22.5 10.5 V17.5
C22.5 18.6 21.6 19.5 20.5 19.5 H4.5
C3.4 19.5 2.5 18.6 2.5 17.5 V10.5 Z"/></clipPath>
<linearGradient id="${gid}-back" x1="4" y1="20" x2="20" y2="4" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#fff" stop-opacity="0"/>
<stop offset=".55" stop-color="#fff" stop-opacity=".10"/>
<stop offset="1" stop-color="#fff" stop-opacity="0"/>
</linearGradient>
<linearGradient id="${gid}-front" x1="6" y1="19" x2="19" y2="7" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#000" stop-opacity="0"/>
<stop offset="1" stop-color="#000" stop-opacity=".06"/>
</linearGradient>
</defs>
<!-- BACK -->
<g class="back-group" clip-path="url(#${gid}-clipBack)">
<path class="folder-back"
d="M3 7.4h7.6l1.6 1.8H20.3c1.1 0 2 .9 2 2v7.6c0 1.1-.9 2-2 2H5
c-1.1 0-2-.9-2-2V9.4c0-1.1.9-2 2-2z"/>
d="M3.5 7.5 H10.5 L12.5 9.5 H20.5
C21.6 9.5 22.5 10.4 22.5 11.5 V19.5
C22.5 20.6 21.6 21.5 20.5 21.5 H5.5
C4.4 21.5 3.5 20.6 3.5 19.5 V9.5
C3.5 8.4 4.4 7.5 5.5 7.5 Z"/>
<path d="M3.5 7.5 H10.5 L12.5 9.5 H20.5 V21.5 H3.5 Z"
fill="url(#${gid}-back)" pointer-events="none"/>
</g>
${kind === 'paper'
? `
<!-- Paper raised so it peeks above the lip -->
<rect class="paper" x="6.1" y="5.7" width="11.8" height="10.8" rx="1.2"/>
<!-- Bigger fold -->
<path class="paper-fold" d="M18.0 5.7h-3.2l3.2 3.2z"/>
<!-- Content lines -->
<path class="paper-line" d="M7.7 8.2h8.3"/>
<path class="paper-line" d="M7.7 9.8h7.2"/>
<path class="paper-line" d="M7.7 11.3h6.0"/>
`
: ''
}
${kind === 'paper' ? `
<!-- Move the entire paper block up (keep your existing shift if you use it) -->
<g class="paper-group" transform="translate(0, -1.2)">
<rect class="paper" x="6.5" y="6.5" width="11" height="10" rx="1"/>
<!-- Fold aligned to the paper's top-right corner (right edge = 17.5) -->
<path class="paper-fold" d="M17.5 6.5 H15.2 L17.5 9.0 Z"/>
<!-- Front lip (angled) -->
<!-- handwriting dashes -->
<g transform="translate(0, -2.4)">
<path class="paper-ink" d="M9 11.3 H14.2"
stroke="#4da3ff" stroke-width=".9" fill="none"
stroke-linecap="round" stroke-linejoin="round"
paint-order="normal" vector-effect="non-scaling-stroke"/>
<path class="paper-ink" d="M9 12.8 H16.4"
stroke="#4da3ff" stroke-width=".9" fill="none"
stroke-linecap="round" stroke-linejoin="round"
paint-order="normal" vector-effect="non-scaling-stroke"/>
</g>
</g>
` : ``}
<!-- FRONT -->
<g class="front-group" clip-path="url(#${gid}-clipFront)">
<path class="folder-front"
d="M2.3 10.1H10.9l2.0-2.1h7.4c.94 0 1.7.76 1.7 1.7v7.3c0 .94-.76 1.7-1.7 1.7H4
c-.94 0-1.7-.76-1.7-1.7v-6.9z"/>
d="M2.5 10.5 H11.5 L13.5 8.5 H20.5
C21.6 8.5 22.5 9.4 22.5 10.5 V17.5
C22.5 18.6 21.6 19.5 20.5 19.5 H4.5
C3.4 19.5 2.5 18.6 2.5 17.5 V10.5 Z"/>
<path d="M2.5 10.5 H11.5 L13.5 8.5 H20.5 V19.5 H2.5 Z"
fill="url(#${gid}-front)" pointer-events="none"/>
</g>
<!-- Subtle highlight along the lip to add depth -->
<path class="lip-highlight"
d="M3.3 10.2H11.2l1.7-1.8h7.0"
/>
</svg>`;
<!-- Lip highlight -->
<path class="lip-highlight" d="M3 10.5 H11.5 L13.5 8.5 H20.3"/>
</svg>`;
}
const _folderCountCache = new Map();
@@ -1253,7 +1306,7 @@ if (submitRename) {
}
// === Move Folder Modal helper (shared by button + context menu) ===
function openMoveFolderUI(sourceFolder) {
export function openMoveFolderUI(sourceFolder) {
const modal = document.getElementById('moveFolderModal');
const targetSel = document.getElementById('moveFolderTarget');