summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile20
-rw-r--r--api/admin_test.go8
-rw-r--r--doc/help/Notifications.md31
-rw-r--r--doc/help/README.md15
-rw-r--r--doc/help/Team-Settings.md3
-rw-r--r--doc/install/Configuration-Settings.md13
-rw-r--r--store/sql_post_store.go17
-rw-r--r--store/sql_post_store_test.go5
-rw-r--r--utils/time.go23
-rw-r--r--utils/time_test.go50
-rw-r--r--web/react/components/posts_view.jsx4
-rw-r--r--web/react/components/sidebar.jsx4
-rw-r--r--web/react/components/view_image.jsx34
-rw-r--r--web/react/utils/markdown.jsx124
-rw-r--r--web/sass-files/sass/partials/_modal.scss40
-rw-r--r--web/sass-files/sass/partials/_post.scss12
-rw-r--r--web/sass-files/sass/partials/_responsive.scss6
18 files changed, 303 insertions, 108 deletions
diff --git a/.gitignore b/.gitignore
index 50cdca100..6e433df3c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,8 @@ web/static/js/libs*.js
# Build Targets
.prepare
+.prepare-go
+.prepare-jsx
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
diff --git a/Makefile b/Makefile
index 87219e68c..098422052 100644
--- a/Makefile
+++ b/Makefile
@@ -33,7 +33,7 @@ dist: | build-server build-client go-test package
mv ./model/version.go.bak ./model/version.go
dist-local: | start-docker dist
-
+
dist-travis: | travis-init build-container
start-docker:
@@ -153,7 +153,7 @@ go-test:
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./utils || exit 1
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./web || exit 1
-test: | start-docker go-test
+test: | start-docker .prepare-go go-test
travis-init:
@echo Setting up enviroment for travis
@@ -217,25 +217,29 @@ clean: stop-docker
rm -rf Godeps/_workspace/pkg/
rm -f mattermost.log
- rm -f .prepare
+ rm -f .prepare-go .prepare-jsx
nuke: | clean clean-docker
rm -rf data
-.prepare:
- @echo Preparation for run step
-
+.prepare-go:
+ @echo Preparation for running go code
go get $(GOFLAGS) github.com/tools/godep
+ touch $@
+
+.prepare-jsx:
+ @echo Preparation for compiling jsx code
+
cd web/react/ && npm install
cd web/react/ && npm run build-libs
touch $@
-run: start-docker .prepare
+run: start-docker .prepare-go .prepare-jsx
mkdir -p web/static/js
- @echo Starting react processor
+ @echo Starting react processo
cd web/react && npm start &
@echo Starting go web server
diff --git a/api/admin_test.go b/api/admin_test.go
index 0db5caa4c..0a1682a99 100644
--- a/api/admin_test.go
+++ b/api/admin_test.go
@@ -235,6 +235,10 @@ func TestGetPostCount(t *testing.T) {
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
+ // manually update creation time, since it's always set to 0 upon saving and we only retrieve posts < today
+ Srv.Store.(*store.SqlStore).GetMaster().Exec("UPDATE Posts SET CreateAt = :CreateAt WHERE ChannelId = :ChannelId",
+ map[string]interface{}{"ChannelId": channel1.Id, "CreateAt": utils.MillisFromTime(utils.Yesterday())})
+
if _, err := Client.GetAnalytics(team.Id, "post_counts_day"); err == nil {
t.Fatal("Shouldn't have permissions")
}
@@ -276,6 +280,10 @@ func TestUserCountsWithPostsByDay(t *testing.T) {
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
+ // manually update creation time, since it's always set to 0 upon saving and we only retrieve posts < today
+ Srv.Store.(*store.SqlStore).GetMaster().Exec("UPDATE Posts SET CreateAt = :CreateAt WHERE ChannelId = :ChannelId",
+ map[string]interface{}{"ChannelId": channel1.Id, "CreateAt": utils.MillisFromTime(utils.Yesterday())})
+
if _, err := Client.GetAnalytics(team.Id, "user_counts_with_posts_day"); err == nil {
t.Fatal("Shouldn't have permissions")
}
diff --git a/doc/help/Notifications.md b/doc/help/Notifications.md
new file mode 100644
index 000000000..31f06e713
--- /dev/null
+++ b/doc/help/Notifications.md
@@ -0,0 +1,31 @@
+# Notifications
+___
+
+Notifications in Mattermost alert you to unread mentions and messages.
+
+## Types of Notifications
+
+#### Email Notifications
+These are emails sent to your primary email address for any mentions you receive while offline or inactive.
+- Users are offline when they do not have Mattermost open.
+- Users are inactive when they have Mattermost open but haven’t performed an action for a set amount of time.
+- You can change the email to which these notifications are sent in **Account Settings** > **General** > **Email**.
+- You can turn email notifications on or off in **Account Settings** > **Notifications** > **Email Notifications**.
+
+#### Desktop Notifications
+These are browser notifications that are by default sent for all activity.
+- You can adjust this setting in **Account Settings** > **Notifications** > **Send Desktop Notifications**.
+- Channel specific notifications are automatically set to the global default but can be modified in **Channel Settings** > **Notification Preferences** > **Send Desktop Notifications**.
+- Desktop notifications are available on Firefox, Safari, and Chrome.
+
+
+#### Sound Notifications
+These accompany each desktop notification by default.
+- You can change this setting in **Account Settings** > **Notifications** > **Desktop Notification Sounds**.
+
+
+#### Browser Tab Notifications
+These appear in the Mattermost tab and inform you of any unread messages and alert you to the number of mentions you have.
+- Unread messages are denoted by an asterisk (*) next to the Mattermost icon.
+- Mentions and replies are denoted by a red Mattermost icon.
+- The total number of unread mentions and replies are shown in brackets next to the Mattermost icon. For example, if you have 3 unread mentions, you’ll see a (3) in the browser tab.
diff --git a/doc/help/README.md b/doc/help/README.md
index 3b3c1709b..23c8b192d 100644
--- a/doc/help/README.md
+++ b/doc/help/README.md
@@ -5,13 +5,18 @@
- User Interface
- Main Menu
- - [Team Settings ](https://github.com/mattermost/platform/blob/help-docs-update/doc/help/Team-Settings.md)
- - [General Settings](https://github.com/mattermost/platform/blob/help-docs-update/doc/help/Team-Settings.md#general)
- - [Slack Import](https://github.com/mattermost/platform/blob/help-docs-update/doc/help/Team-Settings.md#import-from-slack-beta)
+ - [Team Settings ](Team-Settings.md)
+ - [General Settings](Team-Settings.md#general)
+ - [Slack Import](Team-Settings.md#import-from-slack-beta)
- [Manage Members](Manage-Members.md)
- - Messaging
- - [Mattermost Markdown Formatting](help/Markdown.md)
+ - [Account Settings](Account-Settings.md)
+ - [Messaging](Messaging.md)
+ - [Mattermost Markdown Formatting](Markdown.md)
- [Search](Search.md)
+ - [Channels](Channels.md)
+ - [Channel Types](Channels.md#channel-types)
+ - [Managing Channels](Channels.md#managing-channels)
+ - [Channel Settings](Channels.md#channel-settings)
- System Console
- Team
diff --git a/doc/help/Team-Settings.md b/doc/help/Team-Settings.md
index 7c8665565..fead9f4ca 100644
--- a/doc/help/Team-Settings.md
+++ b/doc/help/Team-Settings.md
@@ -64,7 +64,8 @@ The Slack Import feature in Mattermost is in "Beta" and focus is on supporting m
#### Notes:
-- Newly added markdown suppport in Slack's Posts 2.0 feature announced on September 28, 2015 is not yet supported.
+- Users are not automatically added to channels or groups when importing from Slack.
+- Newly added markdown support in Slack's Posts 2.0 feature announced on September 28, 2015 is not yet supported.
- Slack does not export files or images your team has stored in Slack's database. Mattermost will provide links to the location of your assets in Slack's web UI.
- Slack does not export any content from private groups or direct messages that your team has stored in Slack's database.
- In Beta, Slack accounts with usernames or email addresses identical to existing Mattermost accounts will not import and mentions do not resolve as Mattermost usernames (still shows Slack ID). No pre-check or roll-back is currently offered.
diff --git a/doc/install/Configuration-Settings.md b/doc/install/Configuration-Settings.md
index a92893753..44730d40f 100644
--- a/doc/install/Configuration-Settings.md
+++ b/doc/install/Configuration-Settings.md
@@ -29,7 +29,12 @@ Set this key to enable embedding of YouTube video previews based on hyperlinks a
#### Webhooks
```"EnableIncomingWebhooks": true```
-Developers building integrations can create webhook URLs for channels and private groups. Please see http://mattermost.org/webhooks to learn about creating webhooks, view samples, and to let the community know about integrations you have built. "true": Incoming webhooks will be allowed. To manage incoming webhooks, go to Account Settings -> Integrations. The webhook URLs created in Account Settings can be used by external applications to create posts in any channels or private groups that you have access to; “false”: The Integrations tab of Account Settings is hidden and incoming webhooks are disabled.
+Developers building integrations can create webhook URLs for channels and private groups. Please see http://mattermost.org/webhooks to learn about creating webhooks, view samples, and to let the community know about integrations you have built. "true": Incoming webhooks will be allowed. To manage incoming webhooks, go to **Account Settings -> Integrations**. The webhook URLs created in Account Settings can be used by external applications to create posts in any channels or private groups that you have access to; “false”: The Integrations > Incoming Webhooks section of Account Settings is hidden and all incoming webhooks are disabled.
+
+Security note: By enabling this feature, users may be able to perform [phishing attacks](https://en.wikipedia.org/wiki/Phishing) by attempting to impersonate other users. To combat these attacks, a BOT tag appears next to all posts from a webhook. Enable at your own risk.
+
+```"EnableOutgoingWebhooks": true```
+Developers building integrations can create webhook tokens for public channels. Trigger words are used to fire new message events to external integrations. For security reasons, outgoing webhooks are only available in public channels. Please see our [documentation page](https://github.com/mattermost/platform/blob/master/doc/integrations/webhooks/Outgoing-Webhooks.md) to learn about creating webhooks and view samples. "true": Outgoing webhooks will be allowed. To manage outgoing webhooks, go to **Account Settings -> Integrations**; “false”: The Integrations > Outgoing Webhooks section of Account Settings is hidden and all outgoing webhooks are disabled.
Security note: By enabling this feature, users may be able to perform [phishing attacks](https://en.wikipedia.org/wiki/Phishing) by attempting to impersonate other users. To combat these attacks, a BOT tag appears next to all posts from a webhook. Enable at your own risk.
@@ -58,6 +63,12 @@ Maximum number of users per team, including both active and inactive users.
```"RestrictCreationToDomains": ""```
Teams can only be created by a verified email from this list of comma-separated domains (e.g. "corp.mattermost.com, mattermost.org").
+```"RestrictTeamNames": true```
+"true": Newly created team names cannot contain the following restricted words: www, web, admin, support, notify, test, demo, mail, team, channel, internal, localhost, dockerhost, stag, post, cluster, api, oauth; “false”: Newly created team names are not restricted.
+
+```"EnableTeamListing": false```
+"true": Teams that are configured to appear in the team directory will appear on the system main page. Teams can configure this setting from **Team Settings -> Include this team in the Team Directory**; "true": Team directory on the system main page is disabled.
+
### SQL Settings
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index cc596074f..1831eb23c 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -807,6 +807,7 @@ func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) StoreChan
WHERE
Posts.ChannelId = Channels.Id
AND Channels.TeamId = :TeamId
+ AND Posts.CreateAt <= :EndTime
ORDER BY Name DESC) AS t1
GROUP BY Name
ORDER BY Name DESC
@@ -825,17 +826,20 @@ func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) StoreChan
WHERE
Posts.ChannelId = Channels.Id
AND Channels.TeamId = :TeamId
+ AND Posts.CreateAt <= :EndTime
ORDER BY Name DESC) AS t1
GROUP BY Name
ORDER BY Name DESC
LIMIT 30`
}
+ end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
+
var rows model.AnalyticsRows
_, err := s.GetReplica().Select(
&rows,
query,
- map[string]interface{}{"TeamId": teamId, "Time": model.GetMillis() - 1000*60*60*24*31})
+ map[string]interface{}{"TeamId": teamId, "EndTime": end})
if err != nil {
result.Err = model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "We couldn't get user counts with posts", err.Error())
} else {
@@ -867,7 +871,8 @@ func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) StoreChannel {
WHERE
Posts.ChannelId = Channels.Id
AND Channels.TeamId = :TeamId
- AND Posts.CreateAt >:Time) AS t1
+ AND Posts.CreateAt <= :EndTime
+ AND Posts.CreateAt >= :StartTime) AS t1
GROUP BY Name
ORDER BY Name DESC
LIMIT 30`
@@ -885,17 +890,21 @@ func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) StoreChannel {
WHERE
Posts.ChannelId = Channels.Id
AND Channels.TeamId = :TeamId
- AND Posts.CreateAt > :Time) AS t1
+ AND Posts.CreateAt <= :EndTime
+ AND Posts.CreateAt >= :StartTime) AS t1
GROUP BY Name
ORDER BY Name DESC
LIMIT 30`
}
+ end := utils.MillisFromTime(utils.EndOfDay(utils.Yesterday()))
+ start := utils.MillisFromTime(utils.StartOfDay(utils.Yesterday().AddDate(0, 0, -31)))
+
var rows model.AnalyticsRows
_, err := s.GetReplica().Select(
&rows,
query,
- map[string]interface{}{"TeamId": teamId, "Time": model.GetMillis() - 1000*60*60*24*31})
+ map[string]interface{}{"TeamId": teamId, "StartTime": start, "EndTime": end})
if err != nil {
result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "We couldn't get post counts by day", err.Error())
} else {
diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go
index d9b087ea7..12b50cad3 100644
--- a/store/sql_post_store_test.go
+++ b/store/sql_post_store_test.go
@@ -9,6 +9,7 @@ import (
"time"
"github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
)
func TestPostStoreSave(t *testing.T) {
@@ -776,7 +777,7 @@ func TestUserCountsWithPostsByDay(t *testing.T) {
o1 := &model.Post{}
o1.ChannelId = c1.Id
o1.UserId = model.NewId()
- o1.CreateAt = model.GetMillis()
+ o1.CreateAt = utils.MillisFromTime(utils.Yesterday())
o1.Message = "a" + model.NewId() + "b"
o1 = Must(store.Post().Save(o1)).(*model.Post)
@@ -836,7 +837,7 @@ func TestPostCountsByDay(t *testing.T) {
o1 := &model.Post{}
o1.ChannelId = c1.Id
o1.UserId = model.NewId()
- o1.CreateAt = model.GetMillis()
+ o1.CreateAt = utils.MillisFromTime(utils.Yesterday())
o1.Message = "a" + model.NewId() + "b"
o1 = Must(store.Post().Save(o1)).(*model.Post)
diff --git a/utils/time.go b/utils/time.go
new file mode 100644
index 000000000..7d5afdf8f
--- /dev/null
+++ b/utils/time.go
@@ -0,0 +1,23 @@
+package utils
+
+import (
+ "time"
+)
+
+func MillisFromTime(t time.Time) int64 {
+ return t.UnixNano() / int64(time.Millisecond)
+}
+
+func StartOfDay(t time.Time) time.Time {
+ year, month, day := t.Date()
+ return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
+}
+
+func EndOfDay(t time.Time) time.Time {
+ year, month, day := t.Date()
+ return time.Date(year, month, day, 23, 59, 59, 999999999, t.Location())
+}
+
+func Yesterday() time.Time {
+ return time.Now().AddDate(0, 0, -1)
+}
diff --git a/utils/time_test.go b/utils/time_test.go
new file mode 100644
index 000000000..7d65046bf
--- /dev/null
+++ b/utils/time_test.go
@@ -0,0 +1,50 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package utils
+
+import (
+ "testing"
+ "time"
+)
+
+var format = "2006-01-02 15:04:05.000000000"
+
+func TestMillisFromTime(t *testing.T) {
+ input, _ := time.Parse(format, "2015-01-01 12:34:00.000000000")
+ actual := MillisFromTime(input)
+ expected := int64(1420115640000)
+
+ if actual != expected {
+ t.Fatalf("TestMillisFromTime failed, %v=%v", expected, actual)
+ }
+}
+
+func TestYesterday(t *testing.T) {
+ actual := Yesterday()
+ expected := time.Now().AddDate(0, 0, -1)
+
+ if actual.Year() != expected.Year() || actual.Day() != expected.Day() || actual.Month() != expected.Month() {
+ t.Fatalf("TestYesterday failed, %v=%v", expected, actual)
+ }
+}
+
+func TestStartOfDay(t *testing.T) {
+ input, _ := time.Parse(format, "2015-01-01 12:34:00.000000000")
+ actual := StartOfDay(input)
+ expected, _ := time.Parse(format, "2015-01-01 00:00:00.000000000")
+
+ if actual != expected {
+ t.Fatalf("TestStartOfDay failed, %v=%v", expected, actual)
+ }
+}
+
+func TestEndOfDay(t *testing.T) {
+ input, _ := time.Parse(format, "2015-01-01 12:34:00.000000000")
+ actual := EndOfDay(input)
+ expected, _ := time.Parse(format, "2015-01-01 23:59:59.999999999")
+
+ if actual != expected {
+ t.Fatalf("TestEndOfDay failed, %v=%v", expected, actual)
+ }
+}
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index 5e374b877..9aa1a45b5 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -240,6 +240,7 @@ export default class PostsView extends React.Component {
this.updateScrolling();
}
window.addEventListener('resize', this.handleResize);
+ $(this.refs.postlist).perfectScrollbar();
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
@@ -248,6 +249,7 @@ export default class PostsView extends React.Component {
if (this.props.postList != null) {
this.updateScrolling();
}
+ $(this.refs.postlist).perfectScrollbar('update');
}
shouldComponentUpdate(nextProps) {
if (this.props.isActive !== nextProps.isActive) {
@@ -326,7 +328,7 @@ export default class PostsView extends React.Component {
return (
<div
ref='postlist'
- className={'post-list-holder-by-time ' + activeClass}
+ className={'ps-container post-list-holder-by-time ' + activeClass}
onScroll={this.handleScroll}
>
<div className='post-list__table'>
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 30422ff7d..b4c037183 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -173,6 +173,10 @@ export default class Sidebar extends React.Component {
this.updateScrollbar();
window.addEventListener('resize', this.handleResize);
+
+ if ($(window).width() > 768) {
+ $('.nav-pills__container').perfectScrollbar();
+ }
}
shouldComponentUpdate(nextProps, nextState) {
if (!Utils.areObjectsEqual(nextState, this.state)) {
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index 91f4b3bdc..2b505607e 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -424,23 +424,27 @@ export default class ViewImageModal extends React.Component {
>
<div
className={'image-wrapper ' + bgClass}
- onMouseEnter={this.onMouseEnterImage}
- onMouseLeave={this.onMouseLeaveImage}
- onClick={(e) => e.stopPropagation()}
+ onClick={this.props.onModalDismissed}
>
<div
- className={closeButtonClass}
- onClick={this.props.onModalDismissed}
- />
- {content}
- <ViewImagePopoverBar
- show={this.state.showFooter}
- fileId={this.state.imgId}
- totalFiles={this.props.filenames.length}
- filename={name}
- fileURL={fileUrl}
- getPublicLink={this.getPublicLink}
- />
+ onMouseEnter={this.onMouseEnterImage}
+ onMouseLeave={this.onMouseLeaveImage}
+ onClick={(e) => e.stopPropagation()}
+ >
+ <div
+ className={closeButtonClass}
+ onClick={this.props.onModalDismissed}
+ />
+ {content}
+ <ViewImagePopoverBar
+ show={this.state.showFooter}
+ fileId={this.state.imgId}
+ totalFiles={this.props.filenames.length}
+ filename={name}
+ fileURL={fileUrl}
+ getPublicLink={this.getPublicLink}
+ />
+ </div>
</div>
{leftArrow}
{rightArrow}
diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx
index b0ec64bfd..f2721c81d 100644
--- a/web/react/utils/markdown.jsx
+++ b/web/react/utils/markdown.jsx
@@ -223,6 +223,16 @@ class MattermostMarkdownRenderer extends marked.Renderer {
return `<table class="markdown__table"><thead>${header}</thead><tbody>${body}</tbody></table>`;
}
+ listitem(text) {
+ const taskListReg = /^\[([ |xX])\] /;
+ const isTaskList = taskListReg.exec(text);
+
+ if (isTaskList) {
+ return `<li>${'<input type="checkbox" disabled="disabled" ' + (isTaskList[1] === ' ' ? '' : 'checked="checked" ') + '/> '}${text.replace(taskListReg, '')}</li>`;
+ }
+ return `<li>${text}</li>`;
+ }
+
text(txt) {
return TextFormatting.doFormatText(txt, this.formattingOptions);
}
@@ -361,78 +371,78 @@ class MattermostLexer extends marked.Lexer {
// list
cap = this.rules.list.exec(src);
if (cap) {
+ src = src.substring(cap[0].length);
const bull = cap[2];
- let l = cap[0].length;
+
+ this.tokens.push({
+ type: 'list_start',
+ ordered: bull.length > 1
+ });
// Get each top-level item.
cap = cap[0].match(this.rules.item);
- if (cap.length > 1) {
- src = src.substring(l);
-
- this.tokens.push({
- type: 'list_start',
- ordered: bull.length > 1
- });
-
- let next = false;
- l = cap.length;
-
- for (let i = 0; i < l; i++) {
- let item = cap[i];
-
- // Remove the list item's bullet
- // so it is seen as the next token.
- let space = item.length;
- item = item.replace(/^ *([*+-]|\d+\.) +/, '');
-
- // Outdent whatever the
- // list item contains. Hacky.
- if (~item.indexOf('\n ')) {
- space -= item.length;
- item = this.options.pedantic ? item.replace(/^ {1,4}/gm, '') : item.replace(new RegExp('^ \{1,' + space + '\}', 'gm'), '');
- }
+ let next = false;
+ const l = cap.length;
+ let i = 0;
+
+ for (; i < l; i++) {
+ let item = cap[i];
+
+ // Remove the list item's bullet
+ // so it is seen as the next token.
+ let space = item.length;
+ item = item.replace(/^ *([*+-]|\d+\.) +/, '');
+
+ // Outdent whatever the
+ // list item contains. Hacky.
+ if (~item.indexOf('\n ')) {
+ space -= item.length;
+ item = this.options.pedantic ?
+ item.replace(/^ {1,4}/gm, '') :
+ item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '');
+ }
- // Determine whether the next list item belongs here.
- // Backpedal if it does not belong in this list.
- if (this.options.smartLists && i !== l - 1) {
- const bullet = /(?:[*+-]|\d+\.)/;
- const b = bullet.exec(cap[i + 1])[0];
- if (bull !== b && !(bull.length > 1 && b.length > 1)) {
- src = cap.slice(i + 1).join('\n') + src;
- i = l - 1;
- }
+ // Determine whether the next list item belongs here.
+ // Backpedal if it does not belong in this list.
+ if (this.options.smartLists && i !== l - 1) {
+ const b = this.rules.bullet.exec(cap[i + 1])[0];
+ if (bull !== b && !(bull.length > 1 && b.length > 1)) {
+ src = cap.slice(i + 1).join('\n') + src;
+ i = l - 1;
}
+ }
- // Determine whether item is loose or not.
- // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
- // for discount behavior.
- let loose = next || (/\n\n(?!\s*$)/).test(item);
- if (i !== l - 1) {
- next = item.charAt(item.length - 1) === '\n';
- if (!loose) {
- loose = next;
- }
+ // Determine whether item is loose or not.
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
+ // for discount behavior.
+ let loose = next || (/\n\n(?!\s*$)/).test(item);
+ if (i !== l - 1) {
+ next = item.charAt(item.length - 1) === '\n';
+ if (!loose) {
+ loose = next;
}
-
- this.tokens.push({
- type: loose ? 'loose_item_start' : 'list_item_start'
- });
-
- // Recurse.
- this.token(item, false, bq);
-
- this.tokens.push({
- type: 'list_item_end'
- });
}
this.tokens.push({
- type: 'list_end'
+ type: loose ?
+ 'loose_item_start' :
+ 'list_item_start'
});
- continue;
+ // Recurse.
+ this.token(item, false, bq);
+
+ this.tokens.push({
+ type: 'list_item_end'
+ });
}
+
+ this.tokens.push({
+ type: 'list_end'
+ });
+
+ continue;
}
// html
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 6270c8608..4a56bc6c7 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -3,6 +3,7 @@
}
.modal-body {
padding: 20px 15px;
+ overflow: auto;
}
.modal {
width: 100%;
@@ -47,9 +48,6 @@
margin-left: auto;
margin-right: auto;
}
- .modal-body {
- overflow: auto;
- }
.modal-push-down {
margin-top: 5%;
}
@@ -195,21 +193,35 @@
width:100%;
height: 100%;
margin: 0 auto;
+ max-width: 100%;
+
+ .modal-body {
+ @include clearfix;
+ height: 100%;
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ max-height: 100%;
+ }
+
.image-wrapper {
position: relative;
max-width: 90%;
- min-height: 100px;
- min-width: 320px;
@include border-radius(3px);
- display: table;
- margin: 0 auto;
+ display: table-cell;
+ vertical-align: middle;
+ text-align: center;
+ width: 100%;
+
&:hover {
@include border-radius(3px 3px 0 0);
}
+
&.default {
width: 100%;
height: 80%;
}
+
.modal-close {
background: url("../images/close.png") no-repeat;
@include background-size(100% 100%);
@@ -225,24 +237,31 @@
transition: opacity 0.6s;
cursor: pointer;
z-index: 9999;
+
&.modal-close--show {
@include opacity(1);
}
+
}
- > a {
+
+ > div {
+ min-height: 100px;
+ min-width: 320px;
background: #FFF;
- display: table-cell;
- vertical-align: middle;
+ display: inline-block;
position: relative;
&:hover .file-playback-controls.stop {
@include opacity(1);
}
+
}
+
img {
max-width: 100%;
max-height: 100%;
}
+
.spinner.file__loading {
z-index: 2;
position: absolute;
@@ -259,7 +278,6 @@
height: 100%;
padding: 0;
border: none;
- display: table;
}
.image-body {
vertical-align: middle;
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index da161e54f..ed1632681 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -374,9 +374,9 @@ body.ios {
ul {
margin: 0;
padding: 0;
- list-style: none;
}
+
p {
margin: 0 0 1em;
line-height: 1.6em;
@@ -603,6 +603,16 @@ body.ios {
padding: 5px 0 0 20px;
}
+ ul, ol {
+ li ul, li ol {
+ padding: 0 0 0 20px
+ }
+
+ li input[type="checkbox"]:disabled {
+ vertical-align: sub;
+ cursor: default;
+ }
+ }
}
.post__link {
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 00fa7d817..5f5cca89b 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -621,8 +621,10 @@
.modal {
.modal-image {
.image-wrapper {
- font-size: 12px;
- min-width: 250px;
+ > div {
+ font-size: 12px;
+ min-width: 250px;
+ }
.modal-close {
@include opacity(1);
}