Skip to content
Snippets Groups Projects
Commit 2f8c637f authored by Gabriel Engel's avatar Gabriel Engel Committed by GitHub
Browse files

Merge pull request #4623 from claranet/snippet-message

Snippet message
parents 395ef957 be4b5f7a
No related branches found
No related tags found
No related merge requests found
Showing
with 439 additions and 0 deletions
...@@ -122,6 +122,7 @@ rocketchat:version ...@@ -122,6 +122,7 @@ rocketchat:version
rocketchat:videobridge rocketchat:videobridge
rocketchat:webrtc rocketchat:webrtc
rocketchat:wordpress rocketchat:wordpress
rocketchat:message-snippet
#rocketchat:chatops #rocketchat:chatops
konecty:change-case konecty:change-case
......
...@@ -169,6 +169,7 @@ rocketchat:mentions-flextab@0.0.1 ...@@ -169,6 +169,7 @@ rocketchat:mentions-flextab@0.0.1
rocketchat:message-attachments@0.0.1 rocketchat:message-attachments@0.0.1
rocketchat:message-mark-as-unread@0.0.1 rocketchat:message-mark-as-unread@0.0.1
rocketchat:message-pin@0.0.1 rocketchat:message-pin@0.0.1
rocketchat:message-snippet@0.0.1
rocketchat:message-star@0.0.1 rocketchat:message-star@0.0.1
rocketchat:migrations@0.0.1 rocketchat:migrations@0.0.1
rocketchat:oauth2-server@1.4.0 rocketchat:oauth2-server@1.4.0
......
...@@ -352,6 +352,7 @@ ...@@ -352,6 +352,7 @@
"Do_you_want_to_change_to_s_question": "Do you want to change to <strong>%s</strong>?", "Do_you_want_to_change_to_s_question": "Do you want to change to <strong>%s</strong>?",
"Domain": "Domain", "Domain": "Domain",
"Domains": "Domains", "Domains": "Domains",
"Download_Snippet": "Download",
"Drop_to_upload_file": "Drop to upload file", "Drop_to_upload_file": "Drop to upload file",
"Dry_run": "Dry run", "Dry_run": "Dry run",
"Dry_run_description": "Will only send one email, to the same address as in From. The email must belong to a valid user.", "Dry_run_description": "Will only send one email, to the same address as in From. The email must belong to a valid user.",
...@@ -800,6 +801,7 @@ ...@@ -800,6 +801,7 @@
"Message_AllowEditing_BlockEditInMinutesDescription": "Enter 0 to disable blocking.", "Message_AllowEditing_BlockEditInMinutesDescription": "Enter 0 to disable blocking.",
"Message_AllowPinning": "Allow Message Pinning", "Message_AllowPinning": "Allow Message Pinning",
"Message_AllowPinning_Description": "Allow messages to be pinned to any of the channels.", "Message_AllowPinning_Description": "Allow messages to be pinned to any of the channels.",
"Message_AllowSnippeting": "Allow Message Snippeting",
"Message_AllowStarring": "Allow Message Starring", "Message_AllowStarring": "Allow Message Starring",
"Message_AllowUnrecognizedSlashCommand": "Allow Unrecognized Slash Commands", "Message_AllowUnrecognizedSlashCommand": "Allow Unrecognized Slash Commands",
"Message_AlwaysSearchRegExp": "Always search using RegExp", "Message_AlwaysSearchRegExp": "Always search using RegExp",
...@@ -888,6 +890,7 @@ ...@@ -888,6 +890,7 @@
"No_results_found": "No results found", "No_results_found": "No results found",
"No_starred_messages": "No starred messages", "No_starred_messages": "No starred messages",
"No_such_command": "No such command: `/__command__`", "No_such_command": "No such command: `/__command__`",
"No_snippet_messages": "No snippet",
"No_user_with_username_%s_was_found": "No user with username <strong>\"%s\"</strong> was found!", "No_user_with_username_%s_was_found": "No user with username <strong>\"%s\"</strong> was found!",
"Nobody_available": "Nobody available", "Nobody_available": "Nobody available",
"Node_version": "Node version", "Node_version": "Node version",
...@@ -1182,6 +1185,9 @@ ...@@ -1182,6 +1185,9 @@
"SMTP_Port": "SMTP Port", "SMTP_Port": "SMTP Port",
"SMTP_Test_Button": "Test SMTP Settings", "SMTP_Test_Button": "Test SMTP Settings",
"SMTP_Username": "SMTP Username", "SMTP_Username": "SMTP Username",
"Snippet_Added": "Created on %s",
"Snippet_Messages": "Snippet Messages",
"Snippeted_a_message": "Created a snippet __snippetLink__",
"Sound": "Sound", "Sound": "Sound",
"SSL": "SSL", "SSL": "SSL",
"Star_Message": "Star Message", "Star_Message": "Star Message",
......
...@@ -13,6 +13,7 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base ...@@ -13,6 +13,7 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base
@tryEnsureIndex { 'file._id': 1 }, { sparse: 1 } @tryEnsureIndex { 'file._id': 1 }, { sparse: 1 }
@tryEnsureIndex { 'mentions.username': 1 }, { sparse: 1 } @tryEnsureIndex { 'mentions.username': 1 }, { sparse: 1 }
@tryEnsureIndex { 'pinned': 1 }, { sparse: 1 } @tryEnsureIndex { 'pinned': 1 }, { sparse: 1 }
@tryEnsureIndex { 'snippeted': 1 }, { sparse: 1 }
@tryEnsureIndex { 'location': '2dsphere' } @tryEnsureIndex { 'location': '2dsphere' }
...@@ -157,6 +158,14 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base ...@@ -157,6 +158,14 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base
return @find query, options return @find query, options
findSnippetedByRoom: (roomId, options) ->
query =
_hidden: { $ne: true }
snippeted: true
rid: roomId
return @find query, options
getLastTimestamp: (options = {}) -> getLastTimestamp: (options = {}) ->
query = { ts: { $exists: 1 } } query = { ts: { $exists: 1 } }
options.sort = { ts: -1 } options.sort = { ts: -1 }
...@@ -225,6 +234,22 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base ...@@ -225,6 +234,22 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base
return @update query, update return @update query, update
setSnippetedByIdAndUserId: (message, snippetName, snippetedBy, snippeted=true, snippetedAt=0) ->
query =
_id: message._id
msg = "```" + message.msg + "```"
update =
$set:
msg: msg
snippeted: snippeted
snippetedAt: snippetedAt || new Date
snippetedBy: snippetedBy
snippetName: snippetName
return @update query, update
setUrlsById: (_id, urls) -> setUrlsById: (_id, urls) ->
query = query =
_id: _id _id: _id
......
node_modules
This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.
You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.
{
"dependencies": {
"mime-db": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz",
"from": "mime-db@>=1.23.0 <1.24.0"
},
"mime-types": {
"version": "2.1.11",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz",
"from": "mime-types@2.1.11"
}
}
}
Meteor.startup(function() {
RocketChat.MessageAction.addButton({
id: 'snippeted-message',
icon: 'icon-code',
i18nLabel: 'Snippet',
context: [
'snippeted',
'message',
'message-mobile'
],
action: function() {
let message = this._arguments[1];
swal({
title: 'Create a Snippet',
text: 'The name of your snippet (with file extension):',
type: 'input',
showCancelButton: true,
closeOnConfirm: false,
animation: 'slide-from-top',
inputPlaceholder: 'Snippet name'
}, function(filename) {
if (filename === false) {
return false;
}
if (filename === '') {
swal.showInputError('You need to write something!');
return false;
}
message.snippeted = true;
Meteor.call('snippetMessage', message, filename, function(error) {
if (error) {
return handleError(error);
}
swal('Nice!', `Snippet '${filename}' created.`, 'success');
});
});
},
validation: function(message) {
if (RocketChat.models.Subscriptions.findOne({ rid: message.rid, 'u._id': Meteor.userId() }) === undefined) {
return false;
}
if (message.snippeted || ((RocketChat.settings.get('Message_AllowSnippeting') === undefined) ||
(RocketChat.settings.get('Message_AllowSnippeting') === null) ||
(RocketChat.settings.get('Message_AllowSnippeting')) === false)) {
return false;
}
return RocketChat.authz.hasAtLeastOnePermission('snippet-message', message.rid);
}
});
});
this.SnippetedMessages = new Meteor.Collection('rocketchat_snippeted_message');
Meteor.startup(function() {
RocketChat.MessageTypes.registerType({
id: 'message_snippeted',
system: true,
message: 'Snippeted_a_message',
data: function(message) {
let snippetLink = `<a href="/snippet/${message.snippetId}/${message.snippetName}">${message.snippetName}</a>`;
return { snippetLink: snippetLink };
}
});
});
<template name="snippetPage">
<div id="{{_id}}" class="snippet-page {{t}} {{own}} {{isTemp}}" data-username="{{u.username}}" data-groupable="{{isGroupable}}" data-date="{{date}}" data-timestamp="{{timestamp}}">
<div class="snippet-informations">
{{> avatar username=snippet.u.username}}
<span class="username">{{snippet.u.username}}</span>
<span class="snippet-filename">{{ snippet.snippetName }}</span>
</div>
<span class="info"> {{_ "Snippet_Added" date }}</span>
<a class="download-button" target="_blank" href="/snippet/download/{{snippet._id}}/{{snippet.snippetName}}">
<i class="icon-download"></i>
{{_ "Download_Snippet" }}
</a>
<div class="body" dir="auto">
{{{ snippetContent }}}
</div>
</div>
</template>
/* global SnippetedMessages */
Template.snippetPage.helpers({
snippet: function() {
return SnippetedMessages.findOne({ _id: FlowRouter.getParam('snippetId') });
},
snippetContent: function() {
let message = SnippetedMessages.findOne({ _id: FlowRouter.getParam('snippetId') });
if (message === undefined) {
return null;
}
message.html = message.msg;
let markdownCode = new RocketChat.MarkdownCode(message);
return markdownCode.tokens[0].text;
},
date() {
let snippet = SnippetedMessages.findOne({ _id: FlowRouter.getParam('snippetId') });
if (snippet !== undefined) {
return moment(snippet.ts).format(RocketChat.settings.get('Message_DateFormat'));
}
},
time() {
let snippet = SnippetedMessages.findOne({ _id: FlowRouter.getParam('snippetId') });
if (snippet !== undefined) {
return moment(snippet.ts).format(RocketChat.settings.get('Message_TimeFormat'));
}
}
});
Template.snippetPage.onCreated(function() {
let snippetId = FlowRouter.getParam('snippetId');
this.autorun(function() {
Meteor.subscribe('snippetedMessage', snippetId);
});
});
@snippet-page-left-border-size: 30px;
@snippet-page-top-border-size: 20px;
@snippet-informations-height: 40px;
@snippet-page-right-border-size: 30px;
@snippet-informations-span-line-height: @snippet-informations-height / 2;
.snippet-page {
padding-top: @snippet-page-top-border-size;
padding-left: @snippet-page-left-border-size;
padding-right: @snippet-page-right-border-size;
overflow-y: auto;
overflow-x: hidden;
height: 100%;
width: auto;
pre code, h1, span {
-webkit-user-select: text;
-khtml-user-select: all;
-moz-user-select: all;
-ms-user-select: all;
user-select: all;
}
.snippet-informations {
display:inline-block;
width: 100%;
height: 60px;
div.avatar {
display: block;
float:left;
clear:left;
width: @snippet-informations-height;
height:@snippet-informations-height;
margin-right: 10px;
}
span.username {
display: block;
line-height: @snippet-informations-span-line-height;
width: 100%;
}
span.snippet-filename {
display:block;
line-height: @snippet-informations-span-line-height;
font-weight: bold;
width:100%;
}
}
span.info {
font-style: italic;
line-height: @snippet-informations-span-line-height;
color: darkgrey;
}
.download-button {
float: right;
}
h1 {
}
}
/* global FlowRouter, BlazeLayout */
FlowRouter.route('/snippet/:snippetId/:snippetName', {
name: 'snippetView',
action: function() {
BlazeLayout.render('main', {center: 'snippetPage', flexTabBar: null });
},
triggersEnter: [ function() {
RocketChat.TabBar.closeFlex();
RocketChat.TabBar.hide();
}],
triggersExit: [
function() {
RocketChat.TabBar.show();
}
]
});
Meteor.methods({
snippetMessage: function(message) {
if (typeof Meteor.userId() === 'undefined' || Meteor.userId() === null) {
return false;
}
if ((typeof RocketChat.settings.get('Message_AllowSnippeting') === 'undefined') ||
(RocketChat.settings.get('Message_AllowSnippeting') === null) ||
(RocketChat.settings.get('Message_AllowSnippeting') === false)) {
return false;
}
let subscription = RocketChat.models.Subscriptions.findOne({ rid: message.rid, 'u._id': Meteor.userId() });
if (subscription === undefined) {
return false;
}
ChatMessage.update({
_id: message._id
}, {
$set: {
snippeted: true
}
});
}
});
Meteor.startup(function() {
Tracker.autorun(function() {
if (RocketChat.settings.get('Message_AllowSnippeting')) {
RocketChat.TabBar.addButton({
groups: ['channel', 'privategroup', 'directmessages'],
id: 'snippeted-messages',
i18nTitle: 'Snippeted_Messages',
icon: 'icon-code',
template: 'snippetedMessages',
order: 20
});
} else {
RocketChat.TabBar.removeButton('snippeted-messages');
}
});
});
<template name="snippetMessage">
<li id="{{_id}}" class="message {{isSequential}} {{system}} {{t}} {{own}} {{isTemp}} {{chatops}} {{customClass}}" data-username="{{u.username}}" data-groupable="{{isGroupable}}" data-date="{{date}}" data-timestamp="{{timestamp}}">
<div class="day-divider">
<span>{{date}}</span>
</div>
{{#if avatar}}
{{#if avatarFromUsername}}
<button class="thumb user-card-message" data-username="{{u.username}}" tabindex="1">{{> avatar username=avatarFromUsername}}</button>
{{else}}
<button class="thumb user-card-message" data-username="{{u.username}}" tabindex="1">
<div class="avatar">
<div class="avatar-image" style="background-image:url({{avatar}});"></div>
</div>
</button>
{{/if}}
{{else}}
{{#if emoji}}
<button class="thumb user-card-message" data-username="{{u.username}}" tabindex="1">
<div class="avatar">
{{{getEmoji emoji}}}
</div>
</button>
{{else}}
<button class="thumb user-card-message" data-username="{{u.username}}" tabindex="1">{{> avatar username=u.username}}</button>
{{/if}}
{{/if}}
{{#if alias}}
<button type="button" class="user user-card-message" data-username="{{u.username}}" tabindex="1">{{alias}} <span class="message-alias">@{{u.username}}</span></button>
{{else}}
<button type="button" class="user user-card-message" data-username="{{u.username}}" tabindex="1">{{u.username}}</button>
{{/if}}
<span class="info">
{{#each roleTags}}
<span class="role-tag" data-role="{{description}}">{{description}}</span>
{{/each}}
{{#if isBot}}
<span class="is-bot">BOT</span>
{{/if}}
<span class="time" title='{{date}} {{time}}'>{{time}}</span>
{{#if edited}}
<span class="edited" title='{{_ "edited"}} {{_ "at"}} {{editTime}} {{_ "by"}} {{editedBy}}'>
<i class="icon-edit" aria-label="{{_ "Edited"}}"></i>
<button class="thumb thumb-small user-card-message" data-username="{{editedBy}}" tabindex="1">{{> avatar username=editedBy}}</button>
</span>
{{/if}}
{{#if private}}
<span class="private">{{_ "Only_you_can_see_this_message"}}</span>
{{/if}}
<div class="message-cog-container {{hideCog}}">
<i class="icon-cog message-cog" aria-label="{{_ "Actions"}}"></i>
</div>
</span>
<div class="body" dir="auto">
{{{body}}}
{{#if hasOembed}}
{{#each urls}}
{{injectIndex . @index}} {{> oembedBaseWidget}}
{{/each}}
{{/if}}
{{#each attachments}}
{{injectIndex . @index}} {{> messageAttachment}}
{{/each}}
</div>
</li>
</template>
Template.snippetMessage.helpers({
time: function() {
return moment(this.ts).format(RocketChat.settings.get('Message_TimeFormat'));
},
date: function() {
return moment(this.ts).format(RocketChat.settings.get('Message_DateFormat'));
},
own: function() {
if (this.u !== undefined && this.u && this.u._id === Meteor.userId()) {
return 'own';
}
},
body: function() {
return `<a href="/snippet/${this._id}/${this.snippetName}">${this.snippetName}</a>`;
}
});
<template name="snippetedMessages">
<div class="content">
<div class="list-view pinned-messages-list">
<div class="title">
<h2>{{_ "Snippet_Messages"}}</h2>
</div>
{{#if Template.subscriptionsReady}}
{{#unless hasMessages}}
<h2>{{_ "No_snippet_messages"}}</h2>
{{/unless}}
{{/if}}
</div>
<ul class="list clearfix">
{{#each messages}}
{{#nrr nrrargs 'snippetMessage' message}}{{/nrr}}
{{/each}}
{{#if hasMore}}
<li class="load-more">
{{#if Template.subscriptionsReady}}
<button>{{_ "Has_more"}}...</button>
{{else}}
<div class="load-more-loading">{{_ "Loading..."}}</div>
{{/if}}
</li>
{{/if}}
</ul>
</div>
</template>
/* global SnippetedMessages */
Template.snippetedMessages.helpers({
hasMessages() {
return SnippetedMessages.find({ snippeted:true, rid: this.rid }, { sort: { ts: -1 } }).count() > 0;
},
messages() {
return SnippetedMessages.find({ snippeted: true, rid: this.rid }, { sort: { ts: -1 } });
},
message() {
return _.extend(this, { customClass: 'snippeted' });
},
hasMore() {
return Template.instance().hasMore.get();
}
});
Template.snippetedMessages.onCreated(function() {
this.hasMore = new ReactiveVar(true);
this.limit = new ReactiveVar(50);
let self = this;
this.autorun(function() {
let data = Template.currentData();
self.subscribe('snippetedMessages', data.rid, self.limit.get(), function() {
if (SnippetedMessages.find({ snippeted: true, rid: data.rid }).count() < self.limit.get()) {
return self.hasMore.set(false);
}
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment