diff --git a/resources/js/components/ui/ExternalLink.tsx b/resources/js/components/ui/ExternalLink.tsx new file mode 100644 index 000000000..ac37274a2 --- /dev/null +++ b/resources/js/components/ui/ExternalLink.tsx @@ -0,0 +1,61 @@ +import {A, AnchorProps} from "@solidjs/router"; +import {createMemo, VoidComponent} from "solid-js"; +import {title} from "./title"; + +const _Directives = typeof title; + +const URL_REGEXP = /^(ftp|https?|app):\S+$/; + +interface Props { + readonly link: string; + readonly linkProps?: Partial; + /** Whether to show the full URL as a hover title. Default: true */ + readonly showTitle?: boolean; + /** + * Whether to allow specifying link text in link. Default: true. + * Supported variants: + * - `http://example.com link text` + * - `link text http://example.com` + * - `link text: http://example.com` + */ + readonly allowLabel?: boolean; +} + +export const ExternalLink: VoidComponent = (props) => { + const linkData = createMemo(() => { + const link = props.link.trim(); + const parts = link.trim().split(" "); + if (parts.length === 1 || !(props.allowLabel ?? true)) { + return {href: link, text: link}; + } + const firstPart = parts[0]!; + const lastPart = parts.at(-1)!; + if (URL_REGEXP.test(lastPart)) { + const text = parts.slice(0, -1).join(" "); + return {href: lastPart, text: text.at(-1) === ":" ? text.slice(0, -1).trim() || lastPart : text}; + } else if (URL_REGEXP.test(firstPart)) { + return {href: firstPart, text: parts.slice(1).join(" ")}; + } else { + return {href: link, text: link}; + } + }); + return ( + {linkData().href}, {delay: [1000, undefined]}] + : undefined + } + > + + {linkData().text} + + + ); +}; diff --git a/resources/js/components/ui/LinksList.tsx b/resources/js/components/ui/LinksList.tsx index 24533908b..c43208d1e 100644 --- a/resources/js/components/ui/LinksList.tsx +++ b/resources/js/components/ui/LinksList.tsx @@ -1,5 +1,5 @@ -import {A} from "@solidjs/router"; import {VoidComponent} from "solid-js"; +import {ExternalLink} from "./ExternalLink"; import {ThingsList} from "./ThingsList"; interface Props { @@ -11,12 +11,8 @@ export const LinksList: VoidComponent = (props) => {
( - - {link} - - )} - mode="bullets" + map={(link) => } + mode={props.links.length <= 1 ? "commas" : "bullets"} />
); diff --git a/resources/js/components/ui/title.scss b/resources/js/components/ui/title.scss index 206cbe8d0..bdefadc85 100644 --- a/resources/js/components/ui/title.scss +++ b/resources/js/components/ui/title.scss @@ -1,5 +1,5 @@ .tippy-box[data-theme~="memo"] { - @apply text-black bg-select border-solid border-memo-active; + @apply overflow-clip text-black bg-select border-solid border-memo-active; &[data-placement^="top"] > .tippy-arrow::before { @apply border-t-select; diff --git a/resources/js/features/meeting/MeetingAttendantsFields.tsx b/resources/js/features/meeting/MeetingAttendantsFields.tsx index ce075e57c..849b2c246 100644 --- a/resources/js/features/meeting/MeetingAttendantsFields.tsx +++ b/resources/js/features/meeting/MeetingAttendantsFields.tsx @@ -693,7 +693,12 @@ export const MeetingAttendantsFields: VoidComponent = (props) => { { value: "none", label: () => ( - + {translations.fieldName("clientsGroupsMode.none")} ), @@ -710,7 +715,7 @@ export const MeetingAttendantsFields: VoidComponent = (props) => { ? "clientsGroupsMode.shared.desc" : "clientsGroupsMode.shared.desc_no_shared_options", ), - {delay: 500}, + {delay: [800, undefined]}, ]} > {translations.fieldName( @@ -727,7 +732,10 @@ export const MeetingAttendantsFields: VoidComponent = (props) => { value: "separate", label: () => ( {translations.fieldName("clientsGroupsMode.separate")} diff --git a/resources/js/index.scss b/resources/js/index.scss index 1f69358cb..6764f7be8 100644 --- a/resources/js/index.scss +++ b/resources/js/index.scss @@ -228,4 +228,9 @@ html { @apply wrapText; overflow-wrap: anywhere; } + + .wrapLinkAnywhere { + word-break: break-all; + overflow-wrap: anywhere; + } }