mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-01 20:04:09 +03:00
Merge pull request #5071 from urbit/lf/fixes-more
interface: handle malformed group links, fix mixed content errors on embeds
This commit is contained in:
commit
b1b78bce5e
@ -10,8 +10,18 @@ const makeIndexes = () => new Map([
|
||||
['other', []]
|
||||
]);
|
||||
|
||||
export interface OmniboxItem {
|
||||
title: string;
|
||||
link: string;
|
||||
app: string;
|
||||
host: string;
|
||||
description: string;
|
||||
shiftLink: string;
|
||||
shiftDescription: string;
|
||||
}
|
||||
|
||||
// result schematic
|
||||
const result = function(title, link, app, host, description = 'Open', shiftLink = null, shiftDescription = null) {
|
||||
const result = function(title: string, link: string, app: string, host: string, description = 'Open', shiftLink = null as string | null, shiftDescription = null as string | null): OmniboxItem {
|
||||
return {
|
||||
'title': title,
|
||||
'link': link,
|
||||
@ -93,7 +103,7 @@ const otherIndex = function(config) {
|
||||
return other;
|
||||
};
|
||||
|
||||
export default function index(contacts, associations, apps, currentGroup, groups, hide) {
|
||||
export default function index(contacts, associations, apps, currentGroup, groups, hide): Map<string, OmniboxItem[]> {
|
||||
const indexes = makeIndexes();
|
||||
indexes.set('ships', shipIndex(contacts));
|
||||
// all metadata from all apps is indexed
|
||||
@ -117,7 +127,7 @@ export default function index(contacts, associations, apps, currentGroup, groups
|
||||
let app = each['app-name'];
|
||||
if (each['app-name'] === 'contacts') {
|
||||
app = 'groups';
|
||||
};
|
||||
}
|
||||
|
||||
if (each['app-name'] === 'graph') {
|
||||
app = each.metadata.config.graph;
|
||||
@ -159,4 +169,4 @@ export default function index(contacts, associations, apps, currentGroup, groups
|
||||
indexes.set('other', otherIndex(hide));
|
||||
|
||||
return indexes;
|
||||
};
|
||||
}
|
@ -5,7 +5,7 @@ const URL_REGEX = new RegExp(String(/^([^[\]]*?)(([\w\-\+]+:\/\/)[-a-zA-Z0-9:@;?
|
||||
|
||||
const PATP_REGEX = /^([\s\S]*?)(~[a-z_-]+)([\s\S]*)/;
|
||||
|
||||
const GROUP_REGEX = new RegExp(String(/^([\s\S ]*?)(~[-a-z_]+\/[-a-z]+)([\s\S]*)/.source));
|
||||
const GROUP_REGEX = new RegExp(String(/^([\s\S ]*?)(~[-a-z_]+\/[-a-z0-9]+)([\s\S]*)/.source));
|
||||
|
||||
const convertToGroupRef = group => `web+urbitgraph://group/${group}`;
|
||||
|
||||
@ -33,9 +33,10 @@ const raceRegexes = (str) => {
|
||||
content = { url: link[2] };
|
||||
}
|
||||
}
|
||||
if(groupRef && groupRef[1].length < pfix?.length) {
|
||||
const perma = parsePermalink(convertToGroupRef(groupRef?.[2]));
|
||||
const [,,host] = perma?.group.split('/') ?? [];
|
||||
if(groupRef && groupRef[1].length < pfix?.length && Boolean(perma) && urbitOb.isValidPatp(host)) {
|
||||
pfix = groupRef[1];
|
||||
const perma = parsePermalink(convertToGroupRef(groupRef[2]));
|
||||
content = permalinkToReference(perma);
|
||||
sfix = groupRef[3];
|
||||
}
|
||||
|
@ -113,4 +113,19 @@ describe('tokenizeMessage', () => {
|
||||
expect(text).toBe('. foo');
|
||||
expect(url).toBe('https://tlon.io/test');
|
||||
});
|
||||
|
||||
it('should ignore malformed group links', () => {
|
||||
const example = 'test ~zoid/fakegroup';
|
||||
const [{ text }, ...rest] = tokenizeMessage(example);
|
||||
expect(text).toBe(example);
|
||||
expect(rest.length).toBe(0);
|
||||
});
|
||||
it('should handle groups with numbers', () => {
|
||||
const example = 'oh no, ~sampel/group-123-abc';
|
||||
|
||||
const [{ text }, { reference }] = tokenizeMessage(example);
|
||||
expect(text).toBe('oh no, ');
|
||||
expect(reference.group).toBe('/ship/~sampel/group-123-abc');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -11,7 +11,7 @@ export interface EmbedState {
|
||||
fetch: (url: string) => Promise<any>;
|
||||
}
|
||||
|
||||
const OEMBED_PROVIDER = 'http://noembed.com/embed';
|
||||
const OEMBED_PROVIDER = 'https://noembed.com/embed';
|
||||
|
||||
const useEmbedState = create<EmbedState>((set, get) => ({
|
||||
embeds: {},
|
||||
|
@ -26,6 +26,7 @@ export interface LocalState {
|
||||
setTutorialRef: (el: HTMLElement | null) => void;
|
||||
dark: boolean;
|
||||
mobile: boolean;
|
||||
mdBreak: boolean;
|
||||
background: BackgroundConfig;
|
||||
omniboxShown: boolean;
|
||||
suspendedFocus?: HTMLElement;
|
||||
@ -45,6 +46,7 @@ export const selectLocalState =
|
||||
const useLocalState = create<LocalStateZus>(persist((set, get) => ({
|
||||
dark: false,
|
||||
mobile: false,
|
||||
mdBreak: false,
|
||||
background: undefined,
|
||||
theme: 'auto',
|
||||
hideAvatars: false,
|
||||
|
@ -22,6 +22,7 @@ export const Image = () => (
|
||||
<Row flexWrap="wrap" m="2" width="700px" backgroundColor="white">
|
||||
<LinkBlockItem
|
||||
summary
|
||||
size="250px"
|
||||
m="2"
|
||||
node={createLink(
|
||||
'Gas',
|
||||
@ -30,6 +31,7 @@ export const Image = () => (
|
||||
/>
|
||||
<LinkBlockItem
|
||||
summary
|
||||
size="250px"
|
||||
m="2"
|
||||
node={createLink(
|
||||
'Ocean',
|
||||
@ -57,10 +59,8 @@ Image.parameters = {
|
||||
|
||||
export const Fallback = () => (
|
||||
<Col gapY="2" p="2" width="500px" backgroundColor="white">
|
||||
<LinkBlockItem
|
||||
node={createLink('', 'https://www.are.na/edouard-urcades/edouard')}
|
||||
/>
|
||||
<LinkBlockItem node={createLink('', 'https://thejaymo.net')} />
|
||||
<LinkBlockItem size="250px" node={createLink('', 'https://www.are.na/edouard-urcades/edouard')} />
|
||||
<LinkBlockItem size="250px" node={createLink('', 'https://thejaymo.net')} />
|
||||
</Col>
|
||||
);
|
||||
|
||||
@ -75,6 +75,7 @@ Fallback.parameters = {
|
||||
export const Audio = () => (
|
||||
<Col gapY="2" p="2" width="500px" backgroundColor="white">
|
||||
<LinkBlockItem
|
||||
size="250px"
|
||||
node={createLink(
|
||||
'Artist · Track',
|
||||
'https://rovnys-public.s3.amazonaws.com/urbit-from-the-outside-in-1.m4a'
|
||||
@ -94,12 +95,14 @@ Audio.parameters = {
|
||||
export const Youtube = () => (
|
||||
<Col gapY="2" p="2" width="500px" backgroundColor="white">
|
||||
<LinkBlockItem
|
||||
size="400px"
|
||||
node={createLink(
|
||||
'Artist · Track',
|
||||
'https://www.youtube.com/watch?v=M04AKTCDavc&t=1s'
|
||||
)}
|
||||
/>
|
||||
<LinkBlockItem
|
||||
size="250px"
|
||||
summary
|
||||
node={createLink(
|
||||
'Artist · Track',
|
||||
|
@ -8,7 +8,8 @@ import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
|
||||
import { GraphNode } from '@urbit/api';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import { makeComment } from '~/logic/lib/fixtures';
|
||||
import moment from 'moment';
|
||||
|
||||
const HOUR = 60*60 * 1000;
|
||||
|
||||
export default {
|
||||
title: 'Collections/LinkDetail',
|
||||
@ -35,20 +36,57 @@ const node = {
|
||||
children: new BigIntOrderedMap<GraphNode>().gas([
|
||||
makeComment(
|
||||
'ridlur-figbud',
|
||||
moment().hour(12).minute(34).valueOf(),
|
||||
Date.now() - 4*HOUR,
|
||||
nodeIndex,
|
||||
[{ text: 'Beautiful' }]
|
||||
),
|
||||
makeComment(
|
||||
'roslet-tanner',
|
||||
moment().hour(12).minute(34).valueOf(),
|
||||
Date.now() - 3*HOUR,
|
||||
nodeIndex,
|
||||
[{ text: 'where did you find this?' }]
|
||||
),
|
||||
|
||||
makeComment(
|
||||
'fabled-faster',
|
||||
moment().hour(12).minute(34).valueOf(),
|
||||
Date.now() - 2*HOUR,
|
||||
nodeIndex,
|
||||
[{ text: 'I dont\'t remember lol' }]
|
||||
)
|
||||
])
|
||||
};
|
||||
|
||||
const twitterNode = {
|
||||
post: {
|
||||
index: '/170141184504850861030994857749504231212',
|
||||
author: 'fabled-faster',
|
||||
'time-sent': 1609969377513,
|
||||
signatures: [],
|
||||
contents: [
|
||||
{ text: 'LindyMan' },
|
||||
{
|
||||
url: 'https://twitter.com/PaulSkallas/status/1388896550198317056'
|
||||
}
|
||||
],
|
||||
hash: null
|
||||
},
|
||||
children: new BigIntOrderedMap<GraphNode>().gas([
|
||||
makeComment(
|
||||
'ridlur-figbud',
|
||||
Date.now() - 4*HOUR,
|
||||
nodeIndex,
|
||||
[{ text: 'Beautiful' }]
|
||||
),
|
||||
makeComment(
|
||||
'roslet-tanner',
|
||||
Date.now() - 3*HOUR,
|
||||
nodeIndex,
|
||||
[{ text: 'where did you find this?' }]
|
||||
),
|
||||
|
||||
makeComment(
|
||||
'fabled-faster',
|
||||
Date.now() - 2*HOUR,
|
||||
nodeIndex,
|
||||
[{ text: 'I dont\'t remember lol' }]
|
||||
)
|
||||
@ -60,7 +98,7 @@ export const Image = () => {
|
||||
s => s.associations.graph['/ship/~bitbet-bolbel/links']
|
||||
);
|
||||
return (
|
||||
<Box width="1166px" p="1" backgroundColor="white">
|
||||
<Box width="100%" height="100%" p="1" backgroundColor="white">
|
||||
<LinkDetail
|
||||
baseUrl="/"
|
||||
node={node}
|
||||
@ -69,6 +107,21 @@ export const Image = () => {
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const Twitter = () => {
|
||||
const association = useMetadataState(
|
||||
s => s.associations.graph['/ship/~bitbet-bolbel/links']
|
||||
);
|
||||
return (
|
||||
<Box height="100%" width="100%" border="1" borderColor="lightGray" maxWidth="1166px" backgroundColor="white">
|
||||
<LinkDetail
|
||||
baseUrl="/"
|
||||
node={twitterNode}
|
||||
association={association}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
Image.parameters = {
|
||||
design: {
|
||||
type: 'figma',
|
||||
|
@ -83,15 +83,20 @@ class App extends React.Component {
|
||||
bootstrapApi();
|
||||
this.props.getShallowChildren(`~${window.ship}`, 'dm-inbox');
|
||||
const theme = this.getTheme();
|
||||
this.themeWatcher = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.mobileWatcher = window.matchMedia(`(max-width: ${theme.breakpoints[0]})`);
|
||||
this.themeWatcher.onchange = this.updateTheme;
|
||||
this.mobileWatcher.onchange = this.updateMobile;
|
||||
setTimeout(() => {
|
||||
// Something about how the store works doesn't like changing it
|
||||
// before the app has actually rendered, hence the timeout.
|
||||
this.themeWatcher = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.mobileWatcher = window.matchMedia(`(max-width: ${theme.breakpoints[0]})`);
|
||||
this.mediumWatcher = window.matchMedia(`(max-width: ${theme.breakpoints[1]})`);
|
||||
// TODO: addListener is deprecated, but safari 13 requires it
|
||||
this.themeWatcher.addListener(this.updateTheme);
|
||||
this.mobileWatcher.addListener(this.updateMobile);
|
||||
this.mediumWatcher.addListener(this.updateMedium);
|
||||
|
||||
this.updateMobile(this.mobileWatcher);
|
||||
this.updateTheme(this.themeWatcher);
|
||||
this.updateMedium(this.mediumWatcher);
|
||||
}, 500);
|
||||
this.props.getBaseHash();
|
||||
this.props.getRuntimeLag(); // TODO consider polling periodically
|
||||
@ -105,8 +110,9 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.themeWatcher.onchange = undefined;
|
||||
this.mobileWatcher.onchange = undefined;
|
||||
this.themeWatcher.removeListener(this.updateTheme);
|
||||
this.mobileWatcher.removeListener(this.updateMobile);
|
||||
this.mediumWatcher.removeListener(this.updateMedium);
|
||||
}
|
||||
|
||||
updateTheme(e) {
|
||||
@ -121,6 +127,12 @@ class App extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
updateMedium = (e) => {
|
||||
this.props.set((state) => {
|
||||
state.mdBreak = e.matches;
|
||||
});
|
||||
}
|
||||
|
||||
getTheme() {
|
||||
const { props } = this;
|
||||
return ((props.dark && props?.display?.theme == 'auto') ||
|
||||
|
@ -84,7 +84,7 @@ export function DmResource(props: DmResourceProps) {
|
||||
if (newer) {
|
||||
const index = dm.peekLargest()?.[0];
|
||||
if (!index) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
await getYoungerSiblings(
|
||||
`~${window.ship}`,
|
||||
@ -96,7 +96,7 @@ export function DmResource(props: DmResourceProps) {
|
||||
} else {
|
||||
const index = dm.peekSmallest()?.[0];
|
||||
if (!index) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
await getOlderSiblings(
|
||||
`~${window.ship}`,
|
||||
|
@ -32,7 +32,7 @@ export function LinkBlocks(props: LinkBlocksProps) {
|
||||
const [linkSize, setLinkSize] = useState(250);
|
||||
const linkSizePx = `${linkSize}px`;
|
||||
|
||||
const isMobile = useLocalState(s => s.mobile);
|
||||
const isMobile = useLocalState(s => s.mobile || s.mdBreak);
|
||||
const colCount = useMemo(() => (isMobile ? 2 : 4), [isMobile]);
|
||||
const bind = useResize<HTMLDivElement>(
|
||||
useCallback(
|
||||
@ -46,12 +46,13 @@ export function LinkBlocks(props: LinkBlocksProps) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const unreads = useHarkState.getState()
|
||||
.unreads.graph?.[association.resource]?.['/']?.unreads || new Set<string>();
|
||||
Array.from((unreads as Set<string>)).forEach((u) => {
|
||||
const unreads =
|
||||
useHarkState.getState().unreads.graph?.[association.resource]?.['/']
|
||||
?.unreads || new Set<string>();
|
||||
Array.from(unreads as Set<string>).forEach((u) => {
|
||||
airlock.poke(markEachAsRead(association.resource, '/', u));
|
||||
});
|
||||
}, [association.resource]);
|
||||
}, [association.resource]);
|
||||
|
||||
const orm = useMemo(() => {
|
||||
const nodes = [null, ...Array.from(props.graph)];
|
||||
@ -62,12 +63,12 @@ export function LinkBlocks(props: LinkBlocksProps) {
|
||||
return [bigInt(i), chunk];
|
||||
})
|
||||
);
|
||||
}, [props.graph]);
|
||||
}, [props.graph, colCount]);
|
||||
|
||||
const renderItem = useCallback(
|
||||
React.forwardRef<any, any>(({ index }, ref) => {
|
||||
const chunk = orm.get(index);
|
||||
const space = [3,4];
|
||||
const chunk = orm.get(index) ?? [];
|
||||
const space = [3, 3, 4];
|
||||
|
||||
return (
|
||||
<Row
|
||||
@ -82,10 +83,7 @@ export function LinkBlocks(props: LinkBlocksProps) {
|
||||
{chunk.map((block) => {
|
||||
if (!block) {
|
||||
return (
|
||||
<LinkBlockInput
|
||||
size={linkSizePx}
|
||||
association={association}
|
||||
/>
|
||||
<LinkBlockInput size={linkSizePx} association={association} />
|
||||
);
|
||||
}
|
||||
const [i, node] = block;
|
||||
@ -115,7 +113,13 @@ export function LinkBlocks(props: LinkBlocksProps) {
|
||||
);
|
||||
|
||||
return (
|
||||
<Col overflowX="hidden" overflowY="auto" height="calc(100% - 48px)" {...bind}>
|
||||
<Col
|
||||
width="100%"
|
||||
overflowX="hidden"
|
||||
overflowY="auto"
|
||||
height="calc(100% - 48px)"
|
||||
{...bind}
|
||||
>
|
||||
<BlockScroller
|
||||
origin="top"
|
||||
offset={0}
|
||||
|
@ -21,7 +21,7 @@ export function LinkDetail(props: LinkDetailProps) {
|
||||
return (
|
||||
/* @ts-ignore indio props?? */
|
||||
<Row height="100%" width="100%" flexDirection={['column', 'column', 'row']} {...rest}>
|
||||
<LinkBlockItem minWidth="0" minHeight="0" maxHeight={['50%', '50%', '100%']} maxWidth={['100%', '100%', 'calc(100% - 350px)']} flexGrow={1} border={0} node={node} />
|
||||
<LinkBlockItem minWidth="0" minHeight="0" height={['50%', '50%', '100%']} width={['100%', '100%', 'calc(100% - 350px)']} flexGrow={0} border={0} node={node} />
|
||||
<Col
|
||||
minHeight="0"
|
||||
flexShrink={0}
|
||||
|
@ -184,6 +184,7 @@ const EmbedBox = styled.div<{ aspect?: number; iHeight?: number; iWidth?: number
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
flex-grow: 1;
|
||||
|
||||
` : `
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Box, Row, Text } from '@tlon/indigo-react';
|
||||
import { omit } from 'lodash';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import _ from 'lodash';
|
||||
import f from 'lodash/fp';
|
||||
import React, {
|
||||
ReactElement, useCallback,
|
||||
useEffect, useMemo,
|
||||
@ -11,7 +13,7 @@ import React, {
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import * as ob from 'urbit-ob';
|
||||
import defaultApps from '~/logic/lib/default-apps';
|
||||
import makeIndex from '~/logic/lib/omnibox';
|
||||
import makeIndex, { OmniboxItem } from '~/logic/lib/omnibox';
|
||||
import { useOutsideClick } from '~/logic/lib/useOutsideClick';
|
||||
import { deSig } from '~/logic/lib/util';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
@ -41,6 +43,7 @@ const SEARCHED_CATEGORIES = [
|
||||
'apps'
|
||||
];
|
||||
const settingsSel = (s: SettingsState) => s.leap;
|
||||
const CAT_LIMIT = 6;
|
||||
|
||||
export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
const location = useLocation();
|
||||
@ -111,7 +114,7 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
}, [props.show]);
|
||||
|
||||
const initialResults = useMemo(() => {
|
||||
return new Map(
|
||||
return new Map<string, OmniboxItem[]>(
|
||||
SEARCHED_CATEGORIES.map((category) => {
|
||||
if (category === 'other') {
|
||||
return [
|
||||
@ -129,7 +132,7 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
return initialResults;
|
||||
}
|
||||
const q = query.toLowerCase();
|
||||
const resultsMap = new Map();
|
||||
const resultsMap = new Map<string, OmniboxItem[]>();
|
||||
SEARCHED_CATEGORIES.map((category) => {
|
||||
const categoryIndex = index.get(category);
|
||||
resultsMap.set(
|
||||
@ -175,7 +178,7 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
);
|
||||
|
||||
const setPreviousSelected = useCallback(() => {
|
||||
const flattenedResults = Array.from(results.values()).flat();
|
||||
const flattenedResults = Array.from(results.values()).map(f.take(CAT_LIMIT)).flat();
|
||||
const totalLength = flattenedResults.length;
|
||||
if (selected.length) {
|
||||
const currentIndex = flattenedResults.indexOf(
|
||||
@ -198,7 +201,7 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
}, [results, selected]);
|
||||
|
||||
const setNextSelected = useCallback(() => {
|
||||
const flattenedResults = Array.from(results.values()).flat();
|
||||
const flattenedResults = Array.from(results.values()).map(f.take(CAT_LIMIT)).flat();
|
||||
if (selected.length) {
|
||||
const currentIndex = flattenedResults.indexOf(
|
||||
// @ts-ignore unclear how to give this spread a return signature
|
||||
@ -309,13 +312,15 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
return (
|
||||
<Box
|
||||
maxHeight={['200px', '400px']}
|
||||
overflowY='auto'
|
||||
overflowX='hidden'
|
||||
overflow='hidden'
|
||||
borderBottomLeftRadius={2}
|
||||
borderBottomRightRadius={2}
|
||||
>
|
||||
{SEARCHED_CATEGORIES.map(category =>
|
||||
Object({ category, categoryResults: results.get(category) })
|
||||
({
|
||||
category,
|
||||
categoryResults: _.take(results.get(category).sort(sortResults), CAT_LIMIT)
|
||||
})
|
||||
)
|
||||
.filter(category => category.categoryResults.length > 0)
|
||||
.map(({ category, categoryResults }, i) => {
|
||||
@ -331,7 +336,7 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
return (
|
||||
<Box key={i} width='max(50vw, 300px)' maxWidth='700px'>
|
||||
{categoryTitle}
|
||||
{categoryResults.sort(sortResults).map((result, i2) => (
|
||||
{categoryResults.map((result, i2) => (
|
||||
<OmniboxResult
|
||||
key={i2}
|
||||
// @ts-ignore withHovering doesn't pass props
|
||||
|
Loading…
Reference in New Issue
Block a user