From vtk88com, 1 Week ago, written in Plain Text.
This paste is a reply to Untitled from vtk88com
- go back
Embed
Viewing differences between Untitled and Untitled
vtk88.com l� nh� c�i tr?c tuy?n uy t�n h�ng ??u Vi?t Nam, ho?t ??ng t? n?m 2012 v?i s? h? tr? ??c quy?n t? CLB Manchester United. ???c ?y quy?n b?i C�ng ty Gi?i tr� First Cagayan, TK88 cung c?p danh m?c tr� ch?i phong ph�, bao g?m c� c??c th? thao, casino tr?c tuy?n, x? s?, game b�i v� slot games h?p d?n.
??i ng? h? tr? chuy�n nghi?p ho?t ??ng 24/7, ??m b?o gi?i ?�p m?i th?c m?c nhanh ch�ng v� t?n t�m. V?i c�ng ngh? ti�n ti?n v� m�i tr??ng c� c??c hi?n ??i, vtk88 mang ??n tr?i nghi?m gi?i tr� ch?t l??ng cao, 
define('core/models/Post',[
    'jquery',
    'underscore',
    'backbone',
    'moment',

    'core/config/urls',

    'core/api',
    'core/strings',
    'core/time',
    'core/utils',
    'core/utils/html',
    'core/advice',
    'remote/config',

    'core/models/mixins',

    'core/collections/VotersUserCollection',
    'core/collections/VoteCollection',
], function (
    $,
    _,
    Backbone,
    moment,

    urls,

    api,
    strings,
    time,
    utils,
    htmlUtils,
    advice,
    config,

    modelMixins,

    VotersUserCollection,
    VoteCollection
) {
    'use strict';

    var VOTE_RATE_LIMIT = 1000; // 1s rate limit on voting
    var LAST_VOTE = 0;
    var MAX_POST_LENGTH = 25000;

    var acquireVoteLock = function () {
        var now = $.now();
        if (now - LAST_VOTE < VOTE_RATE_LIMIT) {
            return false;
        }
        LAST_VOTE = now;
        return true;
    };

    var gettext = strings.get;

    var Post = Backbone.Model.extend({
        votersCollectionClass: VotersUserCollection,

        defaults: function () {
            return {
                createdAt: moment().format(time.ISO_8601),
                editableUntil: moment().add(config.max_post_edit_days, 'days').format(time.ISO_8601),
                dislikes: 0,
                isApproved: true, // Assume approved
                isDeleted: false,
                isEdited: false,
                isFlagged: false,
                isFlaggedByUser: false,
                isHighlighted: false,
                isRealtime: false,
                isImmediateReply: false,
                isMinimized: null,
                hasMedia: false,
                message: null,
                raw_message: null,
                likes: 0,
                media: [],
                parent: null,
                points: 0,
                depth: 0,
                userScore: 0,
                rating: null,
            };
        },

        initialize: function () {
            this.votes = new VoteCollection();
        },

        messageText: function () {
            var msg = this.get('message');
            return msg && htmlUtils.stripTags(msg);
        },

        permalink: function (thread, useCurrentPage) {
            var id = this.id;
            if (!(id && thread)) {
                return '';
            }
            // Favor currentUrl if useCurrentPage is not false and always fallback to permalink
            var baseUrl = useCurrentPage !== false &&  // default to true
                          thread.currentUrl || thread.permalink();
            var urlTool = window.document.createElement('a');
            urlTool.href = baseUrl;
            urlTool.hash = '#comment-' + id;

            return urlTool.href;
        },

        shortLink: function () {
            return urls.shortener + '/p/' + Number(this.id).toString(36);
        },

        twitterText: function (permalink) {
            var charsRemaining = 140;

            var author = this.author.get('name') || this.author.get('username');
            charsRemaining -= author.length + 3;  // +3 for space dash space before author
            charsRemaining -= permalink.length + 1;  // +1 for space before url
            charsRemaining -= 2;  // For the quotation marks

            var text = utils.niceTruncate(this.messageText(), charsRemaining);

            return '"' + text + '" \u2014 ' + author;
        },

        toJSON: function (options) {
            var json = Backbone.Model.prototype.toJSON.call(this);
            if (options) {
                var session = options.session;
                var thread = options.thread;

                json.canBeEdited = this.canBeEdited(session, thread);
                json.canBeRepliedTo = this.canBeRepliedTo(session, thread);
                json.canBeShared = this.canBeShared();
                json.permalink = this.permalink(thread);
            }

            json.shortLink = this.shortLink();
            json.isMinimized = this.isMinimized();
            json.plaintext = this.messageText();

            // Add relativeCreatedAt for display in the UI (e.g., "Comment created 1 hour ago")
            json.relativeCreatedAt = this.getRelativeCreatedAt();
            // formattedCreatedAt is nice absolute time (e.g. "Jan 1, 2008, 12:00 pm")
            json.formattedCreatedAt = this.getFormattedCreatedAt();
            // expose cid to make it easier to find the model in collections later
            json.cid = this.cid;

            return json;
        },

        /**
         * Is this post visible to all users?
         * @returns {boolean}
         */
        isPublic: function () {

            // Posts that are highlighted or sponsored are always public.
            if (this.get('isHighlighted') || this.get('isSponsored')) {
                return true;
            }
            // Otherwise, if they are deleted they are definitely *not* public.
            if (this.get('isDeleted')) {
                return false;
            }
            // Finally, if they are approved then they are public.
            return this.get('isApproved');
        },

        isMinimized: function () {
            // If comment has been highlighted by a moderator, it is NOT minimized
            // (highlighting is implicit approval by the moderator)
            if (this.get('isHighlighted')) {
                return false;
            }
            // If the comment has been explicitly expanded (unminimized) by
            // the user, it is NOT minimized
            if (this.get('isMinimized') === false) {
                return false;
            }
            return !this.get('isApproved');
        },

        // These methods are stubbed out because they rely on the Session object,
        // which hasn't been ported to next-core yet. They still exist, and return
        // sensible default values, because they're called from Post.prototype.toJSON.
        //
        // NOTE: Until Session is ported to next-core, any applications using Post w/
        //       sessions will need to override these prototype methods

        isAuthorSessionUser: function () { return false; },

        canBeEdited: function () { return false; },

        canBeRepliedTo: function () { return false; },

        canBeShared: function () { return false; },

        validateMessage: function (attrs) {
            if (_.isString(attrs.raw_message)) {
                if (attrs.raw_message === '') {
                    return gettext('Comments can\'t be blank.');
                }
                // eslint-disable-next-line no-magic-numbers
                if (attrs.raw_message.length < 2) {
                    return gettext('Comments must have at least 2 characters.');
                }
                if (attrs.raw_message.length > MAX_POST_LENGTH) {
                    return strings.interpolate(gettext('Comments can\'t be longer than %(maxLength)s characters (currently %(currentLength)s characters).'), {
                        maxLength: MAX_POST_LENGTH,
                        currentLength: attrs.raw_message.length,
                    });
                }
            }
        },

        validate: function (attrs) {
            if (this.id || attrs.id) {
                return;
            }
            var messageError = this.validateMessage(attrs);
            if (messageError) {
                return messageError;
            }
            if (attrs.author_email) {
                attrs.author_email = $.trim(attrs.author_email);
            }
            if (attrs.author_name) {
                attrs.author_name = $.trim(attrs.author_name);
            }
            if (attrs.author_email === '' && attrs.author_name === '') {
                return gettext('Please sign in or enter a name and email address.');
            }
            else if (attrs.author_email === '' || attrs.author_name === '') {
                return gettext('Please enter both a name and email address.');
            }
            else if (attrs.author_name && attrs.author_name.length && !attrs.age_declaration) {
                return gettext('Please confirm that you are 18 years of age or older.');
            }

            // Simple client-side email validation. This is obviously not a complete
            // regex, but it should catch 90+% of errors. We can catch the rest on
            // the server.
            if (_.isString(attrs.author_email) && !this.validateEmail(attrs.author_email)) {
                return gettext('Invalid email address format.');
            }
        },

        validateEmail: function (email) {
            return utils.validateEmail(email);
        },

        report: function (reason) {
            this.set('isFlagged', true);

            var data = {
                post: this.id,
            };

            if (reason) {
                data.reason = reason;
            }
            api.call('posts/report.json', {
                data: data,
                method: 'POST',
            });
        },

        _highlight: function (shouldBeHighlighted) {
            this.set('isHighlighted', shouldBeHighlighted);
            api.call('posts/' + (shouldBeHighlighted ? 'highlight' : 'unhighlight') + '.json', {
                data: {
                    post: this.id,
                },
                method: 'POST',
            });
        },

        highlight: function () {
            this._highlight(true);
        },

        unhighlight: function () {
            this._highlight(false);
        },

        /**
         * Override for post models that don't store the thread id
         * in the thread attr
         *
         * @returns {string} id of thread.
         */
        getThreadId: function () {
            return this.get('thread');
        },

        getUpvotersUserCollection: _.memoize(function () {
            var CollectionClass = this.votersCollectionClass;

            return new CollectionClass(undefined, {
                postId: this.id,
                threadId: this.getThreadId(),
            });
        }, function () {
            return [this.id, '1'].join('');
        }),

        getDownvotersUserCollection: _.memoize(function () {
            var CollectionClass = this.votersCollectionClass;

            return new CollectionClass(undefined, {
                postId: this.id,
                threadId: this.getThreadId(),
            });
        }, function () {
            return [this.id, '-1'].join('');
        }),

        // This function does score calculations and increments/decrements
        // counters. We can't just bind it to the 'add' event because
        // we want to call _vote before making a network request and adding
        // a freshly created vote to a collection. (sad face)

        _vote: function (vote, score, voter) {
            var delta = vote - score;
            var updates = {
                likes: this.get('likes'),
                dislikes: this.get('dislikes'),
                points: this.get('points'),
            };

            // If the vote is unchanged from the user's previous vote,
            // so ignore - there's nothing to do

            if (delta === 0) {
                return delta;
            }
            // Update likes and dislikes. This is tricky because a
            // switched vote requires updating *both* attributes, whereas
            // a new vote only touches one.

            if (vote > 0) {
                updates.likes += vote;
                updates.dislikes += score;
            } else if (vote < 0) {
                updates.dislikes -= vote;
                updates.likes -= score;
            } else if (score > 0) {
                updates.likes -= score;
            } else {
                updates.dislikes += score;
            }

            updates.points += delta;

            // add user into correct collection and remove from the other one
            if (voter) {
                if (vote === 1) {
                    this.getUpvotersUserCollection().add(voter);
                    this.getDownvotersUserCollection().remove(voter);
                } else {
                    this.getDownvotersUserCollection().add(voter);
                    this.getUpvotersUserCollection().remove(voter);
                }
            }


            // All these values should be updated at once to prevent any "half-baked"
            // data to trigger the view or any other thing that listens on change
            // events of these properties.
            this.set(updates);

            return delta;
        },

        // Vote on a post, taking into account any earlier votes
        // made by the current user (via the userScore attribute).
        //
        // @params vote {Integer} Can be one of -1, 0, or 1

        vote: function (vote) {
            // Rate limit voting on the Post's public vote interface.
            // We don't rate limit on the private interface because that
            // is used for realtime votes which could come in faster than
            // the rate limit.
            if (!acquireVoteLock()) {
                return 0;
            }
            var self = this;
            var delta = self._vote(vote, self.get('userScore'));

            if (delta === 0) {
                return;
            }

            var newNumLikesReceived = self.author ? self.author.get('numLikesReceived') : 0;
            if (self.get('userScore') === 1) {
                newNumLikesReceived -= 1;
            } else if (vote === 1) {
                newNumLikesReceived += 1;
            }

            self.set('userScore', vote);
            api.call('posts/vote.json', {
                data: {
                    post: self.id,
                    vote: vote,
                },
                method: 'POST',
                success: function (data) {
                    // We have merge:true in order to update existing vote instances,
                    // so when the same vote will come from realtime it won't be recognized
                    // as a change.
                    self.votes.add({ id: data.response.id, score: vote }, { merge: true });

                    if (self.author) {
                        self.author.set('numLikesReceived', newNumLikesReceived);
                    }
                },
            });
        },

        _delete: function () {
            this.set({
                isApproved: false,
                isDeleted: true,
            });

            return api.call('posts/remove.json', {
                data: { post: this.id },
                method: 'POST',
            });
        },

        spam: function () {
            this.set({
                isApproved: false,
                isDeleted: true,
                isSpam: true,
            });

            this.trigger('spam');

            api.call('posts/spam.json', {
                data: { post: this.id },
                method: 'POST',
            });
        },

        _create: function (model, options) {
            var self = this;
            var attrs = model.attributes;
            var params = {
                thread: attrs.thread,
                message: attrs.raw_message,
                rating: attrs.rating,
            };

            if (attrs.parent) {
                params.parent = attrs.parent;
            }
            // Anon posting
            if (attrs.author_name) {
                params.author_name = attrs.author_name;
                params.author_email = attrs.author_email;
                params.age_declaration = attrs.age_declaration;
            }

            return api.call('posts/create.json', {
                data: params,
                method: 'POST',
                success: function (data) {
                    self.set(data.response);
                    if (options.success) {
                        options.success();
                    }
                },
                error: options.error,
            });
        },

        _update: function (model, options) {
            var self = this;
            var attrs = model.attributes;

            var params = {
                post: attrs.id,
                message: attrs.raw_message,
                rating: attrs.rating,
            };


            return api.call('posts/update.json', {
                data: params,
                method: 'POST',
                success: function (data) {
                    self.set(data.response);
                    if (options.success) {
                        options.success();
                    }
                },
                error: options.error,
            });
        },

        _read: function (_model, options) {
            var self = this;
            options = options || {};

            return api.call('posts/details.json', {
                data: { post: self.id },
                method: 'GET',
                success: function (data) {
                    // When highlighting and unhighlighting posts in quick succession,
                    // sometimes we send a cached version of the model that is unhighlighted.
                    // If that happens, we fix that here in the response
                    if (options.isHighlighted && !data.response.isHighlighted) {
                        data.response.isHighlighted = true;
                    }
                    self.set(data.response);
                    if (options.success) {
                        options.success();
                    }
                },
                error: options.error,
            });
        },

        sync: function (method, model, options) {
            options = options || {};

            var error = options.error;
            if (error) {
                options.error = function (xhr) {
                    var response = {};
                    try {
                        response = JSON.parse(xhr.responseText);
                    } catch (err) {
                        // ignore
                    }

                    error(response);
                };
            }

            switch (method) {
            case 'create':
                return this._create(model, options);
            case 'update':
                return this._update(model, options);
            case 'delete':
                return this._delete();
            case 'read':
                return this._read(model, options);
            default:
                return null;
            }
        },

        /**
         * Generate key for storing draft posts in local storage.
         * A draft post is one that has not yet been saved on the back
         * end.
         *
         * Example: drafts:thread:1:parent:0
         *
         * @returns {string} key name
         */
        storageKey: function () {
            // Only allow draft posts to be stored in local storage
            if (!this.isNew()) {
                return;
            }
            if (!this.getThreadId()) {
                return;
            }
            return ['drafts', 'thread', this.getThreadId(), 'parent', this.get('parent') || 0].join(':');
        },
    }, {
        /**
         * Get 
an to�n v� ?�ng tin c?y.
Tham gia vtk88.com ?? t?n h??ng nh?ng kho?nh kh?c th? gi�n ??nh cao c�ng c�c d?ch v? h�ng ??u!
#vtk88 #TK88
??a ch? : 59 ???ng D1, M? Ph??c, B?n C�t, B�nh D??ng 825530, Vietnam
Hotline : 0912694050
Website TK88: https://vtk88.com/ 

SOCIAL 
https://www.instagram.com/vtk88com
https://www.pinterest.com/vtk88com
https://www.twitch.tv/vtk88com
https://twitter.com/vtk88com
https://www.youtube.com/@vtk88com
https://pt.3dexport.com/vtk88com
https://500px.com/p/vtk88com
https://www.linkedin.com/in/vtk88com
https://www.facebook.com/vtk88com
https://sites.google.com/view/vtk88com
https://www.threads.net/@vtk88com
https://github.com/vtk88com
https://www.diigo.com/profile/vtk88com
https://www.behance.net/vtk88com
https://www.tumblr.com/vtk88com
https://medium.com/@vtk88com
https://hashnode.com/@vtk88com
https://about.me/vtk88com
https://trello.com/u/vtk88com
https://i9bettvegas.bandcamp.com/album/vtk88com
https://vtk88com.blogspot.com/vtk88com
https://vtk88com.weebly.com/vtk88com
https://vi.gta5-mods.com/users/vtk88com
https://myspace.com/vtk88com
escaped approximation of how a raw message
         * will be formatted by our API.
         *
         * This method currently _only_ formats newlines into
         * <p> and <br> tags.
         *     TODO: make this method handle mentions and various
         *           supported tags.
         *
         * @params {string} text Text to format.
         * @returns {string} Escaped and <p> <br> message.
         */
        formatMessage: (function () {

            var paragraphReg = /(?:\r\n|\r|\n){2,}/;
            var lineReg = /\r\n|\r|\n/;

            return function (text) {

                // Any series of two or more newlines is a paragraph.
                // Use `compact()` to filter empty values.
                var paragraphs = _.chain(text.split(paragraphReg))
                    .compact()
                    .value();

                var html = _.map(paragraphs, function (paragraph) {

                    // Single newlines are spaced with <br> tags.
                    // Use `compact()` to filter empty values.
                    return _.chain(paragraph.split(lineReg))
                        .compact()

                        // Escape text to prevent users from
                        // accidentally XXSing themselves.
                        .map(_.escape)
                        .join('<br>')
                        .value();

                }).join('</p><p>');

                return '<p>' + html + '</p>';
            };

        })(),
    });

    //
    // Applied mixins
    //

    modelMixins.withCreatedAt.call(Post.prototype);
    advice.withAdvice.call(Post.prototype);

    //
    // Optional mixins
    //

    /**
     * Adds `author` as a sub-model of Post
     */

    Post.withAuthor = function (UserModel) {
        this.around('set', function (set, key, val, options) {
            var attrs;

            // eslint-disable-next-line eqeqeq, no-eq-null
            if (key == null) {
                return this;
            }
            // Handle both `"key", value` and `{key: value}` -style arguments.
            if (typeof key === 'object') {
                attrs = key;
                options = val;
            } else {
                attrs = {};
                attrs[key] = val;
            }

            var author = attrs.author;

            if (author) {
                // Convert 'author' attributes to a model instance. Attributes
                // should never contain nested properties, but that's what the API
                // returns, so we convert it here.
                //
                // NOTE: This kind of stuff is made easier with a Backbone plugin
                //       called Backbone-Relational, but I'm not sure we need it
                //       just yet. (https://github.com/PaulUithol/Backbone-relational)

                if (_.isString(author) || _.isNumber(author)) {
                    var id = author;
                    author = {};
                    author[UserModel.prototype.idAttribute || 'id'] = id;
                }

                // Usually the Post will have access to the PostCollection,
                // but when the sort-order changes we need to grab the collection from the author
                var collection = this.collection || (this.author && this.author.collection);

                // Convert the forum badge IDs on the post author to forum badge objects
                var forum = collection && collection.thread && collection.thread.forum;
                if (this.author && this.author.get('badges').length && this.author.get('badges')[0].id) {
                    // If the author already has the forum badge objects
                    author.badges = this.author.get('badges');
                } else if (forum && forum.get('badges') && author.badges) {
                    var badges = [];
                    var badgeIds = author.badges || [];
                    var forumBadges = forum.get('badges');
                    badgeIds.forEach(function (badgeId) {
                        if (forumBadges[badgeId]) {
                            badges.push(forumBadges[badgeId]);
                        }
                    });
                    author.badges = badges;
                }

                this.author = new UserModel(author);

                // This is the standard event structure for when a related model is
                // set (used in home).
                this.trigger('changeRelated:author');

                delete attrs.author;
            }

            return set.call(this, attrs, options);
        });

        this.around('toJSON', function (toJSON) {
            var json = toJSON.apply(this, _.rest(arguments));

            if (this.author) {
                json.author = this.author.toJSON();
            }
            return json;
        });
    };

    /**
     * Adds `media` as a sub-collection of Post
     *
     * @param {Object} MediaCollection - a MediaCollection class that encapsulates media attributes
     * @returns {undefined}
     */
    Post.withMediaCollection = function (MediaCollection) {
        this.after('set', function (attrs) {
            if (attrs && typeof attrs !== 'string') {

                // Convert 'media' attribute into collection
                if (!_.isUndefined(attrs.media)) {
                    // Populate media collection
                    if (this.media) {
                        this.media.reset(attrs.media);
                    } else {
                        this.media = new MediaCollection(attrs.media);
                    }
                    delete attrs.media;
                }
            }
        });

        this.around('toJSON', function (toJSON) {
            var json = toJSON.apply(this, _.rest(arguments));

            if (this.media) {
                json.media = this.media.toJSON();
            }
            return json;
        });
    };

    return Post;
});
// https://c.disquscdn.com/next/next-core/core/models/Post.js