mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-18 18:44:52 +08:00
262 lines
7.4 KiB
JavaScript
262 lines
7.4 KiB
JavaScript
import _ from 'underscore'
|
|
import Message from './message'
|
|
import Contact from './contact'
|
|
import Category from './category'
|
|
import Attributes from '../attributes'
|
|
import DatabaseStore from '../stores/database-store'
|
|
import ModelWithMetadata from './model-with-metadata'
|
|
|
|
|
|
/**
|
|
Public: The Thread model represents a Thread object served by the Nylas Platform API.
|
|
For more information about Threads on the Nylas Platform, read the
|
|
[Threads API Documentation](https://nylas.com/docs/api#threads)
|
|
|
|
Attributes
|
|
|
|
`snippet`: {AttributeString} A short, ~140 character string with the content
|
|
of the last message in the thread. Queryable.
|
|
|
|
`subject`: {AttributeString} The subject of the thread. Queryable.
|
|
|
|
`unread`: {AttributeBoolean} True if the thread is unread. Queryable.
|
|
|
|
`starred`: {AttributeBoolean} True if the thread is starred. Queryable.
|
|
|
|
`version`: {AttributeNumber} The version number of the thread.
|
|
|
|
`participants`: {AttributeCollection} A set of {Contact} models
|
|
representing the participants in the thread.
|
|
Note: Contacts on Threads do not have IDs.
|
|
|
|
`lastMessageReceivedTimestamp`: {AttributeDateTime} The timestamp of the
|
|
last message on the thread.
|
|
|
|
This class also inherits attributes from {Model}
|
|
|
|
Section: Models
|
|
@class Thread
|
|
*/
|
|
class Thread extends ModelWithMetadata {
|
|
|
|
static attributes = _.extend({}, ModelWithMetadata.attributes, {
|
|
snippet: Attributes.String({
|
|
modelKey: 'snippet',
|
|
}),
|
|
|
|
subject: Attributes.String({
|
|
queryable: true,
|
|
modelKey: 'subject',
|
|
}),
|
|
|
|
unread: Attributes.Boolean({
|
|
queryable: true,
|
|
modelKey: 'unread',
|
|
}),
|
|
|
|
starred: Attributes.Boolean({
|
|
queryable: true,
|
|
modelKey: 'starred',
|
|
}),
|
|
|
|
version: Attributes.Number({
|
|
queryable: true,
|
|
modelKey: 'version',
|
|
}),
|
|
|
|
categories: Attributes.Collection({
|
|
queryable: true,
|
|
modelKey: 'categories',
|
|
joinOnField: 'id',
|
|
joinQueryableBy: ['inAllMail', 'lastMessageReceivedTimestamp', 'lastMessageSentTimestamp', 'unread'],
|
|
itemClass: Category,
|
|
}),
|
|
|
|
categoriesType: Attributes.String({
|
|
modelKey: 'categoriesType',
|
|
}),
|
|
|
|
participants: Attributes.Collection({
|
|
queryable: true,
|
|
modelKey: 'participants',
|
|
joinOnField: 'email',
|
|
joinQueryableBy: ['lastMessageReceivedTimestamp'],
|
|
itemClass: Contact,
|
|
}),
|
|
|
|
hasAttachments: Attributes.Boolean({
|
|
modelKey: 'has_attachments',
|
|
}),
|
|
|
|
lastMessageReceivedTimestamp: Attributes.DateTime({
|
|
queryable: true,
|
|
modelKey: 'lastMessageReceivedTimestamp',
|
|
jsonKey: 'last_message_received_timestamp',
|
|
}),
|
|
|
|
lastMessageSentTimestamp: Attributes.DateTime({
|
|
queryable: true,
|
|
modelKey: 'lastMessageSentTimestamp',
|
|
jsonKey: 'last_message_sent_timestamp',
|
|
}),
|
|
|
|
inAllMail: Attributes.Boolean({
|
|
queryable: true,
|
|
modelKey: 'inAllMail',
|
|
jsonKey: 'in_all_mail',
|
|
}),
|
|
})
|
|
|
|
static naturalSortOrder = () => {
|
|
return Thread.attributes.lastMessageReceivedTimestamp.descending()
|
|
}
|
|
|
|
static additionalSQLiteConfig = {
|
|
setup: () => [
|
|
// ThreadCounts
|
|
'CREATE TABLE IF NOT EXISTS `ThreadCounts` (`category_id` TEXT PRIMARY KEY, `unread` INTEGER, `total` INTEGER)',
|
|
'CREATE UNIQUE INDEX IF NOT EXISTS ThreadCountsIndex ON `ThreadCounts` (category_id DESC)',
|
|
|
|
// ThreadContact
|
|
'CREATE INDEX IF NOT EXISTS ThreadContactDateIndex ON `ThreadContact` (last_message_received_timestamp DESC, value, id)',
|
|
|
|
// ThreadCategory
|
|
'CREATE INDEX IF NOT EXISTS ThreadListCategoryIndex ON `ThreadCategory` (last_message_received_timestamp DESC, value, in_all_mail, unread, id)',
|
|
'CREATE INDEX IF NOT EXISTS ThreadListCategorySentIndex ON `ThreadCategory` (last_message_sent_timestamp DESC, value, in_all_mail, unread, id)',
|
|
|
|
// Thread: General index
|
|
'CREATE INDEX IF NOT EXISTS ThreadDateIndex ON `Thread` (last_message_received_timestamp DESC)',
|
|
|
|
// Thread: Partial indexes for specific views
|
|
'CREATE INDEX IF NOT EXISTS ThreadUnreadIndex ON `Thread` (account_id, last_message_received_timestamp DESC) WHERE unread = 1 AND in_all_mail = 1',
|
|
'CREATE INDEX IF NOT EXISTS ThreadUnifiedUnreadIndex ON `Thread` (last_message_received_timestamp DESC) WHERE unread = 1 AND in_all_mail = 1',
|
|
|
|
'DROP INDEX IF EXISTS `Thread`.ThreadStarIndex',
|
|
'CREATE INDEX IF NOT EXISTS ThreadStarredIndex ON `Thread` (account_id, last_message_received_timestamp DESC) WHERE starred = 1 AND in_all_mail = 1',
|
|
'CREATE INDEX IF NOT EXISTS ThreadUnifiedStarredIndex ON `Thread` (last_message_received_timestamp DESC) WHERE starred = 1 AND in_all_mail = 1',
|
|
],
|
|
}
|
|
|
|
static searchable = true
|
|
|
|
static searchFields = ['subject', 'participants', 'body']
|
|
|
|
messages() {
|
|
return (
|
|
DatabaseStore.findAll(Message)
|
|
.where({threadId: this.id})
|
|
.include(Message.attributes.body)
|
|
)
|
|
}
|
|
|
|
get labels() {
|
|
return this.categories;
|
|
}
|
|
|
|
set labels(labels) {
|
|
this.categories = labels;
|
|
}
|
|
|
|
get folders() {
|
|
return this.categories;
|
|
}
|
|
|
|
set folders(folders) {
|
|
this.categories = folders;
|
|
}
|
|
|
|
get inAllMail() {
|
|
if (this.categoriesType === 'labels') {
|
|
const inAllMail = _.any(this.categories, cat => cat.name === 'all')
|
|
if (inAllMail) {
|
|
return true;
|
|
}
|
|
const inTrashOrSpam = _.any(this.categories, cat => cat.name === 'trash' || cat.name === 'spam')
|
|
if (!inTrashOrSpam) {
|
|
return true;
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
fromJSON(json) {
|
|
super.fromJSON(json)
|
|
|
|
if (json.folders) {
|
|
this.categoriesType = 'folders'
|
|
this.categories = Thread.attributes.categories.fromJSON(json.folders)
|
|
}
|
|
|
|
if (json.labels) {
|
|
this.categoriesType = 'labels'
|
|
this.categories = Thread.attributes.categories.fromJSON(json.labels)
|
|
}
|
|
|
|
['participants', 'categories'].forEach((attr) => {
|
|
const value = this[attr]
|
|
if (!(value && value instanceof Array)) {
|
|
return;
|
|
}
|
|
value.forEach((item) => {
|
|
item.accountId = this.accountId
|
|
})
|
|
})
|
|
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Public: Returns true if the thread has a {Category} with the given
|
|
* name. Note, only catgories of type `Category.Types.Standard` have valid
|
|
* `names`
|
|
* - `id` A {String} {Category} name
|
|
*/
|
|
categoryNamed(name) {
|
|
return _.findWhere(this.categories, {name})
|
|
}
|
|
|
|
sortedCategories() {
|
|
if (!this.categories) {
|
|
return []
|
|
}
|
|
let out = []
|
|
const isImportant = (l) => l.name === 'important'
|
|
const isStandardCategory = (l) => l.isStandardCategory()
|
|
const isUnhiddenStandardLabel = (l) => (
|
|
!isImportant(l) &&
|
|
isStandardCategory(l) &&
|
|
!(l.isHiddenCategory())
|
|
)
|
|
|
|
const importantLabel = _.find(this.categories, isImportant)
|
|
if (importantLabel) {
|
|
out = out.concat(importantLabel)
|
|
}
|
|
|
|
const standardLabels = _.filter(this.categories, isUnhiddenStandardLabel)
|
|
if (standardLabels.length > 0) {
|
|
out = out.concat(standardLabels)
|
|
}
|
|
|
|
const userLabels = _.filter(this.categories, (l) => (
|
|
!isImportant(l) && !isStandardCategory(l)
|
|
))
|
|
if (userLabels.length > 0) {
|
|
out = out.concat(_.sortBy(userLabels, 'displayName'))
|
|
}
|
|
return out
|
|
}
|
|
}
|
|
|
|
Object.defineProperty(Thread.attributes, "labels", {
|
|
enumerable: false,
|
|
get: () => Thread.attributes.categories,
|
|
})
|
|
|
|
Object.defineProperty(Thread.attributes, "folders", {
|
|
enumerable: false,
|
|
get: () => Thread.attributes.categories,
|
|
})
|
|
|
|
export default Thread
|