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