mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-28 11:40:11 +03:00
Merge branch 'next/groups'
This commit is contained in:
commit
23655772e0
@ -1 +1 @@
|
||||
16.14.0
|
||||
14.19.0
|
1
pkg/interface/.tool-versions
Normal file
1
pkg/interface/.tool-versions
Normal file
@ -0,0 +1 @@
|
||||
nodejs 14.19.0
|
@ -1,66 +1,71 @@
|
||||
const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
const { execSync } = require('child_process');
|
||||
const path = require("path");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const MomentLocalesPlugin = require("moment-locales-webpack-plugin");
|
||||
const webpack = require("webpack");
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const GIT_DESC = execSync('git describe --always', { encoding: 'utf8' }).trim();
|
||||
const GIT_DESC = execSync("git describe --always", { encoding: "utf8" }).trim();
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
mode: "production",
|
||||
entry: {
|
||||
app: './src/index.tsx',
|
||||
serviceworker: './src/serviceworker.js'
|
||||
app: "./src/index.tsx",
|
||||
serviceworker: "./src/serviceworker.js",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(j|t)sx?$/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ['@babel/preset-env', '@babel/typescript', '@babel/preset-react'],
|
||||
presets: [
|
||||
"@babel/preset-env",
|
||||
"@babel/typescript",
|
||||
"@babel/preset-react",
|
||||
],
|
||||
plugins: [
|
||||
'lodash',
|
||||
'@babel/transform-runtime',
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
'@babel/plugin-proposal-class-properties'
|
||||
]
|
||||
}
|
||||
"lodash",
|
||||
"@babel/transform-runtime",
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
],
|
||||
},
|
||||
},
|
||||
exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light|@tlon\/indigo-react|@urbit\/api)\/).*/
|
||||
exclude:
|
||||
/node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light|@tlon\/indigo-react|@urbit\/api)\/).*/,
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
test: /\.css$/i,
|
||||
use: [
|
||||
// Creates `style` nodes from JS strings
|
||||
'style-loader',
|
||||
"style-loader",
|
||||
// Translates CSS into CommonJS
|
||||
'css-loader',
|
||||
"css-loader",
|
||||
// Compiles Sass to CSS
|
||||
'sass-loader'
|
||||
]
|
||||
"sass-loader",
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
loader: "file-loader",
|
||||
options: {
|
||||
name: '[name].[ext]',
|
||||
outputPath: 'fonts/'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
name: "[name].[ext]",
|
||||
outputPath: "fonts/",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.tsx']
|
||||
extensions: [".js", ".ts", ".tsx"],
|
||||
},
|
||||
devtool: 'source-map',
|
||||
devtool: "source-map",
|
||||
// devServer: {
|
||||
// contentBase: path.join(__dirname, './'),
|
||||
// hot: true,
|
||||
@ -71,26 +76,30 @@ module.exports = {
|
||||
new MomentLocalesPlugin(),
|
||||
new CleanWebpackPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.LANDSCAPE_STREAM': JSON.stringify(process.env.LANDSCAPE_STREAM),
|
||||
'process.env.LANDSCAPE_SHORTHASH': JSON.stringify(GIT_DESC),
|
||||
'process.env.LANDSCAPE_STORAGE_VERSION': Date.now().toString(),
|
||||
'process.env.LANDSCAPE_LAST_WIPE': '2021-10-20',
|
||||
"process.env.LANDSCAPE_STREAM": JSON.stringify(
|
||||
process.env.LANDSCAPE_STREAM
|
||||
),
|
||||
"process.env.LANDSCAPE_SHORTHASH": JSON.stringify(GIT_DESC),
|
||||
"process.env.LANDSCAPE_STORAGE_VERSION": Date.now().toString(),
|
||||
"process.env.LANDSCAPE_LAST_WIPE": "2021-10-20",
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'Groups',
|
||||
template: './public/index.html',
|
||||
favicon: './src/assets/img/Favicon.png'
|
||||
})
|
||||
title: "Groups",
|
||||
template: "./public/index.html",
|
||||
favicon: "./src/assets/img/favicon.png",
|
||||
}),
|
||||
],
|
||||
output: {
|
||||
filename: (pathData) => {
|
||||
return pathData.chunk.name === 'app' ? 'index.[contenthash].js' : '[name].js';
|
||||
return pathData.chunk.name === "app"
|
||||
? "index.[contenthash].js"
|
||||
: "[name].js";
|
||||
},
|
||||
path: path.resolve(__dirname, '../dist'),
|
||||
publicPath: '/apps/landscape/'
|
||||
path: path.resolve(__dirname, "../dist"),
|
||||
publicPath: "/apps/landscape/",
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
usedExports: true
|
||||
}
|
||||
usedExports: true,
|
||||
},
|
||||
};
|
||||
|
61638
pkg/interface/package-lock.json
generated
61638
pkg/interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,21 +5,18 @@
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "16.14.0"
|
||||
"node": "14.19.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
||||
"@radix-ui/react-dialog": "^0.1.0",
|
||||
"@reach/disclosure": "^0.10.5",
|
||||
"@reach/menu-button": "^0.10.5",
|
||||
"@reach/tabs": "^0.10.5",
|
||||
"@react-spring/web": "^9.1.1",
|
||||
"@tlon/indigo-dark": "^1.0.6",
|
||||
"@tlon/indigo-light": "^1.0.7",
|
||||
"@tlon/indigo-react": "^1.2.27",
|
||||
"@tlon/sigil-js": "^1.4.3",
|
||||
"@urbit/api": "^2.1.0",
|
||||
"@tlon/sigil-js": "^1.4.5",
|
||||
"@urbit/api": "^2.1.1",
|
||||
"@urbit/http-api": "^2.1.0",
|
||||
"any-ascii": "^0.1.7",
|
||||
"aws-sdk": "^2.830.0",
|
||||
@ -36,15 +33,15 @@
|
||||
"mousetrap": "^1.6.5",
|
||||
"mousetrap-global-bind": "^1.1.0",
|
||||
"normalize-wheel": "1.0.1",
|
||||
"oembed-parser": "^1.4.5",
|
||||
"oembed-parser": "^3.0.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"querystring": "^0.2.0",
|
||||
"react": "^17.0.2",
|
||||
"react-codemirror2": "^6.0.1",
|
||||
"react-codemirror2": "git@github.com:scniro/react-codemirror2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-oembed-container": "^1.0.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-oembed-container": "^1.0.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-use-gesture": "^9.1.3",
|
||||
"react-virtuoso": "^0.20.3",
|
||||
@ -107,6 +104,7 @@
|
||||
"lint-staged": "^11.0.0",
|
||||
"loki": "^0.28.1",
|
||||
"moment-locales-webpack-plugin": "^1.2.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"react-refresh": "^0.11.0",
|
||||
"sass": "^1.32.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
@ -131,7 +129,8 @@
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook",
|
||||
"chromatic": "chromatic --exit-zero-on-changes",
|
||||
"hook-lint": "eslint --cache --fix"
|
||||
"hook-lint": "eslint --cache --fix",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
|
@ -4,7 +4,7 @@ export interface Suspender<T> {
|
||||
read: () => T;
|
||||
}
|
||||
|
||||
export function suspend<T>(awaiting: Promise<T>): Suspender<T> {
|
||||
export function suspend<T>(awaiting: Promise<T>, defaultValue?: any): Suspender<T> {
|
||||
let state: SuspendState = 'pending';
|
||||
let result: T | null = null;
|
||||
|
||||
@ -22,8 +22,10 @@ export function suspend<T>(awaiting: Promise<T>): Suspender<T> {
|
||||
read: () => {
|
||||
if (state === 'result') {
|
||||
return result!;
|
||||
} else if (state === 'error') {
|
||||
} else if (state === 'error' && typeof defaultValue === 'undefined') {
|
||||
throw result;
|
||||
} else if (state === 'error' && typeof defaultValue !== 'undefined') {
|
||||
return defaultValue;
|
||||
} else {
|
||||
throw promise;
|
||||
}
|
||||
|
@ -34,7 +34,8 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
|
||||
}
|
||||
client.current = new S3Client({
|
||||
credentials: s3.credentials,
|
||||
endpoint: s3.credentials.endpoint
|
||||
endpoint: s3.credentials.endpoint,
|
||||
signatureVersion: 'v4'
|
||||
});
|
||||
}
|
||||
}, [gcp.token, s3.credentials]);
|
||||
|
@ -11,8 +11,7 @@ export function getTitleFromWorkspace(
|
||||
case 'messages':
|
||||
return 'Messages';
|
||||
case 'group':
|
||||
const association = associations.groups[workspace.group];
|
||||
return association?.metadata?.title || '';
|
||||
return associations.groups[workspace.group]?.metadata?.title || 'Groups';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import create from 'zustand';
|
||||
import { suspend, Suspender , suspendWithResult } from '../lib/suspend';
|
||||
import { suspend, Suspender } from '../lib/suspend';
|
||||
import { jsonFetch } from '~/logic/lib/util';
|
||||
|
||||
export interface EmbedState {
|
||||
@ -23,17 +23,19 @@ const useEmbedState = create<EmbedState>((set, get) => ({
|
||||
const search = new URLSearchParams({
|
||||
url
|
||||
});
|
||||
|
||||
const embed = await jsonFetch(`${OEMBED_PROVIDER}?${search.toString()}`);
|
||||
const { embeds: es } = get();
|
||||
set({ embeds: { ...es, [url]: embed } });
|
||||
return embed;
|
||||
},
|
||||
getEmbed: (url: string): Suspender<any> => {
|
||||
const { fetch, embeds } = get();
|
||||
if(url in embeds) {
|
||||
return suspendWithResult(embeds[url]);
|
||||
return embeds[url];
|
||||
}
|
||||
return suspend(fetch(url));
|
||||
const { embeds: es } = get();
|
||||
const embed = suspend(fetch(url), {});
|
||||
set({ embeds: { ...es, [url]: embed } });
|
||||
return embed;
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { acceptDm, cite, Content, declineDm, deSig, Post } from '@urbit/api';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import _ from 'lodash';
|
||||
import bigInt from 'big-integer';
|
||||
import { Box, Row, Col, Text, Center } from '@tlon/indigo-react';
|
||||
@ -49,6 +50,21 @@ function quoteReply(post: Post) {
|
||||
return `${reply}\n\n~${post.author}:`;
|
||||
}
|
||||
|
||||
export function DmHelmet(props: DmHelmetProps) {
|
||||
const { ship } = props;
|
||||
const hark = useHarkDm(ship);
|
||||
const unreadCount = hark.count;
|
||||
const contact = useContact(ship);
|
||||
const { hideNicknames } = useSettingsState(selectCalmState);
|
||||
const showNickname = !hideNicknames && Boolean(contact);
|
||||
const nickname = showNickname ? contact!.nickname : cite(ship) ?? ship;
|
||||
return(
|
||||
<Helmet defer={false}>
|
||||
<title>{unreadCount ? `(${String(unreadCount)}) ` : ''}{ nickname }</title>
|
||||
</Helmet>
|
||||
);
|
||||
}
|
||||
|
||||
export function DmResource(props: DmResourceProps) {
|
||||
const { ship } = props;
|
||||
const dm = useDM(ship);
|
||||
|
@ -1,18 +1,17 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Icon,
|
||||
Center,
|
||||
Row,
|
||||
Text,
|
||||
Col,
|
||||
Box,
|
||||
CenterProps
|
||||
} from '@tlon/indigo-react';
|
||||
import { hasProvider } from 'oembed-parser';
|
||||
import { AUDIO_REGEX, IMAGE_REGEX } from '~/views/components/RemoteContent';
|
||||
import { AUDIO_REGEX, IMAGE_REGEX, validOembedCheck } from '~/views/components/RemoteContent';
|
||||
import { AudioPlayer } from '~/views/components/AudioPlayer';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useHovering } from '~/logic/lib/util';
|
||||
import { useEmbed } from '~/logic/state/embed';
|
||||
import Author from '~/views/components/Author';
|
||||
import {
|
||||
GraphNode,
|
||||
@ -38,8 +37,32 @@ export interface LinkBlockItemProps {
|
||||
summary?: boolean;
|
||||
}
|
||||
|
||||
export function LinkBlockItem(props: LinkBlockItemProps & CenterProps) {
|
||||
const { node, summary, size, m, border = 1, objectFit, ...rest } = props;
|
||||
export const LinkBlockItem = (props: LinkBlockItemProps & CenterProps) => {
|
||||
const { node, ...rest } = props;
|
||||
const { post } = node;
|
||||
const { contents } = post;
|
||||
|
||||
const [{ text: title }, ...content] = contents as [
|
||||
TextContent,
|
||||
UrlContent | ReferenceContent
|
||||
];
|
||||
let url = '';
|
||||
if ('url' in content?.[0]) {
|
||||
url = content[0].url;
|
||||
}
|
||||
|
||||
return(
|
||||
<AsyncFallback fallback={<RemoteContentEmbedFallback url={url} />}>
|
||||
<LinkBlockItemInner
|
||||
node={node}
|
||||
{...rest}
|
||||
/>
|
||||
</AsyncFallback>
|
||||
);
|
||||
}
|
||||
|
||||
function LinkBlockItemInner(props: LinkBlockItemProps & CenterProps) {
|
||||
const { node, summary, m, border = 1, objectFit, ...rest } = props;
|
||||
const { post, children } = node;
|
||||
const { contents, index, author } = post;
|
||||
|
||||
@ -56,8 +79,9 @@ export function LinkBlockItem(props: LinkBlockItemProps & CenterProps) {
|
||||
|
||||
const isImage = IMAGE_REGEX.test(url);
|
||||
const isAudio = AUDIO_REGEX.test(url);
|
||||
const oembed = useEmbed(url);
|
||||
const isOembed = validOembedCheck(oembed, url);
|
||||
|
||||
const isOembed = hasProvider(url);
|
||||
const history = useHistory();
|
||||
const { hovering, bind } = useHovering();
|
||||
const onClick = () => {
|
||||
@ -65,70 +89,67 @@ export function LinkBlockItem(props: LinkBlockItemProps & CenterProps) {
|
||||
history.push(`${pathname}/index${index}${search}`);
|
||||
};
|
||||
return (
|
||||
<Center
|
||||
<Box
|
||||
onClick={onClick}
|
||||
position="relative"
|
||||
m={m}
|
||||
border={border}
|
||||
borderColor="lightGray"
|
||||
position="relative"
|
||||
borderRadius="1"
|
||||
height={size}
|
||||
width={size}
|
||||
m={m}
|
||||
maxHeight="100%"
|
||||
{...rest}
|
||||
{...bind}
|
||||
>
|
||||
<AsyncFallback fallback={<RemoteContentEmbedFallback url={url} />}>
|
||||
{isReference ? (
|
||||
summary ? (
|
||||
<RemoteContentPermalinkEmbed
|
||||
reference={content[0] as ReferenceContent}
|
||||
/>
|
||||
) : (
|
||||
<PermalinkEmbed
|
||||
link={referenceToPermalink(content[0] as ReferenceContent).link}
|
||||
transcluded={0}
|
||||
/>
|
||||
)
|
||||
) : isImage ? (
|
||||
<RemoteContentImageEmbed
|
||||
url={url}
|
||||
tall
|
||||
stretch
|
||||
objectFit={objectFit ? objectFit : "cover"}
|
||||
/>
|
||||
) : isAudio ? (
|
||||
<AudioPlayer title={title} url={url} />
|
||||
) : isOembed ? (
|
||||
<RemoteContentOembed tall={!summary} renderUrl={false} url={url} thumbnail={summary} />
|
||||
) : (
|
||||
<RemoteContentEmbedFallback url={url} />
|
||||
)}
|
||||
</AsyncFallback>
|
||||
<Box
|
||||
backgroundColor="white"
|
||||
display={summary && hovering ? 'block' : 'none'}
|
||||
width="100%"
|
||||
height="64px"
|
||||
position="absolute"
|
||||
left="0"
|
||||
bottom="0"
|
||||
>
|
||||
<Col width="100%" height="100%" p="2" justifyContent="space-between">
|
||||
<Row justifyContent="space-between" width="100%">
|
||||
<Text textOverflow="ellipsis" whiteSpace="nowrap" overflow="hidden">
|
||||
{title}
|
||||
</Text>
|
||||
<Row gapX="1" alignItems="center">
|
||||
<Icon icon="Chat" color="black" />
|
||||
<Text>{children.size}</Text>
|
||||
<Col height="100%" justifyContent="center" alignItems="center">
|
||||
{isReference ? (
|
||||
summary ? (
|
||||
<RemoteContentPermalinkEmbed
|
||||
reference={content[0] as ReferenceContent}
|
||||
/>
|
||||
) : (
|
||||
<PermalinkEmbed
|
||||
link={referenceToPermalink(content[0] as ReferenceContent).link}
|
||||
transcluded={0}
|
||||
/>
|
||||
)
|
||||
) : isImage ? (
|
||||
<RemoteContentImageEmbed
|
||||
url={url}
|
||||
tall
|
||||
stretch
|
||||
objectFit={objectFit ? objectFit : "cover"}
|
||||
/>
|
||||
) : isAudio ? (
|
||||
<AudioPlayer title={title} url={url} />
|
||||
) : isOembed ? (
|
||||
<RemoteContentOembed tall={!summary} renderUrl={false} url={url} thumbnail={summary} oembed={oembed} />
|
||||
) : (
|
||||
<RemoteContentEmbedFallback url={url} />
|
||||
)}
|
||||
<Box
|
||||
backgroundColor="white"
|
||||
display={summary && hovering ? 'block' : 'none'}
|
||||
width="100%"
|
||||
height="64px"
|
||||
position="absolute"
|
||||
left="0"
|
||||
bottom="0"
|
||||
>
|
||||
<Col width="100%" height="100%" p="2" justifyContent="space-between">
|
||||
<Row justifyContent="space-between" width="100%">
|
||||
<Text textOverflow="ellipsis" whiteSpace="nowrap" overflow="hidden">
|
||||
{title}
|
||||
</Text>
|
||||
<Row gapX="1" alignItems="center">
|
||||
<Icon icon="Chat" color="black" />
|
||||
<Text>{children.size}</Text>
|
||||
</Row>
|
||||
</Row>
|
||||
</Row>
|
||||
<Row width="100%">
|
||||
<Author ship={author} date={post['time-sent']} showImage></Author>
|
||||
</Row>
|
||||
</Col>
|
||||
</Box>
|
||||
</Center>
|
||||
<Row width="100%">
|
||||
<Author ship={author} date={post['time-sent']} showImage></Author>
|
||||
</Row>
|
||||
</Col>
|
||||
</Box>
|
||||
</Col>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Col, Row, RowProps } from '@tlon/indigo-react';
|
||||
import { Center, Col, Row, RowProps } from '@tlon/indigo-react';
|
||||
import { Association, GraphNode, markEachAsRead, TextContent, UrlContent } from '@urbit/api';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useGroup } from '~/logic/state/group';
|
||||
@ -27,21 +27,13 @@ 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"
|
||||
height={["50%", "50%", "100%"]}
|
||||
width={["100%", "100%", "calc(100% - 350px)"]}
|
||||
flexGrow={0}
|
||||
border={0}
|
||||
node={node}
|
||||
objectFit="contain"
|
||||
/>
|
||||
<Center flex="3 1 75%" overflowY="scroll" >
|
||||
<LinkBlockItem maxHeight="100%" border={0} node={node} objectFit="contain" />
|
||||
</Center>
|
||||
<Col
|
||||
minHeight="0"
|
||||
flexShrink={1}
|
||||
width={['100%', '100%', '350px']}
|
||||
flexGrow={0}
|
||||
flex="1 25%"
|
||||
maxWidth={['auto', 'auto', '45ch']}
|
||||
maxHeight={['50%', '50%', 'unset']}
|
||||
gapY={[2,4]}
|
||||
borderLeft={[0, 0, 1]}
|
||||
borderTop={[1, 1, 0]}
|
||||
|
@ -90,33 +90,33 @@ export function EditProfile(props: any): ReactElement {
|
||||
|
||||
const onSubmit = async (values: any, actions: any) => {
|
||||
try {
|
||||
Object.keys(values).forEach((key) => {
|
||||
for (const key in values) {
|
||||
const newValue = key !== 'color' ? values[key] : uxToHex(values[key]);
|
||||
if (newValue !== contact[key]) {
|
||||
if (key === 'isPublic') {
|
||||
airlock.poke(setPublic(newValue));
|
||||
return;
|
||||
} else if (key === 'groups') {
|
||||
const toRemove: string[] = _.difference(
|
||||
contact?.groups || [],
|
||||
newValue
|
||||
);
|
||||
const toAdd: string[] = _.difference(
|
||||
newValue,
|
||||
contact?.groups || []
|
||||
);
|
||||
toRemove.forEach(e =>
|
||||
airlock.poke(editContact(ship, { 'remove-group': resourceFromPath(e) }))
|
||||
);
|
||||
toAdd.forEach(e =>
|
||||
airlock.poke(editContact(ship, { 'add-group': resourceFromPath(e) }))
|
||||
);
|
||||
} else if (key !== 'last-updated' && key !== 'isPublic') {
|
||||
airlock.poke(editContact(ship, { [key]: newValue }));
|
||||
return;
|
||||
if (newValue === contact[key] || key === 'last-updated') {
|
||||
continue;
|
||||
} else if (key === 'isPublic') {
|
||||
await airlock.poke(setPublic(newValue));
|
||||
} else if (key === 'groups') {
|
||||
const toRemove: string[] = _.difference(
|
||||
contact?.groups || [],
|
||||
newValue
|
||||
);
|
||||
const toAdd: string[] = _.difference(
|
||||
newValue,
|
||||
contact?.groups || []
|
||||
);
|
||||
for (const i in toRemove) {
|
||||
const group = resourceFromPath(toRemove[i]);
|
||||
await airlock.poke(editContact(ship, { 'remove-group': group }));
|
||||
}
|
||||
for (const i in toAdd) {
|
||||
const group = resourceFromPath(toAdd[i]);
|
||||
await airlock.poke(editContact(ship, { 'add-group': group }));
|
||||
}
|
||||
} else {
|
||||
await airlock.poke(editContact(ship, { [key]: newValue }));
|
||||
}
|
||||
});
|
||||
}
|
||||
history.push(`/~profile/${ship}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
@ -23,6 +23,7 @@ interface MarkdownEditorProps {
|
||||
value: string;
|
||||
onChange: (s: string) => void;
|
||||
onBlur?: (e: any) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const PromptIfDirty = () => {
|
||||
@ -39,7 +40,7 @@ const PromptIfDirty = () => {
|
||||
export function MarkdownEditor(
|
||||
props: MarkdownEditorProps & PropFunc<typeof Box>
|
||||
) {
|
||||
const { onBlur, placeholder, value, onChange, ...boxProps } = props;
|
||||
const { onBlur, placeholder, value, onChange, disabled, ...boxProps } = props;
|
||||
|
||||
const options = {
|
||||
mode: MARKDOWN_CONFIG,
|
||||
@ -56,7 +57,9 @@ export function MarkdownEditor(
|
||||
|
||||
const handleChange = useCallback(
|
||||
(_e, _d, v: string) => {
|
||||
onChange(v);
|
||||
if (!disabled) {
|
||||
onChange(v);
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
@ -93,6 +96,7 @@ export function MarkdownEditor(
|
||||
p={1}
|
||||
border={1}
|
||||
borderColor="lightGray"
|
||||
backgroundColor={disabled ? '#eee' : '#fff'}
|
||||
borderRadius={2}
|
||||
height={['calc(100% - 22vh)', '100%']}
|
||||
{...boxProps}
|
||||
|
@ -6,14 +6,17 @@ import { MarkdownEditor } from './MarkdownEditor';
|
||||
|
||||
export const MarkdownField = ({
|
||||
id,
|
||||
disabled,
|
||||
...rest
|
||||
}: { id: string } & Parameters<typeof Box>[0]) => {
|
||||
}: { id: string; disabled?: boolean } & Parameters<typeof Box>[0]) => {
|
||||
const [{ value, onBlur }, { error, touched }, { setValue }] = useField(id);
|
||||
|
||||
const handleBlur = useCallback(
|
||||
(e: any) => {
|
||||
_.set(e, 'target.id', id);
|
||||
onBlur && onBlur(e);
|
||||
if (!disabled) {
|
||||
_.set(e, 'target.id', id);
|
||||
onBlur && onBlur(e);
|
||||
}
|
||||
},
|
||||
[onBlur, id]
|
||||
);
|
||||
@ -23,7 +26,7 @@ export const MarkdownField = ({
|
||||
return (
|
||||
<Box
|
||||
overflowY="hidden"
|
||||
height='100%'
|
||||
height="100%"
|
||||
width="100%"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
@ -35,6 +38,7 @@ export const MarkdownField = ({
|
||||
onBlur={handleBlur}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<ErrorLabel mt={2} hasError={Boolean(error && touched)}>
|
||||
{error}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import {
|
||||
Button, Col, ManagedTextInputField as Input,
|
||||
Button,
|
||||
Col,
|
||||
ManagedTextInputField as Input,
|
||||
Row
|
||||
} from '@tlon/indigo-react';
|
||||
import { Form, Formik, FormikHelpers } from 'formik';
|
||||
@ -31,7 +33,8 @@ export interface PostFormSchema {
|
||||
}
|
||||
|
||||
export function PostForm(props: PostFormProps) {
|
||||
const { initial, onSubmit, submitLabel, loadingText, cancel, history } = props;
|
||||
const { initial, onSubmit, submitLabel, loadingText, cancel, history } =
|
||||
props;
|
||||
|
||||
return (
|
||||
<Col width="100%" height="100%" p={[2, 4]}>
|
||||
@ -40,30 +43,49 @@ export function PostForm(props: PostFormProps) {
|
||||
initialValues={initial}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form style={{ display: 'contents' }}>
|
||||
<Row flexShrink={0} flexDirection={['column-reverse', 'row']} mb={4} gapX={4} justifyContent='space-between'>
|
||||
<Input maxWidth='40rem' width='100%' flexShrink={[0, 1]} placeholder="Post Title" id="title" />
|
||||
<Row flexDirection={['column', 'row']} mb={[4,0]}>
|
||||
<AsyncButton
|
||||
ml={[0,2]}
|
||||
flexShrink={0}
|
||||
primary
|
||||
loadingText={loadingText}
|
||||
>
|
||||
{submitLabel}
|
||||
</AsyncButton>
|
||||
{cancel && <Button
|
||||
ml={[0,2]}
|
||||
mt={[2,0]}
|
||||
onClick={() => {
|
||||
history.goBack();
|
||||
}}
|
||||
type="button"
|
||||
>Cancel</Button>}
|
||||
{({ isSubmitting }) => (
|
||||
<Form style={{ display: 'contents' }}>
|
||||
<Row
|
||||
flexShrink={0}
|
||||
flexDirection={['column-reverse', 'row']}
|
||||
mb={4}
|
||||
gapX={4}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Input
|
||||
maxWidth="40rem"
|
||||
width="100%"
|
||||
flexShrink={[0, 1]}
|
||||
placeholder="Post Title"
|
||||
id="title"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<Row flexDirection={['column', 'row']} mb={[4, 0]}>
|
||||
<AsyncButton
|
||||
ml={[0, 2]}
|
||||
flexShrink={0}
|
||||
primary
|
||||
loadingText={loadingText}
|
||||
>
|
||||
{submitLabel}
|
||||
</AsyncButton>
|
||||
{cancel && (
|
||||
<Button
|
||||
ml={[0, 2]}
|
||||
mt={[2, 0]}
|
||||
onClick={() => {
|
||||
history.goBack();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</Row>
|
||||
</Row>
|
||||
</Row>
|
||||
<MarkdownField flexGrow={1} id="body" />
|
||||
</Form>
|
||||
<MarkdownField flexGrow={1} id="body" disabled={isSubmitting} />
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Col>
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import moment from 'moment';
|
||||
import React, { ReactElement, ReactNode } from 'react';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { useCopy } from '~/logic/lib/useCopy';
|
||||
import { cite, uxToHex } from '~/logic/lib/util';
|
||||
import { cite, deSig, uxToHex } from '~/logic/lib/util';
|
||||
import { useContact } from '~/logic/state/contact';
|
||||
import { useDark } from '~/logic/state/join';
|
||||
import useSettingsState, { selectCalmState, useShowNickname } from '~/logic/state/settings';
|
||||
@ -52,7 +52,7 @@ function Author(props: AuthorProps & PropFunc<typeof Box>): ReactElement {
|
||||
const { hideAvatars } = useSettingsState(selectCalmState);
|
||||
const name = showNickname && contact ? contact.nickname : cite(ship);
|
||||
const stamp = moment(date);
|
||||
const { copyDisplay, doCopy } = useCopy(`~${ship}`, name);
|
||||
const { copyDisplay, doCopy } = useCopy(`~${deSig(ship)}`, name);
|
||||
|
||||
const sigil = fullNotIcon ? (
|
||||
<Sigil ship={ship} size={size} color={color} padding={sigilPadding} />
|
||||
|
@ -28,7 +28,7 @@ import { Link } from 'react-router-dom';
|
||||
import { AppPermalink, referenceToPermalink } from '~/logic/lib/permalinks';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import { RemoteContentWrapper } from './wrapper';
|
||||
import { useEmbed } from '~/logic/state/embed';
|
||||
import { Suspender } from '~/logic/lib/suspend';
|
||||
import { IS_SAFARI } from '~/logic/lib/platform';
|
||||
import useDocketState, { useTreaty } from '~/logic/state/docket';
|
||||
import { AppTile } from '~/views/apps/permalinks/embed';
|
||||
@ -97,6 +97,7 @@ export function RemoteContentImageEmbed(
|
||||
objectFit="cover"
|
||||
borderRadius={2}
|
||||
onError={onError}
|
||||
style={{ imageRendering: '-webkit-optimize-contrast' }}
|
||||
{...props}
|
||||
/>
|
||||
</Box>
|
||||
@ -319,6 +320,7 @@ type RemoteContentOembedProps = {
|
||||
renderUrl?: boolean;
|
||||
thumbnail?: boolean;
|
||||
tall?: boolean;
|
||||
oembed: Suspender<any>;
|
||||
} & RemoteContentEmbedProps &
|
||||
PropFunc<typeof Box>;
|
||||
|
||||
@ -332,10 +334,9 @@ export const RemoteContentOembed = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
RemoteContentOembedProps
|
||||
>((props, ref) => {
|
||||
const { url, renderUrl = false, thumbnail = false, ...rest } = props;
|
||||
const oembed = useEmbed(url);
|
||||
const { url, oembed, renderUrl = false, thumbnail = false, ...rest } = props;
|
||||
|
||||
const embed = oembed.read();
|
||||
const fallbackError = new Error('fallback');
|
||||
|
||||
const [aspect, width, height] = useMemo(() => {
|
||||
if(!('height' in embed && typeof embed.height === 'number'
|
||||
@ -373,11 +374,9 @@ export const RemoteContentOembed = React.forwardRef<
|
||||
dangerouslySetInnerHTML={{ __html: embed.html }}
|
||||
></EmbedBox>
|
||||
</EmbedContainer>
|
||||
) : renderUrl ? (
|
||||
) : (
|
||||
<RemoteContentEmbedFallback url={url} />
|
||||
) : (() => {
|
||||
throw fallbackError;
|
||||
})()
|
||||
)
|
||||
}
|
||||
</Col>
|
||||
);
|
||||
|
@ -1,12 +1,17 @@
|
||||
import { hasProvider } from 'oembed-parser';
|
||||
import {
|
||||
Box,
|
||||
} from '@tlon/indigo-react';
|
||||
import React from 'react';
|
||||
import useSettingsState from '~/logic/state/settings';
|
||||
import {
|
||||
RemoteContentAudioEmbed,
|
||||
RemoteContentImageEmbed,
|
||||
RemoteContentOembed,
|
||||
RemoteContentVideoEmbed
|
||||
RemoteContentVideoEmbed,
|
||||
RemoteContentEmbedFallback
|
||||
} from './embed';
|
||||
import { useEmbed } from '~/logic/state/embed';
|
||||
import { Suspender } from '~/logic/lib/suspend';
|
||||
import { TruncatedText } from '~/views/components/TruncatedText';
|
||||
import { RemoteContentWrapper } from './wrapper';
|
||||
import AsyncFallback from '../AsyncFallback';
|
||||
@ -43,8 +48,34 @@ export const IMAGE_REGEX = new RegExp(
|
||||
export const AUDIO_REGEX = new RegExp(/(\.mp3|\.wav|\.ogg|\.m4a)$/i);
|
||||
export const VIDEO_REGEX = new RegExp(/(\.mov|\.mp4|\.ogv)$/i);
|
||||
|
||||
// This is used to prevent our oembed parser from
|
||||
// trying to embed facebook/instagram links, which require an API key
|
||||
const isFacebookGraphDependent = (url: string) => {
|
||||
const caseDesensitizedURL = url.toLowerCase()
|
||||
return (caseDesensitizedURL.includes('facebook.com') || caseDesensitizedURL.includes('instagram.com'))
|
||||
}
|
||||
|
||||
export const validOembedCheck = (embed: Suspender<any>, url: string) => {
|
||||
if (!isFacebookGraphDependent(url)) {
|
||||
if (!embed.read().hasOwnProperty("error")) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export const RemoteContent = (props: RemoteContentProps) => {
|
||||
const {url, ...rest} = props
|
||||
return(
|
||||
<AsyncFallback fallback={<RemoteContentEmbedFallback url={url} />}>
|
||||
<RemoteContentInner url={url} {...rest}/>
|
||||
</AsyncFallback>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const emptyRef = () => {};
|
||||
export function RemoteContent(props: RemoteContentProps) {
|
||||
function RemoteContentInner(props: RemoteContentProps) {
|
||||
const {
|
||||
url,
|
||||
embedRef = emptyRef,
|
||||
@ -57,45 +88,51 @@ export function RemoteContent(props: RemoteContentProps) {
|
||||
const isImage = IMAGE_REGEX.test(url);
|
||||
const isAudio = AUDIO_REGEX.test(url);
|
||||
const isVideo = VIDEO_REGEX.test(url);
|
||||
const isOembed = hasProvider(url);
|
||||
const oembed = useEmbed(url);
|
||||
const isOembed = validOembedCheck(oembed, url);
|
||||
|
||||
const wrapperProps = {
|
||||
url,
|
||||
tall,
|
||||
embedOnly: !renderUrl || tall
|
||||
};
|
||||
|
||||
const fallback = !renderUrl ? null : (
|
||||
<RemoteContentWrapper {...wrapperProps}>
|
||||
<TruncatedText>{url}</TruncatedText>
|
||||
</RemoteContentWrapper>
|
||||
);
|
||||
const fallback = null;
|
||||
|
||||
if (isImage && remoteContentPolicy.imageShown) {
|
||||
return (
|
||||
<RemoteContentWrapper {...wrapperProps} noOp={transcluded} replaced>
|
||||
<RemoteContentImageEmbed url={url} />
|
||||
</RemoteContentWrapper>
|
||||
<Box mt={1} mb={2} flexShrink={0}>
|
||||
<RemoteContentWrapper {...wrapperProps} noOp={transcluded} replaced>
|
||||
<RemoteContentImageEmbed url={url} />
|
||||
</RemoteContentWrapper>
|
||||
</Box>
|
||||
);
|
||||
} else if (isAudio && remoteContentPolicy.audioShown) {
|
||||
return (
|
||||
<RemoteContentWrapper {...wrapperProps}>
|
||||
<RemoteContentAudioEmbed url={url} />
|
||||
</RemoteContentWrapper>
|
||||
<Box mt={1} mb={2} flexShrink={0}>
|
||||
<RemoteContentWrapper {...wrapperProps}>
|
||||
<RemoteContentAudioEmbed url={url} />
|
||||
</RemoteContentWrapper>
|
||||
</Box>
|
||||
);
|
||||
} else if (isVideo && remoteContentPolicy.videoShown) {
|
||||
return (
|
||||
<RemoteContentWrapper
|
||||
{...wrapperProps}
|
||||
detail={<RemoteContentVideoEmbed url={url} />}
|
||||
>
|
||||
<TruncatedText>{url}</TruncatedText>
|
||||
</RemoteContentWrapper>
|
||||
<Box mt={1} mb={2} flexShrink={0}>
|
||||
<RemoteContentWrapper
|
||||
{...wrapperProps}
|
||||
detail={<RemoteContentVideoEmbed url={url} />}
|
||||
>
|
||||
<TruncatedText>{url}</TruncatedText>
|
||||
</RemoteContentWrapper>
|
||||
</Box>
|
||||
);
|
||||
} else if (isOembed && remoteContentPolicy.oembedShown) {
|
||||
return (
|
||||
<AsyncFallback fallback={fallback}>
|
||||
<RemoteContentOembed ref={embedRef} url={url} renderUrl={renderUrl} />
|
||||
</AsyncFallback>
|
||||
<Box mt={1} mb={2} flexShrink={0}>
|
||||
<AsyncFallback fallback={fallback}>
|
||||
<RemoteContentOembed ref={embedRef} url={url} renderUrl={renderUrl} oembed={oembed} />
|
||||
</AsyncFallback>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return fallback;
|
||||
|
@ -74,7 +74,7 @@ export function GraphPermissions(props: GraphPermissionsProps) {
|
||||
const writers = _.get(
|
||||
group?.tags,
|
||||
['graph', association.resource, 'writers'],
|
||||
new Set()
|
||||
[]
|
||||
);
|
||||
|
||||
let [, , hostShip] = association.resource.split('/');
|
||||
@ -91,7 +91,7 @@ export function GraphPermissions(props: GraphPermissionsProps) {
|
||||
|
||||
const initialValues = {
|
||||
writePerms,
|
||||
writers: Array.from(writers)
|
||||
writers: writers
|
||||
.filter(x => x !== hostShip),
|
||||
readerComments: association.metadata.vip === 'reader-comments'
|
||||
};
|
||||
@ -104,7 +104,7 @@ export function GraphPermissions(props: GraphPermissionsProps) {
|
||||
resource: association.resource,
|
||||
tag: 'writers'
|
||||
};
|
||||
const allWriters = Array.from(writers).map(w => `~${w}`);
|
||||
const allWriters = writers.map(w => `~${w}`);
|
||||
if (values.readerComments !== readerComments) {
|
||||
await airlock.poke(metadataEdit(association, {
|
||||
vip: values.readerComments ? 'reader-comments' : ''
|
||||
@ -170,7 +170,7 @@ export function GraphPermissions(props: GraphPermissionsProps) {
|
||||
<Col>
|
||||
<Label mb={2}>Permissions Summary</Label>
|
||||
<PermissionsSummary
|
||||
writersSize={writers.size}
|
||||
writersSize={writers.length}
|
||||
vip={association.metadata.vip}
|
||||
/>
|
||||
</Col>
|
||||
|
@ -147,13 +147,14 @@ const contentToMdAst = (tall: boolean) => (
|
||||
];
|
||||
} else if ('url' in content) {
|
||||
return [
|
||||
'block',
|
||||
'inline',
|
||||
{
|
||||
type: 'root',
|
||||
children: [
|
||||
{
|
||||
type: 'graph-url',
|
||||
url: content.url
|
||||
type: 'link',
|
||||
url: content.url,
|
||||
children: [{ type: 'text', value: content.url }]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -186,8 +187,20 @@ function stitchInline(a: any, b: any) {
|
||||
if (!a?.children) {
|
||||
throw new Error('Bad stitchInline call: missing root');
|
||||
}
|
||||
|
||||
const lastParaIdx = a.children.length - 1;
|
||||
const last = a.children[lastParaIdx];
|
||||
|
||||
// wrap bare link in list-item inside a p node
|
||||
// for better typography consistency
|
||||
if (last?.type === 'listItem') {
|
||||
if (last?.children.length === 0) {
|
||||
last.children.push({
|
||||
type: 'paragraph',
|
||||
children: []
|
||||
});
|
||||
}
|
||||
}
|
||||
if (last?.children) {
|
||||
const ros = {
|
||||
...a,
|
||||
@ -217,7 +230,7 @@ function getChildren<T extends unknown>(node: T): AstContent[] {
|
||||
}
|
||||
|
||||
export function asParent<T extends BlockContent>(node: T): Parent | undefined {
|
||||
return ['paragraph', 'heading', 'list', 'listItem', 'table'].includes(
|
||||
return ['paragraph', 'heading', 'list', 'listItem', 'table', 'blockquote'].includes(
|
||||
node.type
|
||||
)
|
||||
? (node as Parent)
|
||||
@ -241,6 +254,7 @@ function stitchMerge(a: Root, b: Root) {
|
||||
children: [...aChildren.slice(0, -1), mergedPara, ...bChildren.slice(1)]
|
||||
};
|
||||
}
|
||||
|
||||
return { ...a, children: [...aChildren, ...bChildren] };
|
||||
}
|
||||
|
||||
@ -256,10 +270,10 @@ function stitchInlineAfterBlock(a: Root, b: GraphMentionNode[]) {
|
||||
}
|
||||
|
||||
function stitchAsts(asts: [StitchMode, GraphAstNode][]) {
|
||||
return _.reduce(
|
||||
const t = _.reduce(
|
||||
asts,
|
||||
([prevMode, ast], [mode, val]): [StitchMode, GraphAstNode] => {
|
||||
if (prevMode === 'block') {
|
||||
if (prevMode === 'block' || prevMode === 'inline') {
|
||||
if (mode === 'inline') {
|
||||
return [mode, stitchInlineAfterBlock(ast, val?.children ?? [])];
|
||||
}
|
||||
@ -283,6 +297,56 @@ function stitchAsts(asts: [StitchMode, GraphAstNode][]) {
|
||||
},
|
||||
['block', { type: 'root', children: [] }] as [StitchMode, GraphAstNode]
|
||||
);
|
||||
|
||||
t[1].children.map((c, idx) => {
|
||||
if (c.type === 'blockquote' && t[1].children[idx +1] !== undefined && t[1].children[idx +1].type === 'paragraph') {
|
||||
const next = idx !== t[1].children.length -1
|
||||
? t[1].children.splice(idx +1, 1)
|
||||
: [];
|
||||
|
||||
if (next.length > 0) {
|
||||
t[1].children[idx].children.push(next[0]);
|
||||
}
|
||||
}
|
||||
|
||||
const links = [];
|
||||
function addRichEmbedURL(nodes) {
|
||||
if (nodes?.children) {
|
||||
nodes.children.filter((k) => {
|
||||
if (k.type === 'link') {
|
||||
links.push({
|
||||
type: 'root',
|
||||
children: [
|
||||
{
|
||||
type: 'graph-url',
|
||||
url: k.url
|
||||
}
|
||||
]
|
||||
});
|
||||
} else if (k?.children) {
|
||||
k.children.filter((o) => {
|
||||
if (o.type === 'link') {
|
||||
links.push({
|
||||
type: 'root',
|
||||
children: [
|
||||
{
|
||||
type: 'graph-url',
|
||||
url: o.url
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
nodes.children.push(...links);
|
||||
}
|
||||
}
|
||||
addRichEmbedURL(c);
|
||||
});
|
||||
|
||||
return t;
|
||||
}
|
||||
const header = ({ children, depth, ...rest }) => {
|
||||
const level = depth;
|
||||
@ -408,7 +472,7 @@ const renderers = {
|
||||
);
|
||||
},
|
||||
list: ({ depth, ordered, children }) => {
|
||||
return ordered ? <Ol>{children}</Ol> : <Ul>{children}</Ul>;
|
||||
return ordered ? <Ol fontSize="1">{children}</Ol> : <Ul fontSize="1">{children}</Ul>;
|
||||
},
|
||||
'graph-mention': (obj) => {
|
||||
return <Mention ship={obj.ship} emphasis={obj.emphasis} />;
|
||||
@ -419,9 +483,7 @@ const renderers = {
|
||||
</Box>
|
||||
),
|
||||
'graph-url': ({ url, tall }) => (
|
||||
<Box mt={1} mb={2} flexShrink={0}>
|
||||
<RemoteContent key={url} url={url} tall={tall} />
|
||||
</Box>
|
||||
<RemoteContent key={url} url={url} tall={tall} />
|
||||
),
|
||||
'graph-reference': ({ reference, transcluded }) => {
|
||||
const { link } = referenceToPermalink({ reference });
|
||||
|
@ -1,128 +1,126 @@
|
||||
/* eslint-disable */
|
||||
/** pulled from remark-parse
|
||||
*
|
||||
*
|
||||
* critical change is that blockquotes require a newline to be continued, see
|
||||
* the `if(!prefixed) conditional
|
||||
*/
|
||||
'use strict'
|
||||
"use strict";
|
||||
|
||||
var trim = require('trim')
|
||||
var interrupt = require('remark-parse/lib/util/interrupt')
|
||||
var trim = require("trim");
|
||||
|
||||
module.exports = blockquote
|
||||
module.exports = blockquote;
|
||||
|
||||
var lineFeed = '\n'
|
||||
var tab = '\t'
|
||||
var space = ' '
|
||||
var greaterThan = '>'
|
||||
var lineFeed = "\n";
|
||||
var tab = "\t";
|
||||
var space = " ";
|
||||
var greaterThan = ">";
|
||||
|
||||
function blockquote(eat, value, silent) {
|
||||
var self = this
|
||||
var offsets = self.offset
|
||||
var tokenizers = self.blockTokenizers
|
||||
var interruptors = self.interruptBlockquote
|
||||
var now = eat.now()
|
||||
var currentLine = now.line
|
||||
var length = value.length
|
||||
var values = []
|
||||
var contents = []
|
||||
var indents = []
|
||||
var add
|
||||
var index = 0
|
||||
var character
|
||||
var rest
|
||||
var nextIndex
|
||||
var content
|
||||
var line
|
||||
var startIndex
|
||||
var prefixed
|
||||
var exit
|
||||
var self = this;
|
||||
var offsets = self.offset;
|
||||
var tokenizers = self.blockTokenizers;
|
||||
var interruptors = self.interruptBlockquote;
|
||||
var now = eat.now();
|
||||
var currentLine = now.line;
|
||||
var length = value.length;
|
||||
var values = [];
|
||||
var contents = [];
|
||||
var indents = [];
|
||||
var add;
|
||||
var index = 0;
|
||||
var character;
|
||||
var rest;
|
||||
var nextIndex;
|
||||
var content;
|
||||
var line;
|
||||
var startIndex;
|
||||
var prefixed;
|
||||
var exit;
|
||||
|
||||
while (index < length) {
|
||||
character = value.charAt(index)
|
||||
character = value.charAt(index);
|
||||
|
||||
if (character !== space && character !== tab) {
|
||||
break
|
||||
break;
|
||||
}
|
||||
|
||||
index++
|
||||
index++;
|
||||
}
|
||||
|
||||
if (value.charAt(index) !== greaterThan) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (silent) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
index = 0
|
||||
index = 0;
|
||||
|
||||
while (index < length) {
|
||||
nextIndex = value.indexOf(lineFeed, index)
|
||||
startIndex = index
|
||||
prefixed = false
|
||||
nextIndex = value.indexOf(lineFeed, index);
|
||||
startIndex = index;
|
||||
prefixed = false;
|
||||
|
||||
if (nextIndex === -1) {
|
||||
nextIndex = length
|
||||
nextIndex = length;
|
||||
}
|
||||
|
||||
while (index < length) {
|
||||
character = value.charAt(index)
|
||||
character = value.charAt(index);
|
||||
|
||||
if (character !== space && character !== tab) {
|
||||
break
|
||||
break;
|
||||
}
|
||||
|
||||
index++
|
||||
index++;
|
||||
}
|
||||
|
||||
if (value.charAt(index) === greaterThan) {
|
||||
index++
|
||||
prefixed = true
|
||||
index++;
|
||||
prefixed = true;
|
||||
|
||||
if (value.charAt(index) === space) {
|
||||
index++
|
||||
index++;
|
||||
}
|
||||
} else {
|
||||
index = startIndex
|
||||
index = startIndex;
|
||||
}
|
||||
|
||||
content = value.slice(index, nextIndex)
|
||||
content = value.slice(index, nextIndex);
|
||||
|
||||
if (!prefixed && !trim(content)) {
|
||||
index = startIndex
|
||||
break
|
||||
index = startIndex;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!prefixed) {
|
||||
break;
|
||||
}
|
||||
|
||||
line = startIndex === index ? content : value.slice(startIndex, nextIndex)
|
||||
line = startIndex === index ? content : value.slice(startIndex, nextIndex);
|
||||
|
||||
indents.push(index - startIndex)
|
||||
values.push(line)
|
||||
contents.push(content)
|
||||
indents.push(index - startIndex);
|
||||
values.push(line);
|
||||
contents.push(content);
|
||||
|
||||
index = nextIndex + 1
|
||||
index = nextIndex + 1;
|
||||
}
|
||||
const trailingNewline = value.charAt(nextIndex) === '\n';
|
||||
const trailingNewline = value.charAt(nextIndex) === "\n";
|
||||
|
||||
index = -1
|
||||
length = indents.length
|
||||
add = eat(values.join(lineFeed))
|
||||
index = -1;
|
||||
length = indents.length;
|
||||
add = eat(values.join(lineFeed));
|
||||
|
||||
while (++index < length) {
|
||||
offsets[currentLine] = (offsets[currentLine] || 0) + indents[index]
|
||||
currentLine++
|
||||
offsets[currentLine] = (offsets[currentLine] || 0) + indents[index];
|
||||
currentLine++;
|
||||
}
|
||||
|
||||
exit = self.enterBlock()
|
||||
contents = self.tokenizeBlock(contents.join(lineFeed), now)
|
||||
console.log(values);
|
||||
exit()
|
||||
exit = self.enterBlock();
|
||||
contents = self.tokenizeBlock(contents.join(lineFeed), now);
|
||||
exit();
|
||||
|
||||
const added = add({type: 'blockquote', children: contents})
|
||||
return trailingNewline ? add({ type: 'paragraph', children: [] }) : added;
|
||||
const added = add({ type: "blockquote", children: contents });
|
||||
return trailingNewline ? add({ type: "paragraph", children: [] }) : added;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
} from 'react-router-dom';
|
||||
import { useShortcut } from '~/logic/state/settings';
|
||||
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
|
||||
import { getGroupFromWorkspace } from '~/logic/lib/workspace';
|
||||
import { getGroupFromWorkspace, getTitleFromWorkspace } from '~/logic/lib/workspace';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
@ -22,7 +22,7 @@ import { Skeleton } from './Skeleton';
|
||||
import { EmptyGroupHome } from './Home/EmptyGroupHome';
|
||||
import { Join } from './Join/Join';
|
||||
import { Resource } from './Resource';
|
||||
import { DmResource } from '~/views/apps/chat/DmResource';
|
||||
import { DmResource, DmHelmet } from '~/views/apps/chat/DmResource';
|
||||
import { UnjoinedResource } from '~/views/components/UnjoinedResource';
|
||||
import { NewChannel } from './NewChannel';
|
||||
import { GroupHome } from './Home/GroupHome';
|
||||
@ -126,16 +126,18 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
const { ship } = match.params as Record<string, string>;
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
mobileHide
|
||||
recentGroups={recentGroups}
|
||||
selected={ship}
|
||||
{...props}
|
||||
baseUrl={match.path}
|
||||
> <DmResource ship={ship} />
|
||||
|
||||
</Skeleton>
|
||||
<>
|
||||
<DmHelmet ship={ship} />
|
||||
<Skeleton
|
||||
mobileHide
|
||||
recentGroups={recentGroups}
|
||||
selected={ship}
|
||||
{...props}
|
||||
baseUrl={match.path}
|
||||
> <DmResource ship={ship} />
|
||||
|
||||
</Skeleton>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@ -180,7 +182,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
const appPath = `/ship/${host}/${name}`;
|
||||
const association = associations.graph[appPath];
|
||||
const resourceUrl = `${baseUrl}/join/${app}${appPath}`;
|
||||
let title = groupAssociation?.metadata?.title ?? 'Groups';
|
||||
let title = getTitleFromWorkspace(associations, workspace);
|
||||
|
||||
if (!association) {
|
||||
return <Loading />;
|
||||
@ -252,7 +254,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
render={(routeProps) => {
|
||||
const shouldHideSidebar =
|
||||
routeProps.location.pathname.includes('/feed');
|
||||
const title = groupAssociation?.metadata?.title ?? 'Groups';
|
||||
const title = getTitleFromWorkspace(associations, workspace);
|
||||
return (
|
||||
<>
|
||||
<Helmet defer={false}>
|
||||
|
@ -352,7 +352,7 @@ function Participant(props: {
|
||||
</Link>
|
||||
</Action>
|
||||
<Action bg="transparent">
|
||||
<Link to={`/~landscape/dm/${contact.patp}`}>
|
||||
<Link to={`/~landscape/messages/dm/~${contact.patp}`}>
|
||||
<Text color="green">Send Message</Text>
|
||||
</Link>
|
||||
</Action>
|
||||
|
@ -190,6 +190,11 @@
|
||||
|%
|
||||
++ pull-action pull-hook-action+!>([%add ship rid])
|
||||
::
|
||||
++ listen-hark
|
||||
|= gr=resource
|
||||
%+ poke-our:pass:io %hark-graph-hook
|
||||
hark-graph-hook-action+!>([%listen gr /])
|
||||
::
|
||||
++ watch-md (watch-our:(jn-pass-io /md) %metadata-store /updates)
|
||||
++ watch-groups (watch-our:(jn-pass-io /groups) %group-store /groups)
|
||||
++ watch-md-nacks (watch-our:(jn-pass-io /md-nacks) %metadata-pull-hook /nack)
|
||||
@ -436,6 +441,9 @@
|
||||
=? jn-core |(hidden autojoin.request)
|
||||
%- emit-many
|
||||
(turn graphs pull-gra:pass)
|
||||
=? jn-core hidden
|
||||
%- emit-many
|
||||
(turn graphs listen-hark:pass)
|
||||
jn-core
|
||||
::
|
||||
++ feed-rid
|
||||
|
@ -1,7 +1,7 @@
|
||||
:~ title+'Groups'
|
||||
info+'A suite of applications to communicate on Urbit'
|
||||
color+0xee.5432
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v7.bmftr.90ktq.cma0h.da190.bs8b1.glob' 0v7.bmftr.90ktq.cma0h.da190.bs8b1]
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v4.2se6m.fvv67.nn5e8.vfrv9.mmi88.glob' 0v4.2se6m.fvv67.nn5e8.vfrv9.mmi88]
|
||||
|
||||
base+'landscape'
|
||||
version+[1 0 11]
|
||||
|
@ -35,6 +35,8 @@
|
||||
(poke-our %group-store group-update-0+!>([%add-members rid (sy our.bowl ~)]))
|
||||
;< ~ bind:m
|
||||
(poke-our %group-push-hook push-hook-act)
|
||||
;< ~ bind:m
|
||||
(poke-our %hark-graph-hook hark-graph-hook-action+!>([%listen rid /]))
|
||||
(pure:m rid)
|
||||
--
|
||||
::
|
||||
|
@ -38,4 +38,6 @@
|
||||
(raw-poke-our %contact-pull-hook pull-hook-act)
|
||||
;< ~ bind:m
|
||||
(raw-poke-our %group-store remove)
|
||||
;< ~ bind:m
|
||||
(raw-poke-our %group-view group-view-action+!>([%done rid]))
|
||||
(pure:m !>(~))
|
||||
|
Loading…
Reference in New Issue
Block a user