@@ -127,6 +127,9 @@ async function confirmFolderConversionIfNeeded(): Promise<boolean> {
127127}
128128
129129// ---------- Upload / refresh plumbing ----------
130+ // The filled viewer is wrapped in an overlay RDropzone (drag files onto the
131+ // manual to add another); the header's Upload button opens its picker.
132+ const manualDz = ref <InstanceType <typeof RDropzone > | null >(null );
130133const redownloadingManual = ref (false );
131134
132135async function refreshRom() {
@@ -186,17 +189,29 @@ function requestDeleteManual() {
186189 <div class =" r-v2-manual" >
187190 <!-- The subtab label in the sidebar already names the section, so the
188191 header skips a redundant title and just hosts the entry selector
189- (when multiple). Delete and Redownload live inside the viewer's
190- toolbar — they act on the currently displayed entry. -->
191- <header v-if =" manualEntries.length > 1" class =" r-v2-manual__head" >
192+ (when multiple) + the Upload button. Delete and Redownload live
193+ inside the viewer's toolbar; they act on the currently displayed
194+ entry. -->
195+ <header v-if =" manualEntries.length > 0" class =" r-v2-manual__head" >
192196 <RSelect
197+ v-if =" manualEntries .length > 1 "
193198 v-model =" selectedManualId "
194199 :items =" manualItems "
195200 density="compact"
196201 variant="outlined"
197202 hide-details
198203 class="r-v2-manual__select"
199204 />
205+ <div class =" r-v2-manual__actions" >
206+ <RBtn
207+ variant="outlined"
208+ size="small"
209+ prepend-icon="mdi-cloud-upload-outline"
210+ @click =" manualDz ?.open ()"
211+ >
212+ {{ t("common.upload") }}
213+ </RBtn >
214+ </div >
200215 </header >
201216
202217 <RDropzone
@@ -222,7 +237,17 @@ function requestDeleteManual() {
222237 </template >
223238 </RDropzone >
224239
225- <template v-if =" selectedManual " >
240+ <RDropzone
241+ v-if =" selectedManual "
242+ ref="manualDz"
243+ overlay
244+ class="r-v2-manual__fill"
245+ :release-label =" t (' common.dropzone-drag-over' )"
246+ :input-label =" t (' rom.upload-manual' )"
247+ accept="application/pdf,.md "
248+ multiple
249+ @files =" handleManualFiles "
250+ >
226251 <div class =" r-v2-manual__viewer" >
227252 <MarkdownViewer
228253 v-if =" selectedManual .kind === ' md' "
@@ -245,7 +270,7 @@ function requestDeleteManual() {
245270 @redownload =" redownloadManual "
246271 />
247272 </div >
248- </template >
273+ </RDropzone >
249274 </div >
250275</template >
251276
@@ -261,7 +286,8 @@ function requestDeleteManual() {
261286 scrollbar-color : var (--r-color-border-strong ) transparent ;
262287}
263288
264- /* Header — hosts the manual entry selector (when more than one manual). */
289+ /* Header — hosts the manual entry selector (when more than one manual) and
290+ the Upload button, pushed to the right. */
265291.r-v2-manual__head {
266292 display : flex ;
267293 align-items : center ;
@@ -276,21 +302,31 @@ function requestDeleteManual() {
276302 min-width : 200px ;
277303 flex-shrink : 1 ;
278304}
305+ .r-v2-manual__actions {
306+ margin-left : auto ;
307+ display : flex ;
308+ align-items : center ;
309+ gap : 6px ;
310+ }
311+
312+ /* Overlay-mode RDropzone wrapping the viewer must fill the panel height so
313+ the inner viewer can stretch to 100%. The min-height keeps the flex chain
314+ from collapsing the viewer to zero. */
315+ .r-v2-manual__fill {
316+ flex : 1 ;
317+ min-height : 30rem ;
318+ display : flex ;
319+ flex-direction : column ;
320+ }
279321
280322/* Viewer — fills the available panel height so the inner PDF / Markdown uses
281323 100% and only its own scroll triggers. */
282324.r-v2-manual__viewer {
283325 flex : 1 ;
326+ min-height : 0 ;
284327 border : 1px solid var (--r-color-border );
285328 border-radius : var (--r-radius-md );
286329 overflow : hidden ;
287330 background : var (--r-color-bg-elevated );
288331}
289-
290- .r-v2-manual__fill {
291- flex : 1 ;
292- min-height : 0 ;
293- display : flex ;
294- flex-direction : column ;
295- }
296332 </style >
0 commit comments