Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
李少辉-开发者
gitlab-foss
提交
1c783007
G
gitlab-foss
项目概览
李少辉-开发者
/
gitlab-foss
通知
15
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
gitlab-foss
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
1c783007
编写于
4月 06, 2017
作者:
R
Regis Boudinot
提交者:
Jacob Schatz
4月 06, 2017
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Issue title realtime
上级
4e3de96e
变更
18
隐藏空白更改
内联
并排
Showing
18 changed file
with
211 addition
and
42 deletion
+211
-42
app/assets/javascripts/issue_show/index.js
app/assets/javascripts/issue_show/index.js
+26
-0
app/assets/javascripts/issue_show/issue_title.js
app/assets/javascripts/issue_show/issue_title.js
+78
-0
app/assets/javascripts/issue_show/services/index.js
app/assets/javascripts/issue_show/services/index.js
+10
-0
app/assets/javascripts/vue_pipelines_index/stores/pipelines_store.js
...javascripts/vue_pipelines_index/stores/pipelines_store.js
+2
-2
app/assets/javascripts/vue_realtime_listener/index.js
app/assets/javascripts/vue_realtime_listener/index.js
+9
-29
app/controllers/projects/issues_controller.rb
app/controllers/projects/issues_controller.rb
+7
-2
app/models/issue.rb
app/models/issue.rb
+11
-0
app/views/projects/issues/show.html.haml
app/views/projects/issues/show.html.haml
+6
-3
config/routes/project.rb
config/routes/project.rb
+1
-0
config/webpack.config.js
config/webpack.config.js
+1
-0
lib/gitlab/etag_caching/middleware.rb
lib/gitlab/etag_caching/middleware.rb
+2
-1
spec/features/gitlab_flavored_markdown_spec.rb
spec/features/gitlab_flavored_markdown_spec.rb
+11
-1
spec/features/issues/award_emoji_spec.rb
spec/features/issues/award_emoji_spec.rb
+4
-1
spec/features/issues/move_spec.rb
spec/features/issues/move_spec.rb
+2
-2
spec/features/issues/spam_issues_spec.rb
spec/features/issues/spam_issues_spec.rb
+1
-1
spec/features/issues_spec.rb
spec/features/issues_spec.rb
+17
-0
spec/javascripts/issue_show/issue_title_spec.js
spec/javascripts/issue_show/issue_title_spec.js
+22
-0
spec/javascripts/test_bundle.js
spec/javascripts/test_bundle.js
+1
-0
未找到文件。
app/assets/javascripts/issue_show/index.js
0 → 100644
浏览文件 @
1c783007
import
Vue
from
'
vue
'
;
import
IssueTitle
from
'
./issue_title
'
;
import
'
../vue_shared/vue_resource_interceptor
'
;
const
vueOptions
=
()
=>
({
el
:
'
.issue-title-entrypoint
'
,
components
:
{
IssueTitle
,
},
data
()
{
const
issueTitleData
=
document
.
querySelector
(
'
.issue-title-data
'
).
dataset
;
return
{
initialTitle
:
issueTitleData
.
initialTitle
,
endpoint
:
issueTitleData
.
endpoint
,
};
},
template
:
`
<IssueTitle
:initialTitle="initialTitle"
:endpoint="endpoint"
/>
`
,
});
(()
=>
new
Vue
(
vueOptions
()))();
app/assets/javascripts/issue_show/issue_title.js
0 → 100644
浏览文件 @
1c783007
import
Visibility
from
'
visibilityjs
'
;
import
Poll
from
'
./../lib/utils/poll
'
;
import
Service
from
'
./services/index
'
;
export
default
{
props
:
{
initialTitle
:
{
required
:
true
,
type
:
String
},
endpoint
:
{
required
:
true
,
type
:
String
},
},
data
()
{
const
resource
=
new
Service
(
this
.
$http
,
this
.
endpoint
);
const
poll
=
new
Poll
({
resource
,
method
:
'
getTitle
'
,
successCallback
:
(
res
)
=>
{
this
.
renderResponse
(
res
);
},
errorCallback
:
(
err
)
=>
{
if
(
process
.
env
.
NODE_ENV
!==
'
production
'
)
{
// eslint-disable-next-line no-console
console
.
error
(
'
ISSUE SHOW TITLE REALTIME ERROR
'
,
err
);
}
else
{
throw
new
Error
(
err
);
}
},
});
return
{
poll
,
timeoutId
:
null
,
title
:
this
.
initialTitle
,
};
},
methods
:
{
fetch
()
{
this
.
poll
.
makeRequest
();
Visibility
.
change
(()
=>
{
if
(
!
Visibility
.
hidden
())
{
this
.
poll
.
restart
();
}
else
{
this
.
poll
.
stop
();
}
});
},
renderResponse
(
res
)
{
const
body
=
JSON
.
parse
(
res
.
body
);
this
.
triggerAnimation
(
body
);
},
triggerAnimation
(
body
)
{
const
{
title
}
=
body
;
/**
* since opacity is changed, even if there is no diff for Vue to update
* we must check the title even on a 304 to ensure no visual change
*/
if
(
this
.
title
===
title
)
return
;
this
.
$el
.
style
.
opacity
=
0
;
this
.
timeoutId
=
setTimeout
(()
=>
{
this
.
title
=
title
;
this
.
$el
.
style
.
transition
=
'
opacity 0.2s ease
'
;
this
.
$el
.
style
.
opacity
=
1
;
clearTimeout
(
this
.
timeoutId
);
},
100
);
},
},
created
()
{
this
.
fetch
();
},
template
:
`
<h2 class='title' v-html='title'></h2>
`
,
};
app/assets/javascripts/issue_show/services/index.js
0 → 100644
浏览文件 @
1c783007
export
default
class
Service
{
constructor
(
resource
,
endpoint
)
{
this
.
resource
=
resource
;
this
.
endpoint
=
endpoint
;
}
getTitle
()
{
return
this
.
resource
.
get
(
this
.
endpoint
);
}
}
app/assets/javascripts/vue_pipelines_index/stores/pipelines_store.js
浏览文件 @
1c783007
/* eslint-disable no-underscore-dangle*/
/* eslint-disable no-underscore-dangle*/
import
'
../../vue_realtime_listener
'
;
import
VueRealtimeListener
from
'
../../vue_realtime_listener
'
;
export
default
class
PipelinesStore
{
export
default
class
PipelinesStore
{
constructor
()
{
constructor
()
{
...
@@ -56,6 +56,6 @@ export default class PipelinesStore {
...
@@ -56,6 +56,6 @@ export default class PipelinesStore {
const
removeIntervals
=
()
=>
clearInterval
(
this
.
timeLoopInterval
);
const
removeIntervals
=
()
=>
clearInterval
(
this
.
timeLoopInterval
);
const
startIntervals
=
()
=>
startTimeLoops
();
const
startIntervals
=
()
=>
startTimeLoops
();
gl
.
VueRealtimeListener
(
removeIntervals
,
startIntervals
);
VueRealtimeListener
(
removeIntervals
,
startIntervals
);
}
}
}
}
app/assets/javascripts/vue_realtime_listener/index.js
浏览文件 @
1c783007
/* eslint-disable no-param-reassign */
export
default
(
removeIntervals
,
startIntervals
)
=>
{
window
.
removeEventListener
(
'
focus
'
,
startIntervals
);
((
gl
)
=>
{
window
.
removeEventListener
(
'
blur
'
,
removeIntervals
);
gl
.
VueRealtimeListener
=
(
removeIntervals
,
startIntervals
)
=>
{
window
.
removeEventListener
(
'
onbeforeload
'
,
removeIntervals
);
const
removeAll
=
()
=>
{
removeIntervals
();
window
.
addEventListener
(
'
focus
'
,
startIntervals
);
window
.
removeEventListener
(
'
beforeunload
'
,
removeIntervals
);
window
.
addEventListener
(
'
blur
'
,
removeIntervals
);
window
.
removeEventListener
(
'
focus
'
,
startIntervals
);
window
.
addEventListener
(
'
onbeforeload
'
,
removeIntervals
);
window
.
removeEventListener
(
'
blur
'
,
removeIntervals
);
};
document
.
removeEventListener
(
'
beforeunload
'
,
removeAll
);
};
window
.
addEventListener
(
'
beforeunload
'
,
removeIntervals
);
window
.
addEventListener
(
'
focus
'
,
startIntervals
);
window
.
addEventListener
(
'
blur
'
,
removeIntervals
);
document
.
addEventListener
(
'
beforeunload
'
,
removeAll
);
// add removeAll methods to stack
const
stack
=
gl
.
VueRealtimeListener
.
reset
;
gl
.
VueRealtimeListener
.
reset
=
()
=>
{
gl
.
VueRealtimeListener
.
reset
=
stack
;
removeAll
();
stack
();
};
};
// remove all event listeners and intervals
gl
.
VueRealtimeListener
.
reset
=
()
=>
undefined
;
// noop
})(
window
.
gl
||
(
window
.
gl
=
{}));
app/controllers/projects/issues_controller.rb
浏览文件 @
1c783007
...
@@ -11,10 +11,10 @@ class Projects::IssuesController < Projects::ApplicationController
...
@@ -11,10 +11,10 @@ class Projects::IssuesController < Projects::ApplicationController
before_action
:redirect_to_external_issue_tracker
,
only:
[
:index
,
:new
]
before_action
:redirect_to_external_issue_tracker
,
only:
[
:index
,
:new
]
before_action
:module_enabled
before_action
:module_enabled
before_action
:issue
,
only:
[
:edit
,
:update
,
:show
,
:referenced_merge_requests
,
before_action
:issue
,
only:
[
:edit
,
:update
,
:show
,
:referenced_merge_requests
,
:related_branches
,
:can_create_branch
]
:related_branches
,
:can_create_branch
,
:rendered_title
]
# Allow read any issue
# Allow read any issue
before_action
:authorize_read_issue!
,
only:
[
:show
]
before_action
:authorize_read_issue!
,
only:
[
:show
,
:rendered_title
]
# Allow write(create) issue
# Allow write(create) issue
before_action
:authorize_create_issue!
,
only:
[
:new
,
:create
]
before_action
:authorize_create_issue!
,
only:
[
:new
,
:create
]
...
@@ -200,6 +200,11 @@ class Projects::IssuesController < Projects::ApplicationController
...
@@ -200,6 +200,11 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
end
end
def
rendered_title
Gitlab
::
PollingInterval
.
set_header
(
response
,
interval:
3_000
)
render
json:
{
title:
view_context
.
markdown_field
(
@issue
,
:title
)
}
end
protected
protected
def
issue
def
issue
...
...
app/models/issue.rb
浏览文件 @
1c783007
...
@@ -40,6 +40,8 @@ class Issue < ActiveRecord::Base
...
@@ -40,6 +40,8 @@ class Issue < ActiveRecord::Base
scope
:include_associations
,
->
{
includes
(
:assignee
,
:labels
,
project: :namespace
)
}
scope
:include_associations
,
->
{
includes
(
:assignee
,
:labels
,
project: :namespace
)
}
after_save
:expire_etag_cache
attr_spammable
:title
,
spam_title:
true
attr_spammable
:title
,
spam_title:
true
attr_spammable
:description
,
spam_description:
true
attr_spammable
:description
,
spam_description:
true
...
@@ -252,4 +254,13 @@ class Issue < ActiveRecord::Base
...
@@ -252,4 +254,13 @@ class Issue < ActiveRecord::Base
def
publicly_visible?
def
publicly_visible?
project
.
public?
&&
!
confidential?
project
.
public?
&&
!
confidential?
end
end
def
expire_etag_cache
key
=
Gitlab
::
Routing
.
url_helpers
.
rendered_title_namespace_project_issue_path
(
project
.
namespace
,
project
,
self
)
Gitlab
::
EtagCaching
::
Store
.
new
.
touch
(
key
)
end
end
end
app/views/projects/issues/show.html.haml
浏览文件 @
1c783007
...
@@ -49,11 +49,12 @@
...
@@ -49,11 +49,12 @@
=
link_to
'Submit as spam'
,
mark_as_spam_namespace_project_issue_path
(
@project
.
namespace
,
@project
,
@issue
),
method: :post
,
class:
'hidden-xs hidden-sm btn btn-grouped btn-spam'
,
title:
'Submit as spam'
=
link_to
'Submit as spam'
,
mark_as_spam_namespace_project_issue_path
(
@project
.
namespace
,
@project
,
@issue
),
method: :post
,
class:
'hidden-xs hidden-sm btn btn-grouped btn-spam'
,
title:
'Submit as spam'
=
link_to
'Edit'
,
edit_namespace_project_issue_path
(
@project
.
namespace
,
@project
,
@issue
),
class:
'hidden-xs hidden-sm btn btn-grouped issuable-edit'
=
link_to
'Edit'
,
edit_namespace_project_issue_path
(
@project
.
namespace
,
@project
,
@issue
),
class:
'hidden-xs hidden-sm btn btn-grouped issuable-edit'
.issue-details.issuable-details
.issue-details.issuable-details
.detail-page-description.content-block
{
class:
(
'hide-bottom-border'
unless
@issue
.
description
.
present?
)
}
.detail-page-description.content-block
{
class:
(
'hide-bottom-border'
unless
@issue
.
description
.
present?
)
}
%h2
.title
.issue-title-data.hidden
{
"data"
=>
{
"initial-title"
=>
markdown_field
(
@issue
,
:title
),
=
markdown_field
(
@issue
,
:title
)
"endpoint"
=>
rendered_title_namespace_project_issue_path
(
@project
.
namespace
,
@project
,
@issue
),
}
}
.issue-title-entrypoint
-
if
@issue
.
description
.
present?
-
if
@issue
.
description
.
present?
.description
{
class:
can?
(
current_user
,
:update_issue
,
@issue
)
?
'js-task-list-container'
:
''
}
.description
{
class:
can?
(
current_user
,
:update_issue
,
@issue
)
?
'js-task-list-container'
:
''
}
.wiki
.wiki
...
@@ -77,3 +78,5 @@
...
@@ -77,3 +78,5 @@
=
render
'projects/issues/discussion'
=
render
'projects/issues/discussion'
=
render
'shared/issuable/sidebar'
,
issuable:
@issue
=
render
'shared/issuable/sidebar'
,
issuable:
@issue
=
page_specific_javascript_bundle_tag
(
'issue_show'
)
config/routes/project.rb
浏览文件 @
1c783007
...
@@ -250,6 +250,7 @@ constraints(ProjectUrlConstrainer.new) do
...
@@ -250,6 +250,7 @@ constraints(ProjectUrlConstrainer.new) do
get
:referenced_merge_requests
get
:referenced_merge_requests
get
:related_branches
get
:related_branches
get
:can_create_branch
get
:can_create_branch
get
:rendered_title
end
end
collection
do
collection
do
post
:bulk_update
post
:bulk_update
...
...
config/webpack.config.js
浏览文件 @
1c783007
...
@@ -46,6 +46,7 @@ var config = {
...
@@ -46,6 +46,7 @@ var config = {
u2f
:
[
'
vendor/u2f
'
],
u2f
:
[
'
vendor/u2f
'
],
users
:
'
./users/users_bundle.js
'
,
users
:
'
./users/users_bundle.js
'
,
vue_pipelines
:
'
./vue_pipelines_index/index.js
'
,
vue_pipelines
:
'
./vue_pipelines_index/index.js
'
,
issue_show
:
'
./issue_show/index.js
'
,
},
},
output
:
{
output
:
{
...
...
lib/gitlab/etag_caching/middleware.rb
浏览文件 @
1c783007
...
@@ -3,7 +3,8 @@ module Gitlab
...
@@ -3,7 +3,8 @@ module Gitlab
class
Middleware
class
Middleware
RESERVED_WORDS
=
NamespaceValidator
::
WILDCARD_ROUTES
.
map
{
|
word
|
"/
#{
word
}
/"
}.
join
(
'|'
)
RESERVED_WORDS
=
NamespaceValidator
::
WILDCARD_ROUTES
.
map
{
|
word
|
"/
#{
word
}
/"
}.
join
(
'|'
)
ROUTE_REGEXP
=
Regexp
.
union
(
ROUTE_REGEXP
=
Regexp
.
union
(
%r(^(?!.*(
#{
RESERVED_WORDS
}
)).*/noteable/issue/
\d
+/notes
\z
)
%r(^(?!.*(
#{
RESERVED_WORDS
}
)).*/noteable/issue/
\d
+/notes
\z
)
,
%r(^(?!.*(
#{
RESERVED_WORDS
}
)).*/issues/
\d
+/rendered_title
\z
)
)
)
def
initialize
(
app
)
def
initialize
(
app
)
...
...
spec/features/gitlab_flavored_markdown_spec.rb
浏览文件 @
1c783007
...
@@ -48,7 +48,9 @@ describe "GitLab Flavored Markdown", feature: true do
...
@@ -48,7 +48,9 @@ describe "GitLab Flavored Markdown", feature: true do
end
end
end
end
describe
"for issues"
do
describe
"for issues"
,
feature:
true
,
js:
true
do
include
WaitForVueResource
before
do
before
do
@other_issue
=
create
(
:issue
,
@other_issue
=
create
(
:issue
,
author:
@user
,
author:
@user
,
...
@@ -79,6 +81,14 @@ describe "GitLab Flavored Markdown", feature: true do
...
@@ -79,6 +81,14 @@ describe "GitLab Flavored Markdown", feature: true do
expect
(
page
).
to
have_link
(
fred
.
to_reference
)
expect
(
page
).
to
have_link
(
fred
.
to_reference
)
end
end
it
"renders updated subject once edited somewhere else in issues#show"
do
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
@issue
)
@issue
.
update
(
title:
"fix
#{
@other_issue
.
to_reference
}
and update"
)
wait_for_vue_resource
expect
(
page
).
to
have_text
(
"fix
#{
@other_issue
.
to_reference
}
and update"
)
end
end
end
describe
"for merge requests"
do
describe
"for merge requests"
do
...
...
spec/features/issues/award_emoji_spec.rb
浏览文件 @
1c783007
...
@@ -2,6 +2,7 @@ require 'rails_helper'
...
@@ -2,6 +2,7 @@ require 'rails_helper'
describe
'Awards Emoji'
,
feature:
true
do
describe
'Awards Emoji'
,
feature:
true
do
include
WaitForAjax
include
WaitForAjax
include
WaitForVueResource
let!
(
:project
)
{
create
(
:project
,
:public
)
}
let!
(
:project
)
{
create
(
:project
,
:public
)
}
let!
(
:user
)
{
create
(
:user
)
}
let!
(
:user
)
{
create
(
:user
)
}
...
@@ -22,10 +23,11 @@ describe 'Awards Emoji', feature: true do
...
@@ -22,10 +23,11 @@ describe 'Awards Emoji', feature: true do
# The `heart_tip` emoji is not valid anymore so we need to skip validation
# The `heart_tip` emoji is not valid anymore so we need to skip validation
issue
.
award_emoji
.
build
(
user:
user
,
name:
'heart_tip'
).
save!
(
validate:
false
)
issue
.
award_emoji
.
build
(
user:
user
,
name:
'heart_tip'
).
save!
(
validate:
false
)
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
wait_for_vue_resource
end
end
# Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
# Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
it
'does not shows a 500 page'
do
it
'does not shows a 500 page'
,
js:
true
do
expect
(
page
).
to
have_text
(
issue
.
title
)
expect
(
page
).
to
have_text
(
issue
.
title
)
end
end
end
end
...
@@ -35,6 +37,7 @@ describe 'Awards Emoji', feature: true do
...
@@ -35,6 +37,7 @@ describe 'Awards Emoji', feature: true do
before
do
before
do
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
wait_for_vue_resource
end
end
it
'increments the thumbsdown emoji'
,
js:
true
do
it
'increments the thumbsdown emoji'
,
js:
true
do
...
...
spec/features/issues/move_spec.rb
浏览文件 @
1c783007
...
@@ -37,8 +37,8 @@ feature 'issue move to another project' do
...
@@ -37,8 +37,8 @@ feature 'issue move to another project' do
edit_issue
(
issue
)
edit_issue
(
issue
)
end
end
scenario
'moving issue to another project'
do
scenario
'moving issue to another project'
,
js:
true
do
fi
rst
(
'#move_to_project_id'
,
visible:
false
).
set
(
new_project
.
id
)
fi
nd
(
'#move_to_project_id'
,
visible:
false
).
set
(
new_project
.
id
)
click_button
(
'Save changes'
)
click_button
(
'Save changes'
)
expect
(
current_url
).
to
include
project_path
(
new_project
)
expect
(
current_url
).
to
include
project_path
(
new_project
)
...
...
spec/features/issues/spam_issues_spec.rb
浏览文件 @
1c783007
require
'rails_helper'
require
'rails_helper'
describe
'New issue'
,
feature:
true
do
describe
'New issue'
,
feature:
true
,
js:
true
do
include
StubENV
include
StubENV
let
(
:project
)
{
create
(
:project
,
:public
)
}
let
(
:project
)
{
create
(
:project
,
:public
)
}
...
...
spec/features/issues_spec.rb
浏览文件 @
1c783007
...
@@ -695,4 +695,21 @@ describe 'Issues', feature: true do
...
@@ -695,4 +695,21 @@ describe 'Issues', feature: true do
end
end
end
end
end
end
describe
'title issue#show'
,
js:
true
do
include
WaitForVueResource
it
'updates the title'
,
js:
true
do
issue
=
create
(
:issue
,
author:
@user
,
assignee:
@user
,
project:
project
,
title:
'new title'
)
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
expect
(
page
).
to
have_text
(
"new title"
)
issue
.
update
(
title:
"updated title"
)
wait_for_vue_resource
expect
(
page
).
to
have_text
(
"updated title"
)
end
end
end
end
spec/javascripts/issue_show/issue_title_spec.js
0 → 100644
浏览文件 @
1c783007
import
Vue
from
'
vue
'
;
import
issueTitle
from
'
~/issue_show/issue_title
'
;
describe
(
'
Issue Title
'
,
()
=>
{
let
IssueTitleComponent
;
beforeEach
(()
=>
{
IssueTitleComponent
=
Vue
.
extend
(
issueTitle
);
});
it
(
'
should render a title
'
,
()
=>
{
const
component
=
new
IssueTitleComponent
({
propsData
:
{
initialTitle
:
'
wow
'
,
endpoint
:
'
/gitlab-org/gitlab-shell/issues/9/rendered_title
'
,
},
}).
$mount
();
expect
(
component
.
$el
.
classList
).
toContain
(
'
title
'
);
expect
(
component
.
$el
.
innerHTML
).
toContain
(
'
wow
'
);
});
});
spec/javascripts/test_bundle.js
浏览文件 @
1c783007
...
@@ -64,6 +64,7 @@ if (process.env.BABEL_ENV === 'coverage') {
...
@@ -64,6 +64,7 @@ if (process.env.BABEL_ENV === 'coverage') {
'
./snippet/snippet_bundle.js
'
,
'
./snippet/snippet_bundle.js
'
,
'
./terminal/terminal_bundle.js
'
,
'
./terminal/terminal_bundle.js
'
,
'
./users/users_bundle.js
'
,
'
./users/users_bundle.js
'
,
'
./issue_show/index.js
'
,
];
];
describe
(
'
Uncovered files
'
,
function
()
{
describe
(
'
Uncovered files
'
,
function
()
{
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录