plugins: frontend event 'entry' can return skip the line
Massimo Melina committed
Jun 10, 2024 at 16:08 UTC
b6a3276ae5c8de3869b9bffc53e40d060707e260
4 files changed
+61
-40
dev-plugins.md
+13
-3
@@ -233,7 +233,9 @@ The HFS objects contains many properties:
233
- `toast(message: string | ReactElement, type: ToastType='info')`
234
- show a brief message that doesn't steal focus
235
- `ToastType = 'error' | 'warning' | 'info' | 'success'`
236
-- `dialogLib` this exposes all functions available in [dialog.ts](https://github.com/rejetto/hfs/blob/main/frontend/src/dialog.ts), for example alertDialog and newDialog. These are not documented yet, and subject to change without notification, but you can study the sources if you are interested in using them.
236
+- `dialogLib` this exposes all functions available in [dialog.ts](https://github.com/rejetto/hfs/blob/main/frontend/src/dialog.ts),
237
+ for example alertDialog and newDialog. These are not documented yet, and subject to change without notification,
238
+ but you can study the sources if you are interested in using them.
239
- `misc` many functions and constants available in [cross.ts](https://github.com/rejetto/hfs/blob/main/src/cross.ts). These are not documented, probably never will, and are subject to change without notifications, but you can study the sources if you are interested in using them.
240
- `navigate(uri: string)` use this if you have to change the page address without causing reload
241
- `emit(name: string, params?: object): any[]` use this to emit a custom event. Prefix name with your plugin name to avoid conflicts.
@@ -304,7 +306,7 @@ This is a list of available frontend-events, with respective object parameter an
306
- `entry`
307
- you receive each entry of the list, and optionally produce HTML code that will completely replace the entry row/slot.
308
- parameter `{ entry: Entry }` (refer above for Entry object)
307
- - output `Html`
309
+ - output `Html | null` return null if you want to hide this entry
310
- `afterEntryName`
311
- you receive each entry of the list, and optionally produce HTML code that will be added after the name of the entry.
312
- parameter `{ entry: Entry }` (refer above for Entry object)
@@ -525,6 +527,13 @@ Be sure to also fill the "description" field, especially with words that people
527
The files intended to be installed must go in a folder named `dist`.
528
You can keep other files outside.
529
530
+Hint: if you go in your .hfs/plugins folder on linux and mac, and enter
531
+
532
+ ln -s /PATH_TO_YOUR_REPO/dist MY_PLUGIN_NAME
533
+
534
+you'll install your repo, so that you can edit the sources and see effects in real-time, and still be editing your repo,
535
+ready to commit.
536
+
537
If you have platform-dependent files, you can put those files in `dist-PLATFORM` or `dist-PLATFORM-ARCHITECTURE`.
538
For example, if you want some files to be installed only on Windows with Intel CPUs, put them in `dist-win32-x64`.
539
@@ -590,7 +599,7 @@ If you want to override a text regardless of the language, use the special langu
599
600
## API version history
601
593
-- 8.87 (v0.53.0)
602
+- 8.88 (v0.53.0)
603
- api.openDb
604
- frontend event: menuZip
605
- config.type:username
@@ -607,6 +616,7 @@ If you want to override a text regardless of the language, use the special langu
616
- the old way of returning true is now deprecated
617
- exports.customHtml
618
- more functions in HFS.misc
619
+ - frontend event 'entry' can now ask to skip an entry
620
- 8.72 (v0.52.0)
621
- HFS.toast
622
- HFS.misc functions
frontend/src/BrowseFiles.ts
+33
-32
@@ -218,39 +218,40 @@ const Entry = ({ entry, midnight, separator }: EntryProps) => {
218
location.href = uri
219
}
220
}
221
- return h('li', { className, label: separator },
222
- h(CustomCode, { name: 'entry', entry },
223
- showFilter && h(Checkbox, {
224
- disabled: isLink,
225
- 'aria-labelledby': ariaId,
226
- value: selected[uri],
227
- onChange(v) {
228
- if (v)
229
- return state.selected[uri] = true
230
- delete state.selected[uri]
231
- },
232
- }),
233
- h('span', { className: 'link-wrapper' }, // container to handle mouse over for both children
234
- // we treat webpages as folders, with menu to comment
235
- isFolder ? h(Fragment, {}, // internal navigation, use Link component
236
- h(Link, { to: uri, reloadDocument: entry.web, ...ariaProps, ...loginProps }, // without reloadDocument, once you enter the web page, the back button won't bring you back to the frontend
237
- ico, entry.n.slice(0, -1)), // don't use name, as we want to include whole path in case of search
238
- // popup button is here to be able to detect link-wrapper:hover
239
- file_menu_on_link && !showingButton && h('button', {
240
- className: 'popup-menu-button',
241
- onClick: fileMenu
242
- }, hIcon('menu'), t`Menu`)
243
- ) : h('a', { href: uri, onClick, target: entry.target, ...ariaProps, ...loginProps },
244
- ico, h('span', { className: 'container-folder' }, containerName), name ),
245
- ),
246
- h(CustomCode, { name: 'afterEntryName', entry }),
247
- entry.comment && h('div', { className: 'entry-comment' }, entry.comment),
248
- h('div', { className: 'entry-panel' },
249
- h(EntryDetails, { entry, midnight }),
250
- showingButton && iconBtn('menu', fileMenu, { className: 'file-menu-button' }),
251
- ),
252
- h('div'),
221
+ return h(CustomCode, {
222
+ name: 'entry',
223
+ entry,
224
+ render: x => x ? h('li', { className, label: separator }, x) : _.remove(state.list, { n }) && null
225
+ }, showFilter && h(Checkbox, {
226
+ disabled: isLink,
227
+ 'aria-labelledby': ariaId,
228
+ value: selected[uri],
229
+ onChange(v) {
230
+ if (v)
231
+ return state.selected[uri] = true
232
+ delete state.selected[uri]
233
+ },
234
+ }),
235
+ h('span', { className: 'link-wrapper' }, // container to handle mouse over for both children
236
+ // we treat webpages as folders, with menu to comment
237
+ isFolder ? h(Fragment, {}, // internal navigation, use Link component
238
+ h(Link, { to: uri, reloadDocument: entry.web, ...ariaProps, ...loginProps }, // without reloadDocument, once you enter the web page, the back button won't bring you back to the frontend
239
+ ico, entry.n.slice(0, -1)), // don't use name, as we want to include whole path in case of search
240
+ // popup button is here to be able to detect link-wrapper:hover
241
+ file_menu_on_link && !showingButton && h('button', {
242
+ className: 'popup-menu-button',
243
+ onClick: fileMenu
244
+ }, hIcon('menu'), t`Menu`)
245
+ ) : h('a', { href: uri, onClick, target: entry.target, ...ariaProps, ...loginProps },
246
+ ico, h('span', { className: 'container-folder' }, containerName), name ),
247
),
248
+ h(CustomCode, { name: 'afterEntryName', entry }),
249
+ entry.comment && h('div', { className: 'entry-comment' }, entry.comment),
250
+ h('div', { className: 'entry-panel' },
251
+ h(EntryDetails, { entry, midnight }),
252
+ showingButton && iconBtn('menu', fileMenu, { className: 'file-menu-button' }),
253
+ ),
254
+ h('div'),
255
)
256
257
function fileMenu(ev: MouseEvent) {
frontend/src/components.ts
+14
-4
@@ -1,6 +1,6 @@
1
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { getHFS, hfsEvent, hIcon, Html, isPrimitive, onlyTruthy, prefix } from './misc'
3
+import { Callback, getHFS, hfsEvent, hIcon, Html, isPrimitive, onlyTruthy, prefix } from './misc'
4
import { ButtonHTMLAttributes, ChangeEvent, createElement as h, CSSProperties, forwardRef, Fragment,
5
HTMLAttributes, InputHTMLAttributes, isValidElement, MouseEventHandler, ReactNode, SelectHTMLAttributes,
6
useMemo, useState, ComponentPropsWithoutRef } from 'react'
@@ -66,20 +66,30 @@ export function Select<T extends string>({ onChange, value, options, ...props }:
66
}, options.map(({ value, label }) => h('option', { key: value, value }, label)))
67
}
68
69
-export function CustomCode({ name, children, ...props }: { name: string, children?: ReactNode } & any) {
69
+export function CustomCode({ name, children, render, ...props }: {
70
+ name: string,
71
+ children?: ReactNode,
72
+ render?: Callback<ReactNode, ReactNode>
73
+ [k :string]: any,
74
+}) {
75
const result = useMemo(() => {
76
props.def = children // not using 'default' because user can have unexpected error destructuring object
77
+ let keep = true
78
const ret = onlyTruthy(hfsEvent(name, props)
79
.map((x, key) => isValidElement(x) ? h(Fragment, { key }, x)
80
: x === 0 || x && isPrimitive(x) ? h(Html, { key }, String(x))
81
: _.isArray(x) ? h(Fragment, { key }, ...x)
76
- : null))
82
+ : x === null ? keep = false
83
+ : null))
84
+ if (!keep)
85
+ return null
86
const html = getHFS().customHtml?.[name]
87
if (html?.trim?.())
88
ret.push(h(Html, { key: 'x' }, html))
89
return ret
90
}, [name, children, ...props ? Object.values(props) : []])
82
- return result.length || !children ? h(Fragment, {}, result) : children
91
+ render ??= _.identity
92
+ return h(Fragment, {}, render(result && (result.length || !children ? result : children)) )
93
}
94
95
interface IconBtnOptions extends ButtonHTMLAttributes<any> { style?: any, title?: string }
src/const.ts
+1
-1
@@ -7,7 +7,7 @@ import { mkdirSync } from 'fs'
7
import { basename, dirname, join } from 'path'
8
export * from './cross-const'
9
10
-export const API_VERSION = 8.87
10
+export const API_VERSION = 8.88
11
export const COMPATIBLE_API_VERSION = 1 // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
12
export const HFS_REPO = 'rejetto/hfs'
13