Add basic github support to /connect
This commit is contained in:
parent
da585b5f83
commit
914102f19f
12 changed files with 194 additions and 7 deletions
|
|
@ -300,6 +300,29 @@
|
|||
top:-4px;
|
||||
}
|
||||
|
||||
.activecontent__githubrepo{
|
||||
}
|
||||
|
||||
.activecontent__githubrepoheader{
|
||||
padding-left: 30px;
|
||||
padding-bottom: 3px;
|
||||
min-height: 30px;
|
||||
margin-top:-2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.activecontent__githubrepofiles{
|
||||
border-bottom: 1px solid $light-medium-gray;
|
||||
font-size:0.8em;
|
||||
}
|
||||
|
||||
.activecontent__githubrepofilerow{
|
||||
padding: 4px 2px;
|
||||
border-top: 1px solid $light-medium-gray;
|
||||
}
|
||||
|
||||
.chatchannels {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ class ChatChannelsController < ApplicationController
|
|||
@active_channel = ChatChannel.find_by_slug(slug)
|
||||
@active_channel.current_user = current_user if @active_channel
|
||||
end
|
||||
generate_github_token
|
||||
generate_algolia_search_key
|
||||
end
|
||||
|
||||
|
|
@ -109,4 +110,8 @@ class ChatChannelsController < ApplicationController
|
|||
ENV["ALGOLIASEARCH_SEARCH_ONLY_KEY"], params,
|
||||
)
|
||||
end
|
||||
|
||||
def generate_github_token
|
||||
@github_token = Identity.where(user_id: current_user.id, provider: "github").first&.token
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -129,4 +129,15 @@ export function getContent(url, successCb, failureCb) {
|
|||
.then(response => response.json())
|
||||
.then(successCb)
|
||||
.catch(failureCb);
|
||||
}
|
||||
|
||||
export function getJSONContents(url, successCb, failureCb) {
|
||||
fetch(url, {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(successCb)
|
||||
.catch(failureCb);
|
||||
}
|
||||
|
|
@ -300,6 +300,11 @@ export default class Chat extends Component {
|
|||
} else {
|
||||
alert("Calls are only currently available in direct channels");
|
||||
}
|
||||
} else if (message.startsWith('/github')) {
|
||||
const args = message.split('/github ')[1].trim()
|
||||
let newActiveContent = this.state.activeContent
|
||||
newActiveContent[this.state.activeChannelId] = {type_of: "github", args: args }
|
||||
this.setState({activeContent: newActiveContent})
|
||||
} else if (message[0] === '/') {
|
||||
conductModeration(
|
||||
this.state.activeChannelId,
|
||||
|
|
@ -545,6 +550,7 @@ export default class Chat extends Component {
|
|||
onExit={this.triggerActiveContent}
|
||||
activeChannelId={this.state.activeChannelId}
|
||||
pusherKey={this.props.pusherKey}
|
||||
githubToken={this.props.githubToken}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { h, Component } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
import CodeEditor from './codeEditor';
|
||||
import GithubRepo from './githubRepo';
|
||||
|
||||
export default class Content extends Component {
|
||||
static propTypes = {
|
||||
|
|
@ -72,6 +73,13 @@ function display(props) {
|
|||
<div dangerouslySetInnerHTML={{__html: props.resource.body_html}} ></div>
|
||||
</div>
|
||||
</div>)
|
||||
} else if (props.resource.type_of === "github") {
|
||||
return <GithubRepo
|
||||
activeChannelId={props.activeChannelId}
|
||||
pusherKey={props.pusherKey}
|
||||
githubToken={props.githubToken}
|
||||
resource={props.resource}
|
||||
/>
|
||||
} else if (props.resource.type_of === "code_editor") {
|
||||
return <CodeEditor
|
||||
activeChannelId={props.activeChannelId}
|
||||
|
|
|
|||
128
app/javascript/chat/githubRepo.jsx
Normal file
128
app/javascript/chat/githubRepo.jsx
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import { h, Component } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getJSONContents } from './actions';
|
||||
import marked from 'marked';
|
||||
|
||||
export default class GithubRepo extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
root: true,
|
||||
directories: [],
|
||||
files: [],
|
||||
readme: null,
|
||||
content: null,
|
||||
token: props.githubToken,
|
||||
path: null
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
if (this.state.token) {
|
||||
getJSONContents(`https://api.github.com/repos/${this.props.resource.args}/contents?access_token=`+this.state.token,
|
||||
this.loadContent,
|
||||
this.loadFailure)
|
||||
getJSONContents(`https://api.github.com/repos/${this.props.resource.args}/readme?access_token=`+this.state.token,
|
||||
this.loadContent,
|
||||
this.loadFailure)
|
||||
}
|
||||
this.setState({path: this.props.resource.args})
|
||||
}
|
||||
|
||||
handleItemClick = e => {
|
||||
e.preventDefault();
|
||||
getJSONContents(`${e.target.dataset.apiUrl}&access_token=`+this.state.token,
|
||||
this.loadContent,
|
||||
this.loadFailure)
|
||||
this.setState({
|
||||
root: false,
|
||||
path: e.target.dataset.path
|
||||
})
|
||||
}
|
||||
|
||||
loadContent = response => {
|
||||
let files = [];
|
||||
let directories = [];
|
||||
console.log(response)
|
||||
if (response.message === 'Not Found') {
|
||||
this.setState({path: 'Repo not found (misspelled or private?)'})
|
||||
} else if (Array.isArray(response)) {
|
||||
response.forEach( item => {
|
||||
if (item.type === 'file') {
|
||||
files.push(item)
|
||||
} else {
|
||||
directories.push(item)
|
||||
}
|
||||
})
|
||||
this.setState({files: files,
|
||||
directories: directories,
|
||||
response: response})
|
||||
} else if (response.path === "README.md") {
|
||||
this.setState({
|
||||
readme: window.atob(response.content)
|
||||
})
|
||||
} else if (response.content) {
|
||||
this.setState({
|
||||
content: window.atob(response.content),
|
||||
response: response
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
loadFailure = response => {
|
||||
this.setState({path: response.message})
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.token || this.state.token.length === 0) {
|
||||
return <div className='activecontent__githubrepo'>
|
||||
<div className='activecontent__githubrepoheader'><em>Authentication required</em></div>
|
||||
<p>
|
||||
You must <a href='/users/auth/github' data-no-instant>authenticate with GitHub</a> to use this feature.
|
||||
</p>
|
||||
</div>
|
||||
} else if (this.state.content) {
|
||||
return <div className='activecontent__githubrepo'>
|
||||
<div className='activecontent__githubrepoheader'>{this.state.path}</div>
|
||||
<pre>
|
||||
{this.state.content}
|
||||
</pre>
|
||||
</div>
|
||||
} else {
|
||||
const directories = this.state.directories.map( item => {
|
||||
return <div className='activecontent__githubrepofilerow'>
|
||||
<a href={item.html_url} data-api-url={item.url} data-path={item.path} onClick={this.handleItemClick}>📁 {item.name}</a>
|
||||
</div>
|
||||
});
|
||||
const files = this.state.files.map( item => {
|
||||
return <div className='activecontent__githubrepofilerow'>
|
||||
<a href={item.html_url} data-api-url={item.url} data-path={item.path} onClick={this.handleItemClick}>{item.name}</a>
|
||||
</div>
|
||||
});
|
||||
let readme = ''
|
||||
if (this.state.readme) {
|
||||
readme = <div dangerouslySetInnerHTML={{__html: marked(this.state.readme)}} ></div>
|
||||
}
|
||||
if (this.state.root) {
|
||||
return <div className='activecontent__githubrepo'>
|
||||
<div className='activecontent__githubrepoheader'><a href="/Users/benhalpern/dev/dev.to_core/app"></a>{this.state.path}</div>
|
||||
<div className='activecontent__githubrepofiles'>
|
||||
{directories}
|
||||
{files}
|
||||
</div>
|
||||
{readme}
|
||||
</div>
|
||||
} else {
|
||||
return <div className='activecontent__githubrepo'>
|
||||
<div className='activecontent__githubrepoheader'>{this.state.path}</div>
|
||||
<div className='activecontent__githubrepofiles'>
|
||||
{directories}
|
||||
{files}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ HTMLDocument.prototype.ready = new Promise(resolve => {
|
|||
document.ready.then(
|
||||
getUserDataAndCsrfToken().then(currentUser => {
|
||||
if (document.getElementById('chat')) {
|
||||
const { chatChannels, pusherKey, chatOptions, algoliaKey, algoliaId, algoliaIndex, twilioToken } = document.getElementById(
|
||||
const { chatChannels, pusherKey, chatOptions, algoliaKey, algoliaId, algoliaIndex, githubToken } = document.getElementById(
|
||||
'chat',
|
||||
).dataset;
|
||||
window.currentUser = currentUser;
|
||||
|
|
@ -30,6 +30,7 @@ document.ready.then(
|
|||
algoliaId={algoliaId}
|
||||
algoliaKey={algoliaKey}
|
||||
algoliaIndex={algoliaIndex}
|
||||
githubToken={githubToken}
|
||||
/>,
|
||||
renderTarget, renderTarget.firstChild
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
data-algolia-id="<%= ENV["ALGOLIASEARCH_APPLICATION_ID"] %>"
|
||||
data-algolia-key="<%= @secured_algolia_key %>"
|
||||
data-algolia-index="<%= "SecuredChatChannel_#{Rails.env}" %>"
|
||||
data-github-token="<%= @github_token %>"
|
||||
data-chat-channels="<%= [] %>"
|
||||
data-chat-options="<%= {showChannelsList:true, showTimestamp: true, activeChannelId: @active_channel&.id }.to_json %>"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
key: "video-upload__#{SecureRandom.hex}",
|
||||
key_starts_with: "video-upload__",
|
||||
acl: "public-read",
|
||||
max_file_size: 2500.megabytes,
|
||||
max_file_size: (current_user.has_role?(:super_admin) ? 10000 : 3500).megabytes,
|
||||
id: "s3-uploader",
|
||||
class: "upload-form",
|
||||
data: {:key => :val} do %>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ end
|
|||
|
||||
p "2/7 Creating users"
|
||||
roles = %i(level_1_member level_2_member level_3_member level_4_member workshop_pass)
|
||||
80.times do |i|
|
||||
40.times do |i|
|
||||
begin
|
||||
identity_attributes = { provider: "twitter",
|
||||
uid: "#{i}",
|
||||
|
|
@ -123,7 +123,7 @@ Tag.create!(
|
|||
)
|
||||
|
||||
p "4/7 Creating articles"
|
||||
150.times do
|
||||
80.times do
|
||||
# begin
|
||||
four_tags_string = "discuss, meta, git, changelog"
|
||||
valid_markdown = "---\ntitle: #{Faker::Book.title} #{rand(5000)}\npublished: true\ncover_image: #{Faker::Avatar.image}\ntags: #{four_tags_string}\n---\n#{Faker::Hipster.paragraph(4)}"
|
||||
|
|
@ -142,11 +142,11 @@ end
|
|||
# Create comments
|
||||
|
||||
p "5/7 Creating comments"
|
||||
200.times do
|
||||
120.times do
|
||||
attributes = {
|
||||
body_markdown: Faker::Hipster.paragraph(1),
|
||||
user_id: User.order("RANDOM()").first.id,
|
||||
commentable_id: Article.order("RANDOM()").first.id,
|
||||
commentable_id: Article.where("id > ?", 30).order("RANDOM()").first.id,
|
||||
commentable_type: "Article",
|
||||
}
|
||||
Comment.create!(attributes)
|
||||
|
|
@ -164,7 +164,6 @@ podcast_objects = [
|
|||
|
||||
podcast_objects.each do |attributes|
|
||||
podcast = Podcast.create!(attributes)
|
||||
PodcastFeed.new.get_episodes(podcast, 4)
|
||||
end
|
||||
|
||||
p "7/7 Creating Broadcasts"
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@
|
|||
"@webscopeio/react-textarea-autocomplete": "^2.2.0",
|
||||
"codemirror": "^5.38.0",
|
||||
"intersection-observer": "^0.5.0",
|
||||
"marked": "^0.4.0",
|
||||
"preact": "^8.2.5",
|
||||
"prop-types": "^15.6.0",
|
||||
"pusher-js": "^4.2.2",
|
||||
|
|
|
|||
|
|
@ -6134,6 +6134,10 @@ marked@^0.3.9:
|
|||
version "0.3.18"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.18.tgz#3ef058cd926101849b92a7a7c15db18c7fc76b2f"
|
||||
|
||||
marked@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.4.0.tgz#9ad2c2a7a1791f10a852e0112f77b571dce10c66"
|
||||
|
||||
math-expression-evaluator@^1.2.14:
|
||||
version "1.2.17"
|
||||
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue