Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yii2
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PSDI Army
yii2
Commits
04a825f4
Commit
04a825f4
authored
May 20, 2013
by
Qiang Xue
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Finished bootstrap Widget and Modal.
parent
7f9cc397
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
198 additions
and
277 deletions
+198
-277
View.php
yii/base/View.php
+1
-1
Modal.php
yii/bootstrap/Modal.php
+145
-205
Widget.php
yii/bootstrap/Widget.php
+52
-71
No files found.
yii/base/View.php
View file @
04a825f4
...
@@ -741,7 +741,7 @@ class View extends Component
...
@@ -741,7 +741,7 @@ class View extends Component
$lines
[]
=
Html
::
script
(
implode
(
"
\n
"
,
$this
->
js
[
self
::
POS_END
]),
array
(
'type'
=>
'text/javascript'
));
$lines
[]
=
Html
::
script
(
implode
(
"
\n
"
,
$this
->
js
[
self
::
POS_END
]),
array
(
'type'
=>
'text/javascript'
));
}
}
if
(
!
empty
(
$this
->
js
[
self
::
POS_READY
]))
{
if
(
!
empty
(
$this
->
js
[
self
::
POS_READY
]))
{
$js
=
"jQuery(document).ready(function(){\n
{"
.
implode
(
"
\n
"
,
$this
->
js
[
self
::
POS_READY
])
.
"}
\n
});"
;
$js
=
"jQuery(document).ready(function(){\n
"
.
implode
(
"
\n
"
,
$this
->
js
[
self
::
POS_READY
])
.
"
\n
});"
;
$lines
[]
=
Html
::
script
(
$js
,
array
(
'type'
=>
'text/javascript'
));
$lines
[]
=
Html
::
script
(
$js
,
array
(
'type'
=>
'text/javascript'
));
}
}
return
empty
(
$lines
)
?
''
:
implode
(
"
\n
"
,
$lines
)
.
"
\n
"
;
return
empty
(
$lines
)
?
''
:
implode
(
"
\n
"
,
$lines
)
.
"
\n
"
;
...
...
yii/bootstrap/Modal.php
View file @
04a825f4
...
@@ -8,279 +8,219 @@
...
@@ -8,279 +8,219 @@
namespace
yii\bootstrap
;
namespace
yii\bootstrap
;
use
Yii
;
use
Yii
;
use
yii\helpers\Html
;
use
yii\helpers\ArrayHelper
;
use
yii\helpers\ArrayHelper
;
use
yii\helpers\Html
;
/**
/**
* Modal renders a bootstrap modal on the page for its use on your application.
* Modal renders a modal window that can be toggled by clicking on a button.
*
* For example,
*
*
* Basic usage:
* ~~~php
* echo Modal::widget(array(
* 'header' => '<h2>Hello world</h2>',
* 'body' => 'Say hello...',
* 'toggleButton' => array(
* 'label' => 'click me',
* ),
* ));
* ~~~
*
* The following example will show the content enclosed between the [[begin()]]
* and [[end()]] calls within the modal window:
*
*
* ```php
* ~~~php
* $this->widget(Modal::className(), array(
* Modal::begin(array(
* 'id' => 'myModal',
* 'header' => '<h2>Hello world</h2>',
* 'header' => 'Modal Heading',
* 'toggleButton' => array(
* 'content' => '<p>One fine body...</p>',
* 'label' => 'click me',
* 'footer' => 'Modal Footer',
* ),
* // if we wish to display a modal button
* 'buttonOptions' => array(
* 'label' => 'Show Modal',
* 'class' => 'btn btn-primary'
* )
* ));
* ));
* ```
*
* echo 'Say hello...';
*
* Modal::end();
* ~~~
*
* @see http://twitter.github.io/bootstrap/javascript.html#modals
* @see http://twitter.github.io/bootstrap/javascript.html#modals
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
* @since 2.0
*/
*/
class
Modal
extends
Widget
class
Modal
extends
Widget
{
{
/**
/**
* @var array The additional HTML attributes of the button that will show the modal. If empty array, only
* @var string the header content in the modal window.
* the markup of the modal will be rendered on the page, so users can easily call the modal manually with their own
* scripts. The following special attributes are available:
* <ul>
* <li>label: string, the label of the button</li>
* </ul>
*
* For available options of the button trigger, see http://twitter.github.com/bootstrap/javascript.html#modals.
*/
public
$buttonOptions
=
array
();
/**
* @var boolean indicates whether the modal should use transitions. Defaults to 'true'.
*/
public
$fade
=
true
;
/**
* @var bool $keyboard, closes the modal when escape key is pressed.
*/
public
$keyboard
=
true
;
/**
* @var bool $show, shows the modal when initialized.
*/
public
$show
=
false
;
/**
* @var mixed includes a modal-backdrop element. Alternatively, specify `static` for a backdrop which doesn't close
* the modal on click.
*/
public
$backdrop
=
true
;
/**
* @var mixed the remote url. If a remote url is provided, content will be loaded via jQuery's load method and
* injected into the .modal-body of the modal.
*/
*/
public
$remote
;
public
$header
;
/**
* @var string a javascript function that will be invoked immediately when the `show` instance method is called.
*/
public
$onShow
;
/**
* @var string a javascript function that will be invoked when the modal has been made visible to the user
* (will wait for css transitions to complete).
*/
public
$onShown
;
/**
* @var string a javascript function that will be invoked immediately when the hide instance method has been called.
*/
public
$onHide
;
/**
* @var string a javascript function that will be invoked when the modal has finished being hidden from the user
* (will wait for css transitions to complete).
*/
public
$onHidden
;
/**
* @var string[] the Javascript event handlers.
*/
protected
$events
=
array
();
/**
/**
* @var array $pluginOptions the plugin options.
* @var string the body content in the modal window. Note that anything between
* the [[begin()]] and [[end()]] calls of the Modal widget will also be treated
* as the body content, and will be rendered before this.
*/
*/
protected
$pluginOptions
=
array
();
public
$body
;
/**
/**
* @var string
* @var string
the footer content in the modal window.
*/
*/
public
$closeText
=
'×'
;
public
$footer
;
/**
/**
* @var string header content. Header can also be a path to a view file.
* @var array the options for rendering the close button tag.
* The close button is displayed in the header of the modal window. Clicking
* on the button will hide the modal window. If this is null, no close button will be rendered.
*
* The following special options are supported:
*
* - tag: string, the tag name of the button. Defaults to 'button'.
* - label: string, the label of the button. Defaults to '×'.
*
* The rest of the options will be rendered as the HTML attributes of the button tag.
* Please refer to the [Modal plugin help](http://twitter.github.com/bootstrap/javascript.html#modals)
* for the supported HTML attributes.
*/
*/
public
$header
;
public
$closeButton
=
array
();
/**
/**
* @var string body of modal. Body can also be a path to a view file.
* @var array the options for rendering the toggle button tag.
* The toggle button is used to toggle the visibility of the modal window.
* If this property is null, no toggle button will be rendered.
*
* The following special options are supported:
*
* - tag: string, the tag name of the button. Defaults to 'button'.
* - label: string, the label of the button. Defaults to 'Show'.
*
* The rest of the options will be rendered as the HTML attributes of the button tag.
* Please refer to the [Modal plugin help](http://twitter.github.com/bootstrap/javascript.html#modals)
* for the supported HTML attributes.
*/
*/
public
$
content
;
public
$
toggleButton
;
/**
* @var string footer content. Content can also be a path to a view file.
*/
public
$footer
;
/**
/**
*
Widget's init method
*
Initializes the widget.
*/
*/
public
function
init
()
public
function
init
()
{
{
parent
::
init
();
parent
::
init
();
$this
->
name
=
'modal'
;
$this
->
options
=
array_merge
(
array
(
'class'
=>
'modal hide'
,
$this
->
defaultOption
(
'id'
,
$this
->
getId
()
);
),
$this
->
options
);
$this
->
addCssClass
(
$this
->
options
,
'modal'
);
$this
->
defaultOption
(
'role'
,
'dialog'
);
$this
->
defaultOption
(
'tabindex'
,
'-1'
);
$this
->
pluginOptions
=
array_merge
(
array
(
'show'
=>
false
,
$this
->
addClassName
(
'modal'
);
),
$this
->
pluginOptions
);
$this
->
addClassName
(
'hide'
);
if
(
$this
->
closeButton
!==
null
)
{
if
(
$this
->
fade
)
$this
->
closeButton
=
array_merge
(
array
(
$this
->
addClassName
(
'fade'
);
'data-dismiss'
=>
'modal'
,
'aria-hidden'
=>
'true'
,
$this
->
initPluginOptions
();
'class'
=>
'close'
,
$this
->
initPluginEvents
(
);
),
$this
->
closeButton
);
}
}
/**
if
(
$this
->
toggleButton
!==
null
)
{
* Initialize plugin events if any
$this
->
toggleButton
=
array_merge
(
array
(
*/
'data-toggle'
=>
'modal'
,
public
function
initPluginEvents
()
),
$this
->
toggleButton
);
{
if
(
!
isset
(
$this
->
toggleButton
[
'data-target'
])
&&
!
isset
(
$this
->
toggleButton
[
'href'
]))
{
foreach
(
array
(
'onShow'
,
'onShown'
,
'onHide'
,
'onHidden'
)
as
$event
)
{
$this
->
toggleButton
[
'data-target'
]
=
'#'
.
$this
->
options
[
'id'
];
if
(
$this
->
{
$event
}
!==
null
)
{
$modalEvent
=
strtolower
(
substr
(
$event
,
2
));
if
(
$this
->
{
$event
}
instanceof
JsExpression
)
$this
->
events
[
$modalEvent
]
=
$this
->
$event
;
else
$this
->
events
[
$modalEvent
]
=
new
JsExpression
(
$this
->
{
$event
});
}
}
}
}
}
/**
* Initialize plugin options.
* ***Important***: The display of the button overrides the initialization of the modal bootstrap widget.
*/
public
function
initPluginOptions
()
{
if
(
null
!==
$this
->
remote
)
$this
->
pluginOptions
[
'remote'
]
=
Html
::
url
(
$this
->
remote
);
foreach
(
array
(
'backdrop'
,
'keyboard'
,
'show'
)
as
$option
)
{
ob_start
();
$this
->
pluginOptions
[
$option
]
=
isset
(
$this
->
pluginOptions
[
$option
])
ob_implicit_flush
(
false
);
?
$this
->
pluginOptions
[
$option
]
:
$this
->
{
$option
};
}
}
}
/**
/**
*
Widget's run method
*
Renders the widget.
*/
*/
public
function
run
()
public
function
run
()
{
{
$this
->
renderModal
();
$this
->
body
=
ob_get_clean
()
.
$this
->
body
;
$this
->
renderButton
();
$this
->
registerScript
();
}
/**
* Renders the button that will open the modal if its options have been configured
*/
public
function
renderButton
()
{
if
(
!
empty
(
$this
->
buttonOptions
))
{
$this
->
buttonOptions
[
'data-toggle'
]
=
isset
(
$this
->
buttonOptions
[
'data-toggle'
])
?
$this
->
buttonOptions
[
'data-toggle'
]
:
'modal'
;
if
(
$this
->
remote
!==
null
&&
!
isset
(
$this
->
buttonOptions
[
'data-remote'
]))
echo
$this
->
renderToggleButton
();
$this
->
buttonOptions
[
'data-remote'
]
=
Html
::
url
(
$this
->
remote
);
$label
=
ArrayHelper
::
remove
(
$this
->
buttonOptions
,
'label'
,
'Button'
);
$html
=
$this
->
renderHeader
()
.
"
\n
"
$name
=
ArrayHelper
::
remove
(
$this
->
buttonOptions
,
'name'
);
.
$this
->
renderBody
()
.
"
\n
"
$value
=
ArrayHelper
::
remove
(
$this
->
buttonOptions
,
'value'
);
.
$this
->
renderFooter
();
echo
Html
::
tag
(
'div'
,
"
\n
"
.
$html
.
"
\n
"
,
$this
->
options
);
$attr
=
isset
(
$this
->
buttonOptions
[
'data-remote'
])
$this
->
registerPlugin
(
'modal'
);
?
'data-target'
:
'href'
;
$this
->
buttonOptions
[
$attr
]
=
isset
(
$this
->
buttonOptions
[
$attr
])
?
$this
->
buttonOptions
[
$attr
]
:
'#'
.
ArrayHelper
::
getValue
(
$this
->
options
,
'id'
);
echo
Html
::
button
(
$label
,
$name
,
$value
,
$this
->
buttonOptions
);
}
}
}
/**
/**
* Renders the modal markup
* Renders the header HTML markup of the modal
* @return string the rendering result
*/
*/
p
ublic
function
renderModal
()
p
rotected
function
renderHeader
()
{
{
echo
Html
::
beginTag
(
'div'
,
$this
->
options
);
$button
=
$this
->
renderCloseButton
();
if
(
$button
!==
null
)
{
$this
->
renderModalHeader
();
$this
->
header
=
$button
.
"
\n
"
.
$this
->
header
;
$this
->
renderModalBody
();
}
$this
->
renderModalFooter
();
if
(
$this
->
header
!==
null
)
{
return
Html
::
tag
(
'div'
,
"
\n
"
.
$this
->
header
.
"
\n
"
,
array
(
'class'
=>
'modal-header'
));
echo
Html
::
endTag
(
'div'
);
}
else
{
return
null
;
}
}
}
/**
/**
* Renders the header HTML markup of the modal
* Renders the HTML markup for the body of the modal
* @return string the rendering result
*/
*/
p
ublic
function
renderModalHeader
()
p
rotected
function
renderBody
()
{
{
echo
Html
::
beginTag
(
'div'
,
array
(
'class'
=>
'modal-header'
));
return
Html
::
tag
(
'div'
,
$this
->
body
,
array
(
'class'
=>
'modal-body'
));
if
(
$this
->
closeText
)
echo
Html
::
button
(
$this
->
closeText
,
null
,
null
,
array
(
'data-dismiss'
=>
'modal'
,
'class'
=>
'close'
));
echo
$this
->
header
;
echo
Html
::
endTag
(
'div'
);
}
}
/**
/**
* Renders the HTML markup for the body of the modal
* Renders the HTML markup for the footer of the modal
* @return string the rendering result
*/
*/
p
ublic
function
renderModalBody
()
p
rotected
function
renderFooter
()
{
{
echo
Html
::
beginTag
(
'div'
,
array
(
'class'
=>
'modal-body'
));
if
(
$this
->
footer
!==
null
)
{
echo
$this
->
content
;
return
Html
::
tag
(
'div'
,
$this
->
footer
,
array
(
'class'
=>
'modal-footer'
));
echo
Html
::
endTag
(
'div'
);
}
else
{
return
null
;
}
}
}
/**
/**
* Renders the HTML markup for the footer of the modal
* Renders the toggle button.
* @return string the rendering result
*/
*/
p
ublic
function
renderModalFooter
()
p
rotected
function
renderToggleButton
()
{
{
if
(
$this
->
toggleButton
!==
null
)
{
echo
Html
::
beginTag
(
'div'
,
array
(
'class'
=>
'modal-footer'
));
$tag
=
ArrayHelper
::
remove
(
$this
->
toggleButton
,
'tag'
,
'button'
);
echo
$this
->
footer
;
$label
=
ArrayHelper
::
remove
(
$this
->
toggleButton
,
'label'
,
'Show'
);
echo
Html
::
endTag
(
'div'
);
if
(
$tag
===
'button'
&&
!
isset
(
$this
->
toggleButton
[
'type'
]))
{
$this
->
toggleButton
[
'type'
]
=
'button'
;
}
return
Html
::
tag
(
$tag
,
$label
,
$this
->
toggleButton
)
.
"
\n
"
;
}
else
{
return
null
;
}
}
}
/**
/**
* Registers client scripts
* Renders the close button.
* @return string the rendering result
*/
*/
p
ublic
function
registerScript
()
p
rotected
function
renderCloseButton
()
{
{
// do we render a button? If so, bootstrap will handle its behavior through its
if
(
$this
->
closeButton
!==
null
)
{
// mark-up, otherwise, register the plugin.
$tag
=
ArrayHelper
::
remove
(
$this
->
closeButton
,
'tag'
,
'button'
);
if
(
empty
(
$this
->
buttonOptions
))
$label
=
ArrayHelper
::
remove
(
$this
->
closeButton
,
'label'
,
'×'
);
$this
->
registerPlugin
(
'modal'
,
$this
->
pluginOptions
);
if
(
$tag
===
'button'
&&
!
isset
(
$this
->
closeButton
[
'type'
]))
{
$this
->
closeButton
[
'type'
]
=
'button'
;
// register events
}
$this
->
registerEvents
(
$this
->
events
);
return
Html
::
tag
(
$tag
,
$label
,
$this
->
closeButton
);
}
else
{
return
null
;
}
}
}
}
}
yii/bootstrap/Widget.php
View file @
04a825f4
...
@@ -9,19 +9,20 @@ namespace yii\bootstrap;
...
@@ -9,19 +9,20 @@ namespace yii\bootstrap;
use
Yii
;
use
Yii
;
use
yii\base\View
;
use
yii\base\View
;
use
yii\helpers\Json
;
/**
/**
*
Bootstrap is the base class for
bootstrap widgets.
*
\yii\bootstrap\Widget is the base class for all
bootstrap widgets.
*
*
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
* @since 2.0
*/
*/
class
Widget
extends
\yii\base\Widget
class
Widget
extends
\yii\base\Widget
{
{
/**
/**
* @var bool
whether to register the asset
* @var bool
ean whether to use the responsive version of Bootstrap.
*/
*/
public
static
$responsive
=
true
;
public
static
$responsive
=
true
;
...
@@ -29,95 +30,75 @@ class Widget extends \yii\base\Widget
...
@@ -29,95 +30,75 @@ class Widget extends \yii\base\Widget
* @var array the HTML attributes for the widget container tag.
* @var array the HTML attributes for the widget container tag.
*/
*/
public
$options
=
array
();
public
$options
=
array
();
/**
* @var array the options for the underlying Bootstrap JS plugin.
* Please refer to the corresponding Bootstrap plugin Web page for possible options.
* For example, [this page](http://twitter.github.io/bootstrap/javascript.html#modals) shows
* how to use the "Modal" plugin and the supported options (e.g. "remote").
*/
public
$pluginOptions
=
array
();
/**
* @var array the event handlers for the underlying Bootstrap JS plugin.
* Please refer to the corresponding Bootstrap plugin Web page for possible events.
* For example, [this page](http://twitter.github.io/bootstrap/javascript.html#modals) shows
* how to use the "Modal" plugin and the supported events (e.g. "shown").
*/
public
$pluginEvents
=
array
();
/**
/**
* Initializes the widget.
* Initializes the widget.
* This method will register the bootstrap asset bundle. If you override this method,
* make sure you call the parent implementation first.
*/
*/
public
function
init
()
public
function
init
()
{
{
// ensure bundle
parent
::
init
();
$this
->
registerBundle
(
static
::
$responsive
);
if
(
!
isset
(
$this
->
options
[
'id'
]))
{
$this
->
options
[
'id'
]
=
$this
->
getId
();
}
}
}
/**
/**
* Registers plugin events with the API.
* Registers a specific Bootstrap plugin and the related events
* @param string $selector the CSS selector.
* @param string $name the name of the Bootstrap plugin
* @param string[] $events the JavaScript event configuration (name=>handler).
* @return boolean whether the events were registered.
* @todo To be discussed
*/
*/
protected
function
register
Events
(
$selector
,
$events
=
array
()
)
protected
function
register
Plugin
(
$name
)
{
{
if
(
empty
(
$events
))
$id
=
$this
->
options
[
'id'
];
return
;
$view
=
$this
->
getView
()
;
$script
=
''
;
$bundle
=
static
::
$responsive
?
'yii/bootstrap-responsive'
:
'yii/bootstrap'
;
foreach
(
$events
as
$name
=>
$handler
)
{
$view
->
registerAssetBundle
(
$bundle
);
$handler
=
(
$handler
instanceof
JsExpression
)
?
$handler
:
new
JsExpression
(
$handler
);
$script
.=
";jQuery('
{
$selector
}
').on('
{
$name
}
',
{
$handler
}
);"
;
if
(
$this
->
pluginOptions
!==
false
)
{
}
$options
=
empty
(
$this
->
pluginOptions
)
?
''
:
Json
::
encode
(
$this
->
pluginOptions
);
if
(
!
empty
(
$script
))
$js
=
"jQuery('#
$id
').
$name
(
$options
);"
;
$
this
->
view
->
registerJs
(
$script
);
$
view
->
registerJs
(
$js
);
}
}
/**
if
(
!
empty
(
$this
->
pluginEvents
))
{
* Registers a specific Bootstrap plugin using the given selector and options.
$js
=
array
();
*
foreach
(
$this
->
pluginEvents
as
$event
=>
$handler
)
{
* @param string $name the name of the javascript widget to initialize
$js
[]
=
"jQuery('#
$id
').on('
$event
',
$handler
);"
;
* @param array $options the Javascript options for the plugin
*/
public
function
registerPlugin
(
$name
,
$options
=
array
())
{
$selector
=
'#'
.
ArrayHelper
::
getValue
(
$this
->
options
,
'id'
);
$options
=
!
empty
(
$options
)
?
Json
::
encode
(
$options
)
:
''
;
$script
=
";jQuery('
{
$selector
}
').
{
$name
}
(
{
$options
}
);"
;
$this
->
view
->
registerJs
(
$script
);
}
}
$view
->
registerJs
(
implode
(
"
\n
"
,
$js
));
/**
* Registers bootstrap bundle
* @param bool $responsive
*/
public
function
registerBundle
(
$responsive
=
false
)
{
$bundle
=
$responsive
?
'yii/bootstrap-responsive'
:
'yii/bootstrap'
;
$this
->
view
->
registerAssetBundle
(
$bundle
);
}
}
/**
* Adds a new class to options. If the class key does not exists, it will create one, if it exists it will append
* the value and also makes sure the uniqueness of them.
*
* @param string $class
* @return array
*/
protected
function
addClassName
(
$class
)
{
if
(
isset
(
$this
->
options
[
'class'
]))
{
if
(
!
is_array
(
$this
->
options
[
'class'
]))
$this
->
options
[
'class'
]
=
explode
(
' '
,
$this
->
options
[
'class'
]);
$this
->
options
[
'class'
][]
=
$class
;
$this
->
options
[
'class'
]
=
array_unique
(
$this
->
options
[
'class'
]);
$this
->
options
[
'class'
]
=
implode
(
' '
,
$this
->
options
[
'class'
]);
}
else
$this
->
options
[
'class'
]
=
$class
;
return
$this
->
options
;
}
}
/**
/**
*
Sets the default value for an item if not set
.
*
Adds a CSS class to the specified options
.
*
@param string $key the name of the item
.
*
This method will ensure that the CSS class is unique and the "class" option is properly formatted
.
* @param
mixed $value the default value
.
* @param
array $options the options to be modified
.
* @
return array
* @
param string $class the CSS class to be added
*/
*/
protected
function
defaultOption
(
$key
,
$value
)
protected
function
addCssClass
(
&
$options
,
$class
)
{
{
if
(
!
isset
(
$this
->
options
[
$key
]))
if
(
isset
(
$options
[
'class'
]))
{
$this
->
options
[
$key
]
=
$value
;
$classes
=
preg_split
(
'/\s+/'
,
$options
[
'class'
]
.
' '
.
$class
,
-
1
,
PREG_SPLIT_NO_EMPTY
);
return
$this
->
options
;
$options
[
'class'
]
=
implode
(
' '
,
array_unique
(
$classes
));
}
else
{
$options
[
'class'
]
=
$class
;
}
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment