mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-03 04:40:50 +03:00
Merge branch 'os1-rc' of https://github.com/urbit/urbit into m/link-meta
This commit is contained in:
commit
396d13ee1a
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:41dcebff628fa80e316b64e38309b703cc459518e50fc6b845a52569a5d35f10
|
||||
size 10775767
|
||||
oid sha256:c530047e7210f68498940374e3f1679a0cf7ae1722d159ca172a7359459a9cdc
|
||||
size 10870523
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2105b8e617f7e17a9f70f086e6ee30588002ea4cc846a7cb9a371eebef5e2afa
|
||||
size 13200641
|
||||
oid sha256:1362758ea72a8dfa41a2a283b8cb881e7603e3aad047344cc2b2a3331cb4d52a
|
||||
size 13298466
|
||||
|
@ -224,13 +224,16 @@
|
||||
?> ?=(^ app-path.act)
|
||||
%- zing
|
||||
:~ :~ (chat-hook-poke [%remove app-path.act])
|
||||
(metadata-store-poke [%remove group-path [%chat app-path.act]])
|
||||
(chat-poke [%delete app-path.act])
|
||||
==
|
||||
::
|
||||
?. (is-creator group-path %chat app-path.act) ~
|
||||
[(metadata-poke [%remove group-path [%chat app-path.act]])]~
|
||||
::
|
||||
?: (is-managed group-path) ~
|
||||
:~ (group-poke [%unbundle group-path])
|
||||
(metadata-hook-poke [%remove group-path])
|
||||
(metadata-store-poke [%remove group-path [%chat app-path.act]])
|
||||
==
|
||||
==
|
||||
::
|
||||
@ -300,7 +303,7 @@
|
||||
?: (is-managed app-path) (snag 0 app-path)
|
||||
(snag 1 app-path)
|
||||
==
|
||||
:~ (metadata-store-poke [%add group-path [%chat app-path] metadata])
|
||||
:~ (metadata-poke [%add group-path [%chat app-path] metadata])
|
||||
(metadata-hook-poke [%add-owned group-path])
|
||||
==
|
||||
::
|
||||
@ -309,6 +312,11 @@
|
||||
^- card
|
||||
[%pass / %agent [our.bol %contact-view] %poke %contact-view-action !>(act)]
|
||||
::
|
||||
++ metadata-poke
|
||||
|= act=metadata-action
|
||||
^- card
|
||||
[%pass / %agent [our.bol %metadata-hook] %poke %metadata-action !>(act)]
|
||||
::
|
||||
++ metadata-store-poke
|
||||
|= act=metadata-action
|
||||
^- card
|
||||
@ -364,6 +372,23 @@
|
||||
^- ?
|
||||
?> ?=(^ path)
|
||||
!=(i.path '~')
|
||||
::
|
||||
++ is-creator
|
||||
|= [group-path=path app-name=@ta app-path=path]
|
||||
^- ?
|
||||
=/ =metadata
|
||||
.^ metadata
|
||||
%gx
|
||||
(scot %p our.bol)
|
||||
%metadata-store
|
||||
(scot %da now.bol)
|
||||
%metadata
|
||||
(scot %t (spat group-path))
|
||||
app-name
|
||||
(scot %t (spat app-path))
|
||||
/noun
|
||||
==
|
||||
=(our.bol creator.metadata)
|
||||
--
|
||||
::
|
||||
++ diff-chat-update
|
||||
|
File diff suppressed because one or more lines are too long
@ -42,6 +42,9 @@
|
||||
=^ cards state
|
||||
(poke-hook-action:hc !<(metadata-hook-action vase))
|
||||
[cards this]
|
||||
::
|
||||
%metadata-action
|
||||
[(poke-action:hc !<(metadata-action vase)) this]
|
||||
==
|
||||
::
|
||||
++ on-watch
|
||||
@ -106,6 +109,43 @@
|
||||
[%pass path %agent [ship %metadata-hook] %leave ~]~
|
||||
--
|
||||
::
|
||||
++ poke-action
|
||||
|= act=metadata-action
|
||||
^- (list card)
|
||||
|^
|
||||
?: (team:title our.bowl src.bowl)
|
||||
?- -.act
|
||||
%add (send group-path.act)
|
||||
%remove (send group-path.act)
|
||||
==
|
||||
?> (is-permitted src.bowl group-path.act)
|
||||
?- -.act
|
||||
%add (metadata-poke our.bowl %metadata-store)
|
||||
%remove (metadata-poke our.bowl %metadata-store)
|
||||
==
|
||||
::
|
||||
++ send
|
||||
|= =group-path
|
||||
^- (list card)
|
||||
=/ =ship
|
||||
%+ slav %p
|
||||
?: (is-managed group-path) (snag 0 group-path)
|
||||
(snag 1 group-path)
|
||||
=/ app ?:(=(ship our.bowl) %metadata-store %metadata-hook)
|
||||
(metadata-poke ship app)
|
||||
::
|
||||
++ metadata-poke
|
||||
|= [=ship app=@tas]
|
||||
^- (list card)
|
||||
[%pass / %agent [ship app] %poke %metadata-action !>(act)]~
|
||||
::
|
||||
++ is-managed
|
||||
|= =path
|
||||
^- ?
|
||||
?> ?=(^ path)
|
||||
!=(i.path '~')
|
||||
--
|
||||
::
|
||||
++ watch-group
|
||||
|= =path
|
||||
^- (list card)
|
||||
@ -117,17 +157,6 @@
|
||||
^- card
|
||||
[%give %fact ~ %metadata-update !>([%add group-path resource metadata])]
|
||||
::
|
||||
++ is-permitted
|
||||
|= [=ship pax=^path]
|
||||
^- ?
|
||||
=. pax
|
||||
;: weld
|
||||
/=permission-store/(scot %da now.bowl)/permitted
|
||||
[(scot %p ship) pax]
|
||||
/noun
|
||||
==
|
||||
.^(? %gx pax)
|
||||
::
|
||||
++ metadata-scry
|
||||
|= pax=^path
|
||||
^- associations
|
||||
@ -203,4 +232,15 @@
|
||||
^- (quip card _state)
|
||||
?> ?=(^ wir)
|
||||
[~ ?~(saw state state(synced (~(del by synced) t.wir)))]
|
||||
::
|
||||
++ is-permitted
|
||||
|= [=ship pax=path]
|
||||
^- ?
|
||||
=. pax
|
||||
;: weld
|
||||
/=permission-store/(scot %da now.bowl)/permitted
|
||||
[(scot %p ship) pax]
|
||||
/noun
|
||||
==
|
||||
.^(? %gx pax)
|
||||
--
|
||||
|
@ -8,7 +8,7 @@
|
||||
%+ turn ~(tap by associations)
|
||||
|= [[=group-path =resource] =metadata]
|
||||
^- [cord json]
|
||||
:-
|
||||
:-
|
||||
%- crip
|
||||
;: weld
|
||||
(trip (spat group-path))
|
||||
@ -22,6 +22,50 @@
|
||||
[%metadata (metadata-to-json metadata)]
|
||||
==
|
||||
::
|
||||
++ json-to-action
|
||||
|= jon=json
|
||||
^- metadata-action
|
||||
=, dejs:format
|
||||
=< (parse-json jon)
|
||||
|%
|
||||
++ parse-json
|
||||
%- of
|
||||
:~ [%add add]
|
||||
[%remove remove]
|
||||
==
|
||||
::
|
||||
++ add
|
||||
%- ot
|
||||
:~ [%group-path pa]
|
||||
[%resource resource]
|
||||
[%metadata metadata]
|
||||
==
|
||||
++ remove
|
||||
%- ot
|
||||
:~ [%group-path pa]
|
||||
[%resource resource]
|
||||
==
|
||||
::
|
||||
++ nu
|
||||
|= jon=json
|
||||
?> ?=({$s *} jon)
|
||||
(rash p.jon hex)
|
||||
::
|
||||
++ metadata
|
||||
%- ot
|
||||
:~ [%title so]
|
||||
[%description so]
|
||||
[%color nu]
|
||||
[%date-created (se %da)]
|
||||
[%creator (su ;~(pfix sig fed:ag))]
|
||||
==
|
||||
++ resource
|
||||
%- ot
|
||||
:~ [%app-name so]
|
||||
[%app-path pa]
|
||||
==
|
||||
--
|
||||
::
|
||||
++ metadata-to-json
|
||||
|= met=metadata
|
||||
^- json
|
||||
|
11
pkg/arvo/mar/metadata/action.hoon
Normal file
11
pkg/arvo/mar/metadata/action.hoon
Normal file
@ -0,0 +1,11 @@
|
||||
/+ *metadata-json
|
||||
=, dejs:format
|
||||
|_ act=metadata-action
|
||||
++ grab
|
||||
|%
|
||||
++ noun metadata-action
|
||||
++ json
|
||||
|= jon=^json
|
||||
(json-to-action jon)
|
||||
--
|
||||
--
|
@ -429,9 +429,6 @@
|
||||
.tr {
|
||||
text-align: right;
|
||||
}
|
||||
.pt1 {
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
.pb2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
@ -490,7 +487,7 @@
|
||||
;p:"Urbit ID"
|
||||
;input(value "{(scow %p our)}", disabled "true", class "mono");
|
||||
;p:"Access Key"
|
||||
;p.f9.gray2.pt1
|
||||
;p.f9.gray2
|
||||
; Get key from Bridge, or
|
||||
;span.mono.pr1:"+code"
|
||||
; in dojo
|
||||
|
@ -181,6 +181,30 @@ class UrbitApi {
|
||||
});
|
||||
}
|
||||
|
||||
metadataAction(data) {
|
||||
return this.action("metadata-hook", "metadata-action", data);
|
||||
}
|
||||
|
||||
metadataAdd(appPath, groupPath, title, description, dateCreated, color) {
|
||||
let creator = `~${window.ship}`
|
||||
return this.metadataAction({
|
||||
add: {
|
||||
"group-path": groupPath,
|
||||
resource: {
|
||||
"app-path": appPath,
|
||||
"app-name": "chat"
|
||||
},
|
||||
metadata: {
|
||||
title,
|
||||
description,
|
||||
color,
|
||||
'date-created': dateCreated,
|
||||
creator
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
let sidebarBoolean = true;
|
||||
if (store.state.sidebarShown === true) {
|
||||
|
@ -125,15 +125,15 @@ export class Message extends Component {
|
||||
);
|
||||
} else {
|
||||
let chatroom = letter.text.match(
|
||||
/(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z])+([/-])?)+/
|
||||
/([~][/])?(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z])+([/-])?)+/
|
||||
);
|
||||
if ((chatroom !== null) // matched possible chatroom
|
||||
&& (chatroom[1].length > 2) // possible ship?
|
||||
&& (urbitOb.isValidPatp(chatroom[1]) // valid patp?
|
||||
&& (chatroom[2].length > 2) // possible ship?
|
||||
&& (urbitOb.isValidPatp(chatroom[2]) // valid patp?
|
||||
&& (chatroom[0] === letter.text))) { // entire message is room name?
|
||||
return (
|
||||
<Link
|
||||
className="bb b--black f7 mono lh-copy v-top"
|
||||
className="bb b--black b--white-d f7 mono lh-copy v-top"
|
||||
to={"/~chat/join/" + chatroom.input}>
|
||||
{letter.text}
|
||||
</Link>
|
||||
|
@ -45,12 +45,13 @@ export class Root extends Component {
|
||||
state.invites['/chat'] : {};
|
||||
|
||||
let contacts = !!state.contacts ? state.contacts : {};
|
||||
let associations = !!state.associations ? state.associations : new Map;
|
||||
|
||||
const renderChannelSidebar = (props, station) => (
|
||||
<Sidebar
|
||||
inbox={state.inbox}
|
||||
messagePreviews={messagePreviews}
|
||||
associations={state.associations || new Map}
|
||||
associations={associations}
|
||||
contacts={contacts}
|
||||
invites={invites}
|
||||
unreads={unreads}
|
||||
@ -152,8 +153,14 @@ export class Root extends Component {
|
||||
envelopes: []
|
||||
};
|
||||
|
||||
let roomContacts = (station in contacts)
|
||||
? contacts[station] : {};
|
||||
let roomContacts = {};
|
||||
let associatedGroup = ((associations.has(station)) &&
|
||||
(associations.get(station)["group-path"]))
|
||||
? associations.get(station)["group-path"] : "";
|
||||
|
||||
if ((associations.has(station)) && (associatedGroup in contacts)) {
|
||||
roomContacts = contacts[associatedGroup]
|
||||
}
|
||||
|
||||
let group = state.groups[station] || new Set([]);
|
||||
let popout = props.match.url.includes("/popout/");
|
||||
@ -210,7 +217,7 @@ export class Root extends Component {
|
||||
spinner={state.spinner}
|
||||
sidebarShown={state.sidebarShown}
|
||||
popout={popout}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
>
|
||||
<MemberScreen
|
||||
{...props}
|
||||
@ -240,17 +247,21 @@ export class Root extends Component {
|
||||
|
||||
let popout = props.match.url.includes("/popout/");
|
||||
|
||||
let association = (associations.has(station))
|
||||
? associations.get(station) : {};
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
sidebarHideOnMobile={true}
|
||||
spinner={state.spinner}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
>
|
||||
<SettingsScreen
|
||||
{...props}
|
||||
station={station}
|
||||
association={association}
|
||||
api={api}
|
||||
station={station}
|
||||
group={group}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { deSig } from '/lib/util';
|
||||
import { deSig, uxToHex } from '/lib/util';
|
||||
import { Route, Link } from "react-router-dom";
|
||||
import { store } from "/store";
|
||||
|
||||
|
||||
import { ChatTabBar } from '/components/lib/chat-tabbar';
|
||||
@ -14,10 +13,26 @@ export class SettingsScreen extends Component {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isLoading: false
|
||||
isLoading: false,
|
||||
title: "",
|
||||
description: "",
|
||||
color: ""
|
||||
};
|
||||
|
||||
this.renderDelete = this.renderDelete.bind(this);
|
||||
this.changeTitle = this.changeTitle.bind(this);
|
||||
this.changeDescription = this.changeDescription.bind(this);
|
||||
this.changeColor = this.changeColor.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if ((this.props.association) && (this.props.association.metadata)) {
|
||||
this.setState({
|
||||
title: this.props.association.metadata.title,
|
||||
description: this.props.association.metadata.description,
|
||||
color: uxToHex(this.props.association.metadata.color)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
@ -30,6 +45,26 @@ export class SettingsScreen extends Component {
|
||||
props.history.push('/~chat');
|
||||
});
|
||||
}
|
||||
|
||||
if ((this.state.title === "") && (prevProps !== this.props)) {
|
||||
if ((props.association) && (props.association.metadata))
|
||||
this.setState({
|
||||
title: props.association.metadata.title,
|
||||
description: props.association.metadata.description,
|
||||
color: uxToHex(props.association.metadata.color)});
|
||||
}
|
||||
}
|
||||
|
||||
changeTitle() {
|
||||
this.setState({title: event.target.value})
|
||||
}
|
||||
|
||||
changeDescription() {
|
||||
this.setState({description: event.target.value});
|
||||
}
|
||||
|
||||
changeColor() {
|
||||
this.setState({color: event.target.value});
|
||||
}
|
||||
|
||||
deleteChat() {
|
||||
@ -61,7 +96,7 @@ export class SettingsScreen extends Component {
|
||||
</div>
|
||||
<div className={"w-100 fl mt3 " + ((!chatOwner) ? 'o-30' : '')}>
|
||||
<p className="f8 mt3 lh-copy db">Delete Chat</p>
|
||||
<p className="f9 gray2 db mb4">Permenantly delete this chat. (All current members will no longer see this chat)</p>
|
||||
<p className="f9 gray2 db mb4">Permanently delete this chat. All current members will no longer see this chat.</p>
|
||||
<a onClick={(chatOwner) ? this.deleteChat.bind(this) : null}
|
||||
className={"dib f9 ba pa2 " + deleteButtonClasses}>Delete this chat</a>
|
||||
</div>
|
||||
@ -69,6 +104,124 @@ export class SettingsScreen extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderMetadataSettings() {
|
||||
const { props, state } = this;
|
||||
|
||||
let chatOwner = (deSig(props.match.params.ship) === window.ship);
|
||||
|
||||
let association = ((props.association) && (props.association.metadata))
|
||||
? props.association : {};
|
||||
|
||||
return(
|
||||
<div>
|
||||
<div className={"w-100 pb6 fl mt3 " + ((chatOwner) ? '' : 'o-30')}>
|
||||
<p className="f8 mt3 lh-copy">Rename</p>
|
||||
<p className="f9 gray2 db mb4">Change the name of this chat</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{maxWidth: "29rem"}}>
|
||||
<input
|
||||
className={"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
|
||||
"focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
|
||||
value={this.state.title}
|
||||
disabled={!chatOwner}
|
||||
onChange={this.changeTitle}
|
||||
/>
|
||||
<span className={"f8 absolute pa3 inter " +
|
||||
((chatOwner) ? "pointer" : "")}
|
||||
style={{ right: 12, top: 1 }}
|
||||
ref="rename"
|
||||
onClick={() => {
|
||||
if (chatOwner) {
|
||||
props.api.setSpinner(true);
|
||||
props.api.metadataAdd(
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
this.state.title,
|
||||
association.metadata.description,
|
||||
association.metadata['date-created'],
|
||||
uxToHex(association.metadata.color)
|
||||
).then(() => {
|
||||
this.refs.rename.innerText = "Saved";
|
||||
props.api.setSpinner(false);
|
||||
})
|
||||
}
|
||||
}}>
|
||||
Save
|
||||
</span>
|
||||
</div>
|
||||
<p className="f8 mt3 lh-copy">Change description</p>
|
||||
<p className="f9 gray2 db mb4">Change the description of this chat</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{ maxWidth: "29rem" }}>
|
||||
<input
|
||||
className={"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
|
||||
"focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
|
||||
value={this.state.description}
|
||||
disabled={!chatOwner}
|
||||
onChange={this.changeDescription}
|
||||
/>
|
||||
<span className={"f8 absolute pa3 inter " +
|
||||
((chatOwner) ? "pointer" : "")}
|
||||
style={{ right: 12, top: 1 }}
|
||||
ref="description"
|
||||
onClick={() => {
|
||||
if (chatOwner) {
|
||||
props.api.setSpinner(true);
|
||||
props.api.metadataAdd(
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
this.state.description,
|
||||
association.metadata['date-created'],
|
||||
uxToHex(association.metadata.color)
|
||||
).then(() => {
|
||||
this.refs.description.innerText = "Saved";
|
||||
props.api.setSpinner(false);
|
||||
})
|
||||
}
|
||||
}}>
|
||||
Save
|
||||
</span>
|
||||
</div>
|
||||
<p className="f8 mt3 lh-copy">Change color</p>
|
||||
<p className="f9 gray2 db mb4">Give this chat a color when viewing group channels</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{ maxWidth: "20rem" }}>
|
||||
<input
|
||||
className={"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
|
||||
"focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
|
||||
value={this.state.color}
|
||||
disabled={!chatOwner}
|
||||
onChange={this.changeColor}
|
||||
/>
|
||||
<span className={"f8 absolute pa3 inter " +
|
||||
((chatOwner) ? "pointer" : "")}
|
||||
style={{ right: 12, top: 1 }}
|
||||
ref="color"
|
||||
onClick={() => {
|
||||
if ((chatOwner) && (this.state.color.match(/[0-9A-F]{6}/i))) {
|
||||
props.api.setSpinner(true);
|
||||
props.api.metadataAdd(
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
association.metadata.description,
|
||||
association.metadata['date-created'],
|
||||
this.state.color
|
||||
).then(() => {
|
||||
this.refs.color.innerText = "Saved";
|
||||
props.api.setSpinner(false);
|
||||
})
|
||||
}
|
||||
}}>
|
||||
Save
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
const isinPopout = this.props.popout ? "popout/" : "";
|
||||
@ -148,7 +301,29 @@ export class SettingsScreen extends Component {
|
||||
</div>
|
||||
<div className="w-100 pl3 mt4 cf">
|
||||
<h2 className="f8 pb2">Chat Settings</h2>
|
||||
<div className="w-100 mt3">
|
||||
<p className="f8 mt3 lh-copy">Share</p>
|
||||
<p className="f9 gray2 mb4">Share a shortcode to join this chat</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{ maxWidth: "29rem" }}>
|
||||
<input
|
||||
className="f8 mono ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 db w-100 flex-auto mr3"
|
||||
disabled={true}
|
||||
value={props.station.substr(1)}
|
||||
/>
|
||||
<span className="f8 pointer absolute pa3 inter"
|
||||
style={{right: 12, top: 1}}
|
||||
ref="copy"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(props.station.substr(1));
|
||||
this.refs.copy.innerText = "Copied";
|
||||
}}>
|
||||
Copy
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{this.renderDelete()}
|
||||
{this.renderMetadataSettings()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ export class MetadataReducer {
|
||||
if (data) {
|
||||
this.associations(data, state);
|
||||
this.add(data, state);
|
||||
this.update(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,4 +30,13 @@ export class MetadataReducer {
|
||||
state.associations = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
update(json, state) {
|
||||
let data = _.get(json, 'update-metadata', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
metadata.set(data["app-path"], data);
|
||||
state.associations = metadata;
|
||||
}
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ export class Root extends Component {
|
||||
contacts={contacts}
|
||||
groups={groups}
|
||||
invites={invites}>
|
||||
<div className="h-100 w-100 overflow-x-hidden bg-white dn db-ns">
|
||||
<div className="h-100 w-100 overflow-x-hidden bg-white bg-gray0-d dn db-ns">
|
||||
<div className="pl3 pr3 pt2 dt pb3 w-100 h-100">
|
||||
<p className="f9 pt3 gray2 w-100 h-100 dtc v-mid tc">
|
||||
Select a group to begin.
|
||||
|
Loading…
Reference in New Issue
Block a user