<commit_before>/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import dis from '../../../dispatcher';
import EditorModel from '../../../editor/model';
import {
    htmlSerializeIfNeeded,
    textSerialize,
    containsEmote,
    stripEmoteCommand,
    unescapeMessage,
    startsWith,
    stripPrefix,
} from '../../../editor/serialize';
import {CommandPartCreator} from '../../../editor/parts';
import BasicMessageComposer from "./BasicMessageComposer";
import ReplyPreview from "./ReplyPreview";
import RoomViewStore from '../../../stores/RoomViewStore';
import ReplyThread from "../elements/ReplyThread";
import {parseEvent} from '../../../editor/deserialize';
import {findEditableEvent} from '../../../utils/EventUtils';
import SendHistoryManager from "../../../SendHistoryManager";
import {processCommandInput} from '../../../SlashCommands';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import {_t, _td} from '../../../languageHandler';
import ContentMessages from '../../../ContentMessages';
import {Key} from "../../../Keyboard";
import MatrixClientContext from "../../../contexts/MatrixClientContext";

function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
    const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent);
    Object.assign(content, replyContent);

    // Part of Replies fallback support - prepend the text we're sending
    // with the text we're replying to
    const nestedReply = ReplyThread.getNestedReplyText(repliedToEvent, permalinkCreator);
    if (nestedReply) {
        if (content.formatted_body) {
            content.formatted_body = nestedReply.html + content.formatted_body;
        }
        content.body = nestedReply.body + content.body;
    }
}

function createMessageContent(model, permalinkCreator) {
    const isEmote = containsEmote(model);
    if (isEmote) {
        model = stripEmoteCommand(model);
    }
    if (startsWith(model, "//")) {
        model = stripPrefix(model, "/");
    }
    model = unescapeMessage(model);
    const repliedToEvent = RoomViewStore.getQuotingEvent();

    const body = textSerialize(model);
    const content = {
        msgtype: isEmote ? "m.emote" : "m.text",
        body: body,
    };
    const formattedBody = htmlSerializeIfNeeded(model, {forceHTML: !!repliedToEvent});
    if (formattedBody) {
        content.format = "org.matrix.custom.html";
        content.formatted_body = formattedBody;
    }

    if (repliedToEvent) {
        addReplyToMessageContent(content, repliedToEvent, permalinkCreator);
    }

    return content;
}

export default class SendMessageComposer extends React.Component {
    static propTypes = {
        room: PropTypes.object.isRequired,
        placeholder: PropTypes.string,
        permalinkCreator: PropTypes.object.isRequired,
    };

    static contextType = MatrixClientContext;

    constructor(props) {
        super(props);
        this.model = null;
        this._editorRef = null;
        this.currentlyComposedEditorState = null;
    }

    _setEditorRef = ref => {
        this._editorRef = ref;
    };

    _onKeyDown = (event) => {
        // ignore any keypress while doing IME compositions
        if (this._editorRef.isComposing(event)) {
            return;
        }
        const hasModifier = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
        if (event.key === Key.ENTER && !hasModifier) {
            this._sendMessage();
            event.preventDefault();
        } else if (event.key === Key.ARROW_UP) {
            this.onVerticalArrow(event, true);
        } else if (event.key === Key.ARROW_DOWN) {
            this.onVerticalArrow(event, false);
        }
    }

    onVerticalArrow(e, up) {
        if (e.ctrlKey || e.shiftKey || e.metaKey) return;

        const shouldSelectHistory = e.altKey;
        const shouldEditLastMessage = !e.altKey && up && !RoomViewStore.getQuotingEvent();

        if (shouldSelectHistory) {
            // Try select composer history
            const selected = this.selectSendHistory(up);
            if (selected) {
                // We're selecting history, so prevent the key event from doing anything else
                e.preventDefault();
            }
        } else if (shouldEditLastMessage) {
            // selection must be collapsed and caret at start
            if (this._editorRef.isSelectionCollapsed() && this._editorRef.isCaretAtStart()) {
                const editEvent = findEditableEvent(this.props.room, false);
                if (editEvent) {
                    // We're selecting history, so prevent the key event from doing anything else
                    e.preventDefault();
                    dis.dispatch({
                        action: 'edit_event',
                        event: editEvent,
                    });
                }
            }
        }
    }

    // we keep sent messages/commands in a separate history (separate from undo history)
    // so you can alt+up/down in them
    selectSendHistory(up) {
        const delta = up ? -1 : 1;
        // True if we are not currently selecting history, but composing a message
        if (this.sendHistoryManager.currentIndex === this.sendHistoryManager.history.length) {
            // We can't go any further - there isn't any more history, so nop.
            if (!up) {
                return;
            }
            this.currentlyComposedEditorState = this.model.serializeParts();
        } else if (this.sendHistoryManager.currentIndex + delta === this.sendHistoryManager.history.length) {
            // True when we return to the message being composed currently
            this.model.reset(this.currentlyComposedEditorState);
            this.sendHistoryManager.currentIndex = this.sendHistoryManager.history.length;
            return;
        }
        const serializedParts = this.sendHistoryManager.getItem(delta);
        if (serializedParts) {
            this.model.reset(serializedParts);
            this._editorRef.focus();
        }
    }

    _isSlashCommand() {
        const parts = this.model.parts;
        const firstPart = parts[0];
        if (firstPart) {
            if (firstPart.type === "command" && !firstPart.text.startsWith("//")) {
                return true;
            }
            // be extra resilient when somehow the AutocompleteWrapperModel or
            // CommandPartCreator fails to insert a command part, so we don't send
            // a command as a message
            if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//") && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
                return true;
            }
        }
        return false;
    }

    async _runSlashCommand() {
        const commandText = this.model.parts.reduce((text, part) => {
            // use mxid to textify user pills in a command
            if (part.type === "user-pill") {
                return text + part.resourceId;
            }
            return text + part.text;
        }, "");
        const cmd = processCommandInput(this.props.room.roomId, commandText);

        if (cmd) {
            let error = cmd.error;
            if (cmd.promise) {
                try {
                    await cmd.promise;
                } catch (err) {
                    error = err;
                }
            }
            if (error) {
                console.error("Command failure: %s", error);
                const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                // assume the error is a server error when the command is async
                const isServerError = !!cmd.promise;
                const title = isServerError ? _td("Server error") : _td("Command error");

                let errText;
                if (typeof error === 'string') {
                    errText = error;
                } else if (error.message) {
                    errText = error.message;
                } else {
                    errText = _t("Server unavailable, overloaded, or something else went wrong.");
                }

                Modal.createTrackedDialog(title, '', ErrorDialog, {
                    title: _t(title),
                    description: errText,
                });
            } else {
                console.log("Command success.");
            }
        }
    }

    _sendMessage() {
        if (this.model.isEmpty) {
            return;
        }
        if (!containsEmote(this.model) && this._isSlashCommand()) {
            this._runSlashCommand();
        } else {
            const isReply = !!RoomViewStore.getQuotingEvent();
            const {roomId} = this.props.room;
            const content = createMessageContent(this.model, this.props.permalinkCreator);
            this.context.sendMessage(roomId, content);
            if (isReply) {
                // Clear reply_to_event as we put the message into the queue
                // if the send fails, retry will handle resending.
                dis.dispatch({
                    action: 'reply_to_event',
                    event: null,
                });
            }
        }
        this.sendHistoryManager.save(this.model);
        // clear composer
        this.model.reset([]);
        this._editorRef.clearUndoHistory();
        this._editorRef.focus();
        this._clearStoredEditorState();
    }

    componentDidMount() {
        this._editorRef.getEditableRootNode().addEventListener("paste", this._onPaste, true);
    }

    componentWillUnmount() {
        dis.unregister(this.dispatcherRef);
        this._editorRef.getEditableRootNode().removeEventListener("paste", this._onPaste, true);
    }

    componentWillMount() {
        const partCreator = new CommandPartCreator(this.props.room, this.context);
        const parts = this._restoreStoredEditorState(partCreator) || [];
        this.model = new EditorModel(parts, partCreator);
        this.dispatcherRef = dis.register(this.onAction);
        this.sendHistoryManager = new SendHistoryManager(this.props.room.roomId, 'mx_cider_composer_history_');
    }

    get _editorStateKey() {
        return `cider_editor_state_${this.props.room.roomId}`;
    }

    _clearStoredEditorState() {
        localStorage.removeItem(this._editorStateKey);
    }

    _restoreStoredEditorState(partCreator) {
        const json = localStorage.getItem(this._editorStateKey);
        if (json) {
            const serializedParts = JSON.parse(json);
            const parts = serializedParts.map(p => partCreator.deserializePart(p));
            return parts;
        }
    }

    _saveStoredEditorState = () => {
        if (this.model.isEmpty) {
            this._clearStoredEditorState();
        } else {
            localStorage.setItem(this._editorStateKey, JSON.stringify(this.model.serializeParts()));
        }
    }

    onAction = (payload) => {
        switch (payload.action) {
            case 'reply_to_event':
            case 'focus_composer':
                this._editorRef && this._editorRef.focus();
                break;
            case 'insert_mention':
                this._insertMention(payload.user_id);
                break;
            case 'quote':
                this._insertQuotedMessage(payload.event);
                break;
        }
    };

    _insertMention(userId) {
        const {model} = this;
        const {partCreator} = model;
        const member = this.props.room.getMember(userId);
        const displayName = member ?
            member.rawDisplayName : userId;
        const caret = this._editorRef.getCaret();
        const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
        const insertIndex = position.index + 1;
        const parts = partCreator.createMentionParts(insertIndex, displayName, userId);
        model.transform(() => {
            const addedLen = model.insert(parts, position);
            return model.positionForOffset(caret.offset + addedLen, true);
        });
        // refocus on composer, as we just clicked "Mention"
        this._editorRef && this._editorRef.focus();
    }

    _insertQuotedMessage(event) {
        const {model} = this;
        const {partCreator} = model;
        const quoteParts = parseEvent(event, partCreator, { isQuotedMessage: true });
        // add two newlines
        quoteParts.push(partCreator.newline());
        quoteParts.push(partCreator.newline());
        model.transform(() => {
            const addedLen = model.insert(quoteParts, model.positionForOffset(0));
            return model.positionForOffset(addedLen, true);
        });
        // refocus on composer, as we just clicked "Quote"
        this._editorRef && this._editorRef.focus();
    }

    _onPaste = (event) => {
        const {clipboardData} = event;
        if (clipboardData.files.length) {
            // This actually not so much for 'files' as such (at time of writing
            // neither chrome nor firefox let you paste a plain file copied
            // from Finder) but more images copied from a different website
            // / word processor etc.
            ContentMessages.sharedInstance().sendContentListToRoom(
                Array.from(clipboardData.files), this.props.room.roomId, this.context,
            );
        }
    }

    render() {
        return (

                    <ReplyPreview permalinkCreator={this.props.permalinkCreator} />

                <BasicMessageComposer
                    ref={this._setEditorRef}
                    model={this.model}
                    room={this.props.room}
                    label={this.props.placeholder}
                    placeholder={this.props.placeholder}
                    onChange={this._saveStoredEditorState}
                />

        );
    }
}
<commit_msg>Fix changes after typing / at pos=0 allowing to cancel command<commit_after>/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import dis from '../../../dispatcher';
import EditorModel from '../../../editor/model';
import {
    htmlSerializeIfNeeded,
    textSerialize,
    containsEmote,
    stripEmoteCommand,
    unescapeMessage,
    startsWith,
    stripPrefix,
} from '../../../editor/serialize';
import {CommandPartCreator} from '../../../editor/parts';
import BasicMessageComposer from "./BasicMessageComposer";
import ReplyPreview from "./ReplyPreview";
import RoomViewStore from '../../../stores/RoomViewStore';
import ReplyThread from "../elements/ReplyThread";
import {parseEvent} from '../../../editor/deserialize';
import {findEditableEvent} from '../../../utils/EventUtils';
import SendHistoryManager from "../../../SendHistoryManager";
import {processCommandInput} from '../../../SlashCommands';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import {_t, _td} from '../../../languageHandler';
import ContentMessages from '../../../ContentMessages';
import {Key} from "../../../Keyboard";
import MatrixClientContext from "../../../contexts/MatrixClientContext";

function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
    const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent);
    Object.assign(content, replyContent);

    // Part of Replies fallback support - prepend the text we're sending
    // with the text we're replying to
    const nestedReply = ReplyThread.getNestedReplyText(repliedToEvent, permalinkCreator);
    if (nestedReply) {
        if (content.formatted_body) {
            content.formatted_body = nestedReply.html + content.formatted_body;
        }
        content.body = nestedReply.body + content.body;
    }
}

function createMessageContent(model, permalinkCreator) {
    const isEmote = containsEmote(model);
    if (isEmote) {
        model = stripEmoteCommand(model);
    }
    if (startsWith(model, "//")) {
        model = stripPrefix(model, "/");
    }
    model = unescapeMessage(model);
    const repliedToEvent = RoomViewStore.getQuotingEvent();

    const body = textSerialize(model);
    const content = {
        msgtype: isEmote ? "m.emote" : "m.text",
        body: body,
    };
    const formattedBody = htmlSerializeIfNeeded(model, {forceHTML: !!repliedToEvent});
    if (formattedBody) {
        content.format = "org.matrix.custom.html";
        content.formatted_body = formattedBody;
    }

    if (repliedToEvent) {
        addReplyToMessageContent(content, repliedToEvent, permalinkCreator);
    }

    return content;
}

export default class SendMessageComposer extends React.Component {
    static propTypes = {
        room: PropTypes.object.isRequired,
        placeholder: PropTypes.string,
        permalinkCreator: PropTypes.object.isRequired,
    };

    static contextType = MatrixClientContext;

    constructor(props) {
        super(props);
        this.model = null;
        this._editorRef = null;
        this.currentlyComposedEditorState = null;
    }

    _setEditorRef = ref => {
        this._editorRef = ref;
    };

    _onKeyDown = (event) => {
        // ignore any keypress while doing IME compositions
        if (this._editorRef.isComposing(event)) {
            return;
        }
        const hasModifier = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
        if (event.key === Key.ENTER && !hasModifier) {
            this._sendMessage();
            event.preventDefault();
        } else if (event.key === Key.ARROW_UP) {
            this.onVerticalArrow(event, true);
        } else if (event.key === Key.ARROW_DOWN) {
            this.onVerticalArrow(event, false);
        }
    }

    onVerticalArrow(e, up) {
        if (e.ctrlKey || e.shiftKey || e.metaKey) return;

        const shouldSelectHistory = e.altKey;
        const shouldEditLastMessage = !e.altKey && up && !RoomViewStore.getQuotingEvent();

        if (shouldSelectHistory) {
            // Try select composer history
            const selected = this.selectSendHistory(up);
            if (selected) {
                // We're selecting history, so prevent the key event from doing anything else
                e.preventDefault();
            }
        } else if (shouldEditLastMessage) {
            // selection must be collapsed and caret at start
            if (this._editorRef.isSelectionCollapsed() && this._editorRef.isCaretAtStart()) {
                const editEvent = findEditableEvent(this.props.room, false);
                if (editEvent) {
                    // We're selecting history, so prevent the key event from doing anything else
                    e.preventDefault();
                    dis.dispatch({
                        action: 'edit_event',
                        event: editEvent,
                    });
                }
            }
        }
    }

    // we keep sent messages/commands in a separate history (separate from undo history)
    // so you can alt+up/down in them
    selectSendHistory(up) {
        const delta = up ? -1 : 1;
        // True if we are not currently selecting history, but composing a message
        if (this.sendHistoryManager.currentIndex === this.sendHistoryManager.history.length) {
            // We can't go any further - there isn't any more history, so nop.
            if (!up) {
                return;
            }
            this.currentlyComposedEditorState = this.model.serializeParts();
        } else if (this.sendHistoryManager.currentIndex + delta === this.sendHistoryManager.history.length) {
            // True when we return to the message being composed currently
            this.model.reset(this.currentlyComposedEditorState);
            this.sendHistoryManager.currentIndex = this.sendHistoryManager.history.length;
            return;
        }
        const serializedParts = this.sendHistoryManager.getItem(delta);
        if (serializedParts) {
            this.model.reset(serializedParts);
            this._editorRef.focus();
        }
    }

    _isSlashCommand() {
        const parts = this.model.parts;
        const firstPart = parts[0];
        if (firstPart) {
            if (firstPart.type === "command" && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) {
                return true;
            }
            // be extra resilient when somehow the AutocompleteWrapperModel or
            // CommandPartCreator fails to insert a command part, so we don't send
            // a command as a message
            if (firstPart.text.startsWith("/") && firstPart.text.startsWith("//") && !firstPart.text.startsWith("//")
                && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
                return true;
            }
        }
        return false;
    }

    async _runSlashCommand() {
        const commandText = this.model.parts.reduce((text, part) => {
            // use mxid to textify user pills in a command
            if (part.type === "user-pill") {
                return text + part.resourceId;
            }
            return text + part.text;
        }, "");
        const cmd = processCommandInput(this.props.room.roomId, commandText);

        if (cmd) {
            let error = cmd.error;
            if (cmd.promise) {
                try {
                    await cmd.promise;
                } catch (err) {
                    error = err;
                }
            }
            if (error) {
                console.error("Command failure: %s", error);
                const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                // assume the error is a server error when the command is async
                const isServerError = !!cmd.promise;
                const title = isServerError ? _td("Server error") : _td("Command error");

                let errText;
                if (typeof error === 'string') {
                    errText = error;
                } else if (error.message) {
                    errText = error.message;
                } else {
                    errText = _t("Server unavailable, overloaded, or something else went wrong.");
                }

                Modal.createTrackedDialog(title, '', ErrorDialog, {
                    title: _t(title),
                    description: errText,
                });
            } else {
                console.log("Command success.");
            }
        }
    }

    _sendMessage() {
        if (this.model.isEmpty) {
            return;
        }
        if (!containsEmote(this.model) && this._isSlashCommand()) {
            this._runSlashCommand();
        } else {
            const isReply = !!RoomViewStore.getQuotingEvent();
            const {roomId} = this.props.room;
            const content = createMessageContent(this.model, this.props.permalinkCreator);
            this.context.sendMessage(roomId, content);
            if (isReply) {
                // Clear reply_to_event as we put the message into the queue
                // if the send fails, retry will handle resending.
                dis.dispatch({
                    action: 'reply_to_event',
                    event: null,
                });
            }
        }
        this.sendHistoryManager.save(this.model);
        // clear composer
        this.model.reset([]);
        this._editorRef.clearUndoHistory();
        this._editorRef.focus();
        this._clearStoredEditorState();
    }

    componentDidMount() {
        this._editorRef.getEditableRootNode().addEventListener("paste", this._onPaste, true);
    }

    componentWillUnmount() {
        dis.unregister(this.dispatcherRef);
        this._editorRef.getEditableRootNode().removeEventListener("paste", this._onPaste, true);
    }

    componentWillMount() {
        const partCreator = new CommandPartCreator(this.props.room, this.context);
        const parts = this._restoreStoredEditorState(partCreator) || [];
        this.model = new EditorModel(parts, partCreator);
        this.dispatcherRef = dis.register(this.onAction);
        this.sendHistoryManager = new SendHistoryManager(this.props.room.roomId, 'mx_cider_composer_history_');
    }

    get _editorStateKey() {
        return `cider_editor_state_${this.props.room.roomId}`;
    }

    _clearStoredEditorState() {
        localStorage.removeItem(this._editorStateKey);
    }

    _restoreStoredEditorState(partCreator) {
        const json = localStorage.getItem(this._editorStateKey);
        if (json) {
            const serializedParts = JSON.parse(json);
            const parts = serializedParts.map(p => partCreator.deserializePart(p));
            return parts;
        }
    }

    _saveStoredEditorState = () => {
        if (this.model.isEmpty) {
            this._clearStoredEditorState();
        } else {
            localStorage.setItem(this._editorStateKey, JSON.stringify(this.model.serializeParts()));
        }
    }

    onAction = (payload) => {
        switch (payload.action) {
            case 'reply_to_event':
            case 'focus_composer':
                this._editorRef && this._editorRef.focus();
                break;
            case 'insert_mention':
                this._insertMention(payload.user_id);
                break;
            case 'quote':
                this._insertQuotedMessage(payload.event);
                break;
        }
    };

    _insertMention(userId) {
        const {model} = this;
        const {partCreator} = model;
        const member = this.props.room.getMember(userId);
        const displayName = member ?
            member.rawDisplayName : userId;
        const caret = this._editorRef.getCaret();
        const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
        const insertIndex = position.index + 1;
        const parts = partCreator.createMentionParts(insertIndex, displayName, userId);
        model.transform(() => {
            const addedLen = model.insert(parts, position);
            return model.positionForOffset(caret.offset + addedLen, true);
        });
        // refocus on composer, as we just clicked "Mention"
        this._editorRef && this._editorRef.focus();
    }

    _insertQuotedMessage(event) {
        const {model} = this;
        const {partCreator} = model;
        const quoteParts = parseEvent(event, partCreator, { isQuotedMessage: true });
        // add two newlines
        quoteParts.push(partCreator.newline());
        quoteParts.push(partCreator.newline());
        model.transform(() => {
            const addedLen = model.insert(quoteParts, model.positionForOffset(0));
            return model.positionForOffset(addedLen, true);
        });
        // refocus on composer, as we just clicked "Quote"
        this._editorRef && this._editorRef.focus();
    }

    _onPaste = (event) => {
        const {clipboardData} = event;
        if (clipboardData.files.length) {
            // This actually not so much for 'files' as such (at time of writing
            // neither chrome nor firefox let you paste a plain file copied
            // from Finder) but more images copied from a different website
            // / word processor etc.
            ContentMessages.sharedInstance().sendContentListToRoom(
                Array.from(clipboardData.files), this.props.room.roomId, this.context,
            );
        }
    }

    render() {
        return (

                    <ReplyPreview permalinkCreator={this.props.permalinkCreator} />

                <BasicMessageComposer
                    ref={this._setEditorRef}
                    model={this.model}
                    room={this.props.room}
                    label={this.props.placeholder}
                    placeholder={this.props.placeholder}
                    onChange={this._saveStoredEditorState}
                />

        );
    }
}
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { DragLayer as dragLayer } from 'react-dnd';
import { Card } from 'components';

const layerStyles = {
  position: 'fixed',
  pointerEvents: 'none',
  zIndex: 100,
  left: 0,
  top: 0,
  width: '100%',
  height: '100%',
};

function getItemStyles(props) {
  const { currentOffset } = props;
  if (!currentOffset) {
    return {
      display: 'none',
    };
  }

  console.log('X:', currentOffset.x, 'Y:', currentOffset.y);

  const { x, y } = currentOffset;
  const transform = `translate(${x}px, ${y}px)`;
  return {
    transform,
    WebkitTransform: transform,
  };
}

class CustomDragLayer extends Component {
  static propTypes = {
    item: PropTypes.object,
    itemType: PropTypes.string,
    currentOffset: PropTypes.shape({
      x: PropTypes.number.isRequired,
      y: PropTypes.number.isRequired,
    }),
    isDragging: PropTypes.bool.isRequired,
  }
  render() {
    const { item, itemType, isDragging } = this.props;

    if (!isDragging || itemType !== 'CARD') {
      return null;
    }

    return (

          <Card card={item.card} isDragging />

    );
  }
}

function collect(monitor) {
  return {
    item: monitor.getItem(),
    itemType: monitor.getItemType(),
    currentOffset: monitor.getSourceClientOffset(),
    isDragging: monitor.isDragging(),
  };
}

export default dragLayer(collect)(CustomDragLayer);
<commit_msg>Remove console.log from CustomDragLayer<commit_after>import React, { Component, PropTypes } from 'react';
import { DragLayer as dragLayer } from 'react-dnd';
import { Card } from 'components';

const layerStyles = {
  position: 'fixed',
  pointerEvents: 'none',
  zIndex: 100,
  left: 0,
  top: 0,
  width: '100%',
  height: '100%',
};

function getItemStyles(props) {
  const { currentOffset } = props;
  if (!currentOffset) {
    return {
      display: 'none',
    };
  }

  const { x, y } = currentOffset;
  const transform = `translate(${x}px, ${y}px)`;
  return {
    transform,
    WebkitTransform: transform,
  };
}

class CustomDragLayer extends Component {
  static propTypes = {
    item: PropTypes.object,
    itemType: PropTypes.string,
    currentOffset: PropTypes.shape({
      x: PropTypes.number.isRequired,
      y: PropTypes.number.isRequired,
    }),
    isDragging: PropTypes.bool.isRequired,
  }
  render() {
    const { item, itemType, isDragging } = this.props;

    if (!isDragging || itemType !== 'CARD') {
      return null;
    }

    return (

          <Card card={item.card} isDragging />

    );
  }
}

function collect(monitor) {
  return {
    item: monitor.getItem(),
    itemType: monitor.getItemType(),
    currentOffset: monitor.getSourceClientOffset(),
    isDragging: monitor.isDragging(),
  };
}

export default dragLayer(collect)(CustomDragLayer);
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */

'use strict';

const getMediaButton = document.querySelector('button#getMedia');
const connectButton = document.querySelector('button#connect');
const hangupButton = document.querySelector('button#hangup');

getMediaButton.onclick = getMedia;
connectButton.onclick = createPeerConnection;
hangupButton.onclick = hangup;

const minWidthInput = document.querySelector('div#minWidth input');
const maxWidthInput = document.querySelector('div#maxWidth input');
const minHeightInput = document.querySelector('div#minHeight input');
const maxHeightInput = document.querySelector('div#maxHeight input');
const minFramerateInput = document.querySelector('div#minFramerate input');
const maxFramerateInput = document.querySelector('div#maxFramerate input');

minWidthInput.onchange = maxWidthInput.onchange =
  minHeightInput.onchange = maxHeightInput.onchange =
    minFramerateInput.onchange = maxFramerateInput.onchange = displayRangeValue;

const getUserMediaConstraintsDiv = document.querySelector('div#getUserMediaConstraints');
const bitrateDiv = document.querySelector('div#bitrate');
const peerDiv = document.querySelector('div#peer');
const senderStatsDiv = document.querySelector('div#senderStats');
const receiverStatsDiv = document.querySelector('div#receiverStats');

const localVideo = document.querySelector('div#localVideo video');
const remoteVideo = document.querySelector('div#remoteVideo video');
const localVideoStatsDiv = document.querySelector('div#localVideo div');
const remoteVideoStatsDiv = document.querySelector('div#remoteVideo div');

let localPeerConnection;
let remotePeerConnection;
let localStream;
let bytesPrev;
let timestampPrev;

main();

function main() {
  displayGetUserMediaConstraints();
}

function hangup() {
  console.log('Ending call');
  localPeerConnection.close();
  remotePeerConnection.close();

  // query stats one last time.
  Promise
      .all([
        remotePeerConnection
            .getStats(null)
            .then(showRemoteStats, err => console.log(err)),
        localPeerConnection
            .getStats(null)
            .then(showLocalStats, err => console.log(err))
      ])
      .then(() => {
        localPeerConnection = null;
        remotePeerConnection = null;
      });

  localStream.getTracks().forEach(track => track.stop());
  localStream = null;

  hangupButton.disabled = true;
  getMediaButton.disabled = false;
}

function getMedia() {
  getMediaButton.disabled = true;
  if (localStream) {
    localStream.getTracks().forEach(track => track.stop());
    const videoTracks = localStream.getVideoTracks();
    for (let i = 0; i !== videoTracks.length; ++i) {
      videoTracks[i].stop();
    }
  }
  navigator.mediaDevices.getUserMedia(getUserMediaConstraints())
      .then(gotStream)
      .catch(e => {
        const message = `getUserMedia error: ${e.name}\nPermissionDeniedError may mean invalid constraints.`;
        alert(message);
        console.log(message);
        getMediaButton.disabled = false;
      });
}

function gotStream(stream) {
  connectButton.disabled = false;
  console.log('GetUserMedia succeeded');
  localStream = stream;
  localVideo.srcObject = stream;
}

function getUserMediaConstraints() {
  const constraints = {};
  constraints.audio = true;
  constraints.video = {};
  if (minWidthInput.value !== '0') {
    constraints.video.width = {};
    constraints.video.width.min = minWidthInput.value;
  }
  if (maxWidthInput.value !== '0') {
    constraints.video.width = constraints.video.width || {};
    constraints.video.width.max = maxWidthInput.value;
  }
  if (minHeightInput.value !== '0') {
    constraints.video.height = {};
    constraints.video.height.min = minHeightInput.value;
  }
  if (maxHeightInput.value !== '0') {
    constraints.video.height = constraints.video.height || {};
    constraints.video.height.max = maxHeightInput.value;
  }
  if (minFramerateInput.value !== '0') {
    constraints.video.frameRate = {};
    constraints.video.frameRate.min = minFramerateInput.value;
  }
  if (maxFramerateInput.value !== '0') {
    constraints.video.frameRate = constraints.video.frameRate || {};
    constraints.video.frameRate.max = maxFramerateInput.value;
  }

  return constraints;
}

function displayGetUserMediaConstraints() {
  const constraints = getUserMediaConstraints();
  console.log('getUserMedia constraints', constraints);
  getUserMediaConstraintsDiv.textContent = JSON.stringify(constraints, null, '    ');
}

function createPeerConnection() {
  connectButton.disabled = true;
  hangupButton.disabled = false;

  bytesPrev = 0;
  timestampPrev = 0;
  localPeerConnection = new RTCPeerConnection(null);
  remotePeerConnection = new RTCPeerConnection(null);
  localStream.getTracks().forEach(track => localPeerConnection.addTrack(track, localStream));
  console.log('localPeerConnection creating offer');
  localPeerConnection.onnegotiationeeded = () => console.log('Negotiation needed - localPeerConnection');
  remotePeerConnection.onnegotiationeeded = () => console.log('Negotiation needed - remotePeerConnection');
  localPeerConnection.onicecandidate = e => {
    console.log('Candidate localPeerConnection');
    remotePeerConnection
        .addIceCandidate(e.candidate)
        .then(onAddIceCandidateSuccess, onAddIceCandidateError);
  };
  remotePeerConnection.onicecandidate = e => {
    console.log('Candidate remotePeerConnection');
    localPeerConnection
        .addIceCandidate(e.candidate)
        .then(onAddIceCandidateSuccess, onAddIceCandidateError);
  };
  remotePeerConnection.ontrack = e => {
    if (remoteVideo.srcObject !== e.streams[0]) {
      console.log('remotePeerConnection got stream');
      remoteVideo.srcObject = e.streams[0];
    }
  };
  localPeerConnection.createOffer().then(
      desc => {
        console.log('localPeerConnection offering');
        localPeerConnection.setLocalDescription(desc);
        remotePeerConnection.setRemoteDescription(desc);
        remotePeerConnection.createAnswer().then(
            desc2 => {
              console.log('remotePeerConnection answering');
              remotePeerConnection.setLocalDescription(desc2);
              localPeerConnection.setRemoteDescription(desc2);
            },
            err => console.log(err)
        );
      },
      err => console.log(err)
  );
}

function onAddIceCandidateSuccess() {
  console.log('AddIceCandidate success.');
}

function onAddIceCandidateError(error) {
  console.log(`Failed to add Ice Candidate: ${error.toString()}`);
}

function showRemoteStats(results) {
  const statsString = dumpStats(results);
  receiverStatsDiv.innerHTML = `<h2>Receiver stats</h2>${statsString}`;
  // calculate video bitrate
  results.forEach(report => {
    const now = report.timestamp;

    let bitrate;
    if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
      const bytes = report.bytesReceived;
      if (timestampPrev) {
        bitrate = 8 * (bytes - bytesPrev) / (now - timestampPrev);
        bitrate = Math.floor(bitrate);
      }
      bytesPrev = bytes;
      timestampPrev = now;
    }
    if (bitrate) {
      bitrate += ' kbits/sec';
      bitrateDiv.innerHTML = `<strong>Bitrate:</strong>${bitrate}`;
    }
  });

  // figure out the peer's ip
  let activeCandidatePair = null;
  let remoteCandidate = null;

  // Search for the candidate pair, spec-way first.
  results.forEach(report => {
    if (report.type === 'transport') {
      activeCandidatePair = results.get(report.selectedCandidatePairId);
    }
  });
  // Fallback for Firefox.
  if (!activeCandidatePair) {
    results.forEach(report => {
      if (report.type === 'candidate-pair' && report.selected) {
        activeCandidatePair = report;
      }
    });
  }
  if (activeCandidatePair && activeCandidatePair.remoteCandidateId) {
    remoteCandidate = results.get(activeCandidatePair.remoteCandidateId);
  }
  if (remoteCandidate) {
    if (remoteCandidate.ip && remoteCandidate.port) {
      peerDiv.innerHTML = `<strong>Connected to:</strong>${remoteCandidate.ip}:${remoteCandidate.port}`;
    } else if (remoteCandidate.ipAddress && remoteCandidate.portNumber) {
      // Fall back to old names.
      peerDiv.innerHTML = `<strong>Connected to:</strong>${remoteCandidate.ipAddress}:${remoteCandidate.portNumber}`;
    }
  }
}

function showLocalStats(results) {
  const statsString = dumpStats(results);
  senderStatsDiv.innerHTML = `<h2>Sender stats</h2>${statsString}`;
}

// Display statistics
setInterval(() => {
  if (localPeerConnection && remotePeerConnection) {
    remotePeerConnection
        .getStats(null)
        .then(showRemoteStats, err => console.log(err));
    localPeerConnection
        .getStats(null)
        .then(showLocalStats, err => console.log(err));
  } else {
    console.log('Not connected yet');
  }
  // Collect some stats from the video tags.
  if (localVideo.videoWidth) {
    const width = localVideo.videoWidth;
    const height = localVideo.videoHeight;
    localVideoStatsDiv.innerHTML = `<strong>Video dimensions:</strong> ${width}x${height}px`;
  }
  if (remoteVideo.videoWidth) {
    const rHeight = remoteVideo.videoHeight;
    const rWidth = remoteVideo.videoWidth;
    remoteVideoStatsDiv.innerHTML = `<strong>Video dimensions:</strong> ${rWidth}x${rHeight}px`;
  }
}, 1000);

// Dumping a stats variable as a string.
// might be named toString?
function dumpStats(results) {
  let statsString = '';
  results.forEach(res => {
    statsString += '
Report type=';
    statsString += res.type;
    statsString += '
\n';
    statsString += `id ${res.id}<br>`;
    statsString += `time ${res.timestamp}<br>`;
    Object.keys(res).forEach(k => {
      if (k !== 'timestamp' && k !== 'type' && k !== 'id') {
        statsString += `${k}: ${res[k]}<br>`;
      }
    });
  });
  return statsString;
}

// Utility to show the value of a range in a sibling span element
function displayRangeValue(e) {
  const span = e.target.parentElement.querySelector('span');
  span.textContent = e.target.value;
  displayGetUserMediaConstraints();
}
<commit_msg>constraints: prefer candidate.address to candidate.іp<commit_after>/*
 *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */

'use strict';

const getMediaButton = document.querySelector('button#getMedia');
const connectButton = document.querySelector('button#connect');
const hangupButton = document.querySelector('button#hangup');

getMediaButton.onclick = getMedia;
connectButton.onclick = createPeerConnection;
hangupButton.onclick = hangup;

const minWidthInput = document.querySelector('div#minWidth input');
const maxWidthInput = document.querySelector('div#maxWidth input');
const minHeightInput = document.querySelector('div#minHeight input');
const maxHeightInput = document.querySelector('div#maxHeight input');
const minFramerateInput = document.querySelector('div#minFramerate input');
const maxFramerateInput = document.querySelector('div#maxFramerate input');

minWidthInput.onchange = maxWidthInput.onchange =
  minHeightInput.onchange = maxHeightInput.onchange =
    minFramerateInput.onchange = maxFramerateInput.onchange = displayRangeValue;

const getUserMediaConstraintsDiv = document.querySelector('div#getUserMediaConstraints');
const bitrateDiv = document.querySelector('div#bitrate');
const peerDiv = document.querySelector('div#peer');
const senderStatsDiv = document.querySelector('div#senderStats');
const receiverStatsDiv = document.querySelector('div#receiverStats');

const localVideo = document.querySelector('div#localVideo video');
const remoteVideo = document.querySelector('div#remoteVideo video');
const localVideoStatsDiv = document.querySelector('div#localVideo div');
const remoteVideoStatsDiv = document.querySelector('div#remoteVideo div');

let localPeerConnection;
let remotePeerConnection;
let localStream;
let bytesPrev;
let timestampPrev;

main();

function main() {
  displayGetUserMediaConstraints();
}

function hangup() {
  console.log('Ending call');
  localPeerConnection.close();
  remotePeerConnection.close();

  // query stats one last time.
  Promise
      .all([
        remotePeerConnection
            .getStats(null)
            .then(showRemoteStats, err => console.log(err)),
        localPeerConnection
            .getStats(null)
            .then(showLocalStats, err => console.log(err))
      ])
      .then(() => {
        localPeerConnection = null;
        remotePeerConnection = null;
      });

  localStream.getTracks().forEach(track => track.stop());
  localStream = null;

  hangupButton.disabled = true;
  getMediaButton.disabled = false;
}

function getMedia() {
  getMediaButton.disabled = true;
  if (localStream) {
    localStream.getTracks().forEach(track => track.stop());
    const videoTracks = localStream.getVideoTracks();
    for (let i = 0; i !== videoTracks.length; ++i) {
      videoTracks[i].stop();
    }
  }
  navigator.mediaDevices.getUserMedia(getUserMediaConstraints())
      .then(gotStream)
      .catch(e => {
        const message = `getUserMedia error: ${e.name}\nPermissionDeniedError may mean invalid constraints.`;
        alert(message);
        console.log(message);
        getMediaButton.disabled = false;
      });
}

function gotStream(stream) {
  connectButton.disabled = false;
  console.log('GetUserMedia succeeded');
  localStream = stream;
  localVideo.srcObject = stream;
}

function getUserMediaConstraints() {
  const constraints = {};
  constraints.audio = true;
  constraints.video = {};
  if (minWidthInput.value !== '0') {
    constraints.video.width = {};
    constraints.video.width.min = minWidthInput.value;
  }
  if (maxWidthInput.value !== '0') {
    constraints.video.width = constraints.video.width || {};
    constraints.video.width.max = maxWidthInput.value;
  }
  if (minHeightInput.value !== '0') {
    constraints.video.height = {};
    constraints.video.height.min = minHeightInput.value;
  }
  if (maxHeightInput.value !== '0') {
    constraints.video.height = constraints.video.height || {};
    constraints.video.height.max = maxHeightInput.value;
  }
  if (minFramerateInput.value !== '0') {
    constraints.video.frameRate = {};
    constraints.video.frameRate.min = minFramerateInput.value;
  }
  if (maxFramerateInput.value !== '0') {
    constraints.video.frameRate = constraints.video.frameRate || {};
    constraints.video.frameRate.max = maxFramerateInput.value;
  }

  return constraints;
}

function displayGetUserMediaConstraints() {
  const constraints = getUserMediaConstraints();
  console.log('getUserMedia constraints', constraints);
  getUserMediaConstraintsDiv.textContent = JSON.stringify(constraints, null, '    ');
}

function createPeerConnection() {
  connectButton.disabled = true;
  hangupButton.disabled = false;

  bytesPrev = 0;
  timestampPrev = 0;
  localPeerConnection = new RTCPeerConnection(null);
  remotePeerConnection = new RTCPeerConnection(null);
  localStream.getTracks().forEach(track => localPeerConnection.addTrack(track, localStream));
  console.log('localPeerConnection creating offer');
  localPeerConnection.onnegotiationeeded = () => console.log('Negotiation needed - localPeerConnection');
  remotePeerConnection.onnegotiationeeded = () => console.log('Negotiation needed - remotePeerConnection');
  localPeerConnection.onicecandidate = e => {
    console.log('Candidate localPeerConnection');
    remotePeerConnection
        .addIceCandidate(e.candidate)
        .then(onAddIceCandidateSuccess, onAddIceCandidateError);
  };
  remotePeerConnection.onicecandidate = e => {
    console.log('Candidate remotePeerConnection');
    localPeerConnection
        .addIceCandidate(e.candidate)
        .then(onAddIceCandidateSuccess, onAddIceCandidateError);
  };
  remotePeerConnection.ontrack = e => {
    if (remoteVideo.srcObject !== e.streams[0]) {
      console.log('remotePeerConnection got stream');
      remoteVideo.srcObject = e.streams[0];
    }
  };
  localPeerConnection.createOffer().then(
      desc => {
        console.log('localPeerConnection offering');
        localPeerConnection.setLocalDescription(desc);
        remotePeerConnection.setRemoteDescription(desc);
        remotePeerConnection.createAnswer().then(
            desc2 => {
              console.log('remotePeerConnection answering');
              remotePeerConnection.setLocalDescription(desc2);
              localPeerConnection.setRemoteDescription(desc2);
            },
            err => console.log(err)
        );
      },
      err => console.log(err)
  );
}

function onAddIceCandidateSuccess() {
  console.log('AddIceCandidate success.');
}

function onAddIceCandidateError(error) {
  console.log(`Failed to add Ice Candidate: ${error.toString()}`);
}

function showRemoteStats(results) {
  const statsString = dumpStats(results);
  receiverStatsDiv.innerHTML = `<h2>Receiver stats</h2>${statsString}`;
  // calculate video bitrate
  results.forEach(report => {
    const now = report.timestamp;

    let bitrate;
    if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
      const bytes = report.bytesReceived;
      if (timestampPrev) {
        bitrate = 8 * (bytes - bytesPrev) / (now - timestampPrev);
        bitrate = Math.floor(bitrate);
      }
      bytesPrev = bytes;
      timestampPrev = now;
    }
    if (bitrate) {
      bitrate += ' kbits/sec';
      bitrateDiv.innerHTML = `<strong>Bitrate:</strong>${bitrate}`;
    }
  });

  // figure out the peer's ip
  let activeCandidatePair = null;
  let remoteCandidate = null;

  // Search for the candidate pair, spec-way first.
  results.forEach(report => {
    if (report.type === 'transport') {
      activeCandidatePair = results.get(report.selectedCandidatePairId);
    }
  });
  // Fallback for Firefox.
  if (!activeCandidatePair) {
    results.forEach(report => {
      if (report.type === 'candidate-pair' && report.selected) {
        activeCandidatePair = report;
      }
    });
  }
  if (activeCandidatePair && activeCandidatePair.remoteCandidateId) {
    remoteCandidate = results.get(activeCandidatePair.remoteCandidateId);
  }
  if (remoteCandidate) {
    if (remoteCandidate.address && remoteCandidate.port) {
      peerDiv.innerHTML = `<strong>Connected to:</strong>${remoteCandidate.address}:${remoteCandidate.port}`;
    } else if (remoteCandidate.ip && remoteCandidate.port) {
      peerDiv.innerHTML = `<strong>Connected to:</strong>${remoteCandidate.ip}:${remoteCandidate.port}`;
    } else if (remoteCandidate.ipAddress && remoteCandidate.portNumber) {
      // Fall back to old names.
      peerDiv.innerHTML = `<strong>Connected to:</strong>${remoteCandidate.ipAddress}:${remoteCandidate.portNumber}`;
    }
  }
}

function showLocalStats(results) {
  const statsString = dumpStats(results);
  senderStatsDiv.innerHTML = `<h2>Sender stats</h2>${statsString}`;
}

// Display statistics
setInterval(() => {
  if (localPeerConnection && remotePeerConnection) {
    remotePeerConnection
        .getStats(null)
        .then(showRemoteStats, err => console.log(err));
    localPeerConnection
        .getStats(null)
        .then(showLocalStats, err => console.log(err));
  } else {
    console.log('Not connected yet');
  }
  // Collect some stats from the video tags.
  if (localVideo.videoWidth) {
    const width = localVideo.videoWidth;
    const height = localVideo.videoHeight;
    localVideoStatsDiv.innerHTML = `<strong>Video dimensions:</strong> ${width}x${height}px`;
  }
  if (remoteVideo.videoWidth) {
    const rHeight = remoteVideo.videoHeight;
    const rWidth = remoteVideo.videoWidth;
    remoteVideoStatsDiv.innerHTML = `<strong>Video dimensions:</strong> ${rWidth}x${rHeight}px`;
  }
}, 1000);

// Dumping a stats variable as a string.
// might be named toString?
function dumpStats(results) {
  let statsString = '';
  results.forEach(res => {
    statsString += '
Report type=';
    statsString += res.type;
    statsString += '
\n';
    statsString += `id ${res.id}<br>`;
    statsString += `time ${res.timestamp}<br>`;
    Object.keys(res).forEach(k => {
      if (k !== 'timestamp' && k !== 'type' && k !== 'id') {
        statsString += `${k}: ${res[k]}<br>`;
      }
    });
  });
  return statsString;
}

// Utility to show the value of a range in a sibling span element
function displayRangeValue(e) {
  const span = e.target.parentElement.querySelector('span');
  span.textContent = e.target.value;
  displayGetUserMediaConstraints();
}
<|endoftext|>


<commit_before>import {
  isPlainObject
} from 'uni-shared'

const CLASS_RE = /^\s+|\s+$/g
const WXS_CLASS_RE = /\s+/

function getWxsClsArr (clsArr, classList, isAdd) {
  const wxsClsArr = []

  let checkClassList = function (cls) {
    if (isAdd) {
      checkClassList = function (cls) {
        return !classList.contains(cls)
      }
    } else {
      checkClassList = function (cls) {
        return classList.contains(cls)
      }
    }
    return checkClassList(cls)
  }

  clsArr.forEach(cls => {
    cls = cls.replace(CLASS_RE, '')
    checkClassList(cls) && wxsClsArr.push(cls)
  })
  return wxsClsArr
}

function parseStyleText (cssText) {
  const res = {}
  const listDelimiter = /;(?![^(]*\))/g
  const propertyDelimiter = /:(.+)/
  cssText.split(listDelimiter).forEach(function (item) {
    if (item) {
      const tmp = item.split(propertyDelimiter)
      tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
    }
  })
  return res
}

class ComponentDescriptor {
  constructor (vm) {
    this.$vm = vm
    this.$el = vm.$el
  }

  selectComponent (selector) {
    if (!this.$el || !selector) {
      return
    }
    const el = this.$el.querySelector(selector)
    return el && el.__vue__ && createComponentDescriptor(el.__vue__, false)
  }

  selectAllComponents (selector) {
    if (!this.$el || !selector) {
      return []
    }
    const descriptors = []
    const els = this.$el.querySelectorAll(selector)
    for (let i = 0; i < els.length; i++) {
      const el = els[i]
      el.__vue__ && descriptors.push(createComponentDescriptor(el.__vue__, false))
    }
    return descriptors
  }

  setStyle (style) {
    if (!this.$el || !style) {
      return this
    }
    if (typeof style === 'string') {
      style = parseStyleText(style)
    }
    if (isPlainObject(style)) {
      this.$el.__wxsStyle = style
      this.$vm.$forceUpdate()
    }
    return this
  }

  addClass (...clsArr) {
    if (!this.$el || !clsArr.length) {
      return this
    }

    const wxsClsArr = getWxsClsArr(clsArr, this.$el.classList, true)
    if (wxsClsArr.length) {
      const wxsClass = this.$el.__wxsAddClass || ''
      this.$el.__wxsAddClass = wxsClass + (wxsClass ? ' ' : '') + wxsClsArr.join(' ')
      this.$vm.$forceUpdate()
    }

    return this
  }

  removeClass (...clsArr) {
    if (!this.$el || !clsArr.length) {
      return this
    }
    const classList = this.$el.classList
    const addWxsClsArr = this.$el.__wxsAddClass ? this.$el.__wxsAddClass.split(WXS_CLASS_RE) : []
    const wxsClsArr = getWxsClsArr(clsArr, classList, false)
    if (wxsClsArr.length) {
      const removeWxsClsArr = []
      wxsClsArr.forEach(cls => {
        const clsIndex = addWxsClsArr.findIndex(oldCls => oldCls === cls)
        if (clsIndex !== -1) { // 在 addWxsClass 中
          addWxsClsArr.splice(clsIndex, 1)
        }
        removeWxsClsArr.push(cls)
      })
      this.$el.__wxsRemoveClass = removeWxsClsArr
      this.$el.__wxsAddClass = addWxsClsArr.join(' ')
      this.$vm.$forceUpdate()
    }

    return this
  }

  hasClass (cls) {
    return this.$el && this.$el.classList.contains(cls)
  }

  getDataset () {
    return this.$el && this.$el.dataset
  }

  callMethod (funcName, args = {}) {
    if (this.$vm[funcName]) {
      this.$vm[funcName](JSON.parse(JSON.stringify(args)))
    } else if (this.$vm._$id) {
      UniViewJSBridge.publishHandler('onWxsInvokeCallMethod', {
        cid: this.$vm._$id,
        method: funcName,
        args
      })
    }
  }

  requestAnimationFrame (callback) {
    return (global.requestAnimationFrame(callback), this)
  }

  getState () {
    return this.$el && (this.$el.__wxsState || (this.$el.__wxsState = {}))
  }

  triggerEvent (eventName, detail = {}, options = {}) {
    // TODO options
    return (this.$vm.$emit(eventName, detail), this)
  }
}

export function createComponentDescriptor (vm, isOwnerInstance = true) {
  if (isOwnerInstance && vm && vm.$options.name && vm.$options.name.indexOf('VUni') === 0) {
    // ownerInstance 内置组件需要使用父 vm
    vm = vm.$parent
  }
  if (vm && vm.$el) {
    if (!vm.$el.__wxsComponentDescriptor) {
      vm.$el.__wxsComponentDescriptor = new ComponentDescriptor(vm)
    }
    return vm.$el.__wxsComponentDescriptor
  }
}
<commit_msg>feat(wxs): add getComputedStyle<commit_after>import {
  isPlainObject
} from 'uni-shared'

const CLASS_RE = /^\s+|\s+$/g
const WXS_CLASS_RE = /\s+/

function getWxsClsArr (clsArr, classList, isAdd) {
  const wxsClsArr = []

  let checkClassList = function (cls) {
    if (isAdd) {
      checkClassList = function (cls) {
        return !classList.contains(cls)
      }
    } else {
      checkClassList = function (cls) {
        return classList.contains(cls)
      }
    }
    return checkClassList(cls)
  }

  clsArr.forEach(cls => {
    cls = cls.replace(CLASS_RE, '')
    checkClassList(cls) && wxsClsArr.push(cls)
  })
  return wxsClsArr
}

function parseStyleText (cssText) {
  const res = {}
  const listDelimiter = /;(?![^(]*\))/g
  const propertyDelimiter = /:(.+)/
  cssText.split(listDelimiter).forEach(function (item) {
    if (item) {
      const tmp = item.split(propertyDelimiter)
      tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
    }
  })
  return res
}

class ComponentDescriptor {
  constructor (vm) {
    this.$vm = vm
    this.$el = vm.$el
  }

  selectComponent (selector) {
    if (!this.$el || !selector) {
      return
    }
    const el = this.$el.querySelector(selector)
    return el && el.__vue__ && createComponentDescriptor(el.__vue__, false)
  }

  selectAllComponents (selector) {
    if (!this.$el || !selector) {
      return []
    }
    const descriptors = []
    const els = this.$el.querySelectorAll(selector)
    for (let i = 0; i < els.length; i++) {
      const el = els[i]
      el.__vue__ && descriptors.push(createComponentDescriptor(el.__vue__, false))
    }
    return descriptors
  }

  setStyle (style) {
    if (!this.$el || !style) {
      return this
    }
    if (typeof style === 'string') {
      style = parseStyleText(style)
    }
    if (isPlainObject(style)) {
      this.$el.__wxsStyle = style
      this.$vm.$forceUpdate()
    }
    return this
  }

  addClass (...clsArr) {
    if (!this.$el || !clsArr.length) {
      return this
    }

    const wxsClsArr = getWxsClsArr(clsArr, this.$el.classList, true)
    if (wxsClsArr.length) {
      const wxsClass = this.$el.__wxsAddClass || ''
      this.$el.__wxsAddClass = wxsClass + (wxsClass ? ' ' : '') + wxsClsArr.join(' ')
      this.$vm.$forceUpdate()
    }

    return this
  }

  removeClass (...clsArr) {
    if (!this.$el || !clsArr.length) {
      return this
    }
    const classList = this.$el.classList
    const addWxsClsArr = this.$el.__wxsAddClass ? this.$el.__wxsAddClass.split(WXS_CLASS_RE) : []
    const wxsClsArr = getWxsClsArr(clsArr, classList, false)
    if (wxsClsArr.length) {
      const removeWxsClsArr = []
      wxsClsArr.forEach(cls => {
        const clsIndex = addWxsClsArr.findIndex(oldCls => oldCls === cls)
        if (clsIndex !== -1) { // 在 addWxsClass 中
          addWxsClsArr.splice(clsIndex, 1)
        }
        removeWxsClsArr.push(cls)
      })
      this.$el.__wxsRemoveClass = removeWxsClsArr
      this.$el.__wxsAddClass = addWxsClsArr.join(' ')
      this.$vm.$forceUpdate()
    }

    return this
  }

  hasClass (cls) {
    return this.$el && this.$el.classList.contains(cls)
  }

  getComputedStyle () {
    if (this.$el) {
      return window.getComputedStyle(this.$el)
    }
    return {}
  }

  getDataset () {
    return this.$el && this.$el.dataset
  }

  callMethod (funcName, args = {}) {
    if (this.$vm[funcName]) {
      this.$vm[funcName](JSON.parse(JSON.stringify(args)))
    } else if (this.$vm._$id) {
      UniViewJSBridge.publishHandler('onWxsInvokeCallMethod', {
        cid: this.$vm._$id,
        method: funcName,
        args
      })
    }
  }

  requestAnimationFrame (callback) {
    return (global.requestAnimationFrame(callback), this)
  }

  getState () {
    return this.$el && (this.$el.__wxsState || (this.$el.__wxsState = {}))
  }

  triggerEvent (eventName, detail = {}, options = {}) {
    // TODO options
    return (this.$vm.$emit(eventName, detail), this)
  }
}

export function createComponentDescriptor (vm, isOwnerInstance = true) {
  if (isOwnerInstance && vm && vm.$options.name && vm.$options.name.indexOf('VUni') === 0) {
    // ownerInstance 内置组件需要使用父 vm
    vm = vm.$parent
  }
  if (vm && vm.$el) {
    if (!vm.$el.__wxsComponentDescriptor) {
      vm.$el.__wxsComponentDescriptor = new ComponentDescriptor(vm)
    }
    return vm.$el.__wxsComponentDescriptor
  }
}
<|endoftext|>


<commit_before>(function ($) {
  // register namespace
  $.extend(true, window, {
    "Slick": {
      "CellExternalCopyManager": CellExternalCopyManager
    }
  });

  function CellExternalCopyManager(options) {
    /*
      This manager enables users to copy/paste data from/to an external Spreadsheet application
      such as MS-Excel® or OpenOffice-Spreadsheet.

      Since it is not possible to access directly the clipboard in javascript, the plugin uses
      a trick to do it's job. After detecting the keystroke, we dynamically create a textarea
      where the browser copies/pastes the serialized data.

      options:
        copiedCellStyle : sets the css className used for copied cells. default : "copied"
        copiedCellStyleLayerKey : sets the layer key for setting css values of copied cells. default : "copy-manager"
        dataItemColumnValueExtractor : option to specify a custom column value extractor function
        dataItemColumnValueSetter : option to specify a custom column value setter function
        clipboardCommandHandler : option to specify a custom handler for paste actions
        includeHeaderWhenCopying : set to true and the plugin will take the name property from each column (which is usually what appears in your header) and put that as the first row of the text that's copied to the clipboard
        bodyElement: option to specify a custom DOM element which to will be added the hidden textbox. It's useful if the grid is inside a modal dialog.
    */
    var _grid;
    var _self = this;
    var _copiedRanges;
    var _options = options || {};
    var _copiedCellStyleLayerKey = _options.copiedCellStyleLayerKey || "copy-manager";
    var _copiedCellStyle = _options.copiedCellStyle || "copied";
    var _clearCopyTI = 0;
    var _bodyElement = _options.bodyElement || document.body;

    var keyCodes = {
      'C':67,
      'V':86,
      'ESC':27
    }

    function init(grid) {
      _grid = grid;
      _grid.onKeyDown.subscribe(handleKeyDown);

      // we need a cell selection model
      var cellSelectionModel = grid.getSelectionModel();
      if (!cellSelectionModel) {
        throw new Error("Selection model is mandatory for this plugin. Please set a selection model on the grid before adding this plugin: grid.setSelectionModel(new Slick.CellSelectionModel())");
      }
      // we give focus on the grid when a selection is done on it.
      // without this, if the user selects a range of cell without giving focus on a particular cell, the grid doesn't get the focus and key stroke handles (ctrl+c) don't work
      cellSelectionModel.onSelectedRangesChanged.subscribe(function(e, args){
        _grid.focus();
      });
    }

    function destroy() {
      _grid.onKeyDown.unsubscribe(handleKeyDown);
    }

    function getDataItemValueForColumn(item, columnDef) {
      if (_options.dataItemColumnValueExtractor) {
        return _options.dataItemColumnValueExtractor(item, columnDef);
      }

      var retVal = '';

      // use formatter if available; much faster than editor
      if (columnDef.formatter) {
          return columnDef.formatter(0, 0, item[columnDef.field], columnDef, item);
      }

      // if a custom getter is not defined, we call serializeValue of the editor to serialize
      if (columnDef.editor) {
        var editorArgs = {
          'container':$("body"),  // a dummy container
          'column':columnDef,
          'position':{'top':0, 'left':0}  // a dummy position required by some editors
        };
        var editor = new columnDef.editor(editorArgs);
        editor.loadValue(item);
        retVal = editor.serializeValue();
        editor.destroy();
      }
      else {
        retVal = item[columnDef.field];
      }

      return retVal;
    }

    function setDataItemValueForColumn(item, columnDef, value) {
      if (_options.dataItemColumnValueSetter) {
        return _options.dataItemColumnValueSetter(item, columnDef, value);
      }

      // if a custom setter is not defined, we call applyValue of the editor to unserialize
      if (columnDef.editor){
        var editorArgs = {
          'container':$("body"),  // a dummy container
          'column':columnDef,
          'position':{'top':0, 'left':0}  // a dummy position required by some editors
        };
        var editor = new columnDef.editor(editorArgs);
        editor.loadValue(item);
        editor.applyValue(item, value);
        editor.destroy();
      }
    }

    function _createTextBox(innerText){
      var ta = document.createElement('textarea');
      ta.style.position = 'absolute';
      ta.style.left = '-1000px';
      ta.style.top = document.body.scrollTop + 'px';
      ta.value = innerText;
      _bodyElement.appendChild(ta);
      ta.select();

      return ta;
    }

    function _decodeTabularData(_grid, ta){
      var columns = _grid.getColumns();
      var clipText = ta.value;
      var clipRows = clipText.split(/[\n\f\r]/);
      var clippedRange = [];

      _bodyElement.removeChild(ta);

      for (var i=0; i<clipRows.length; i++) {
        if (clipRows[i]!="")
          clippedRange[i] = clipRows[i].split("\t");
      }

      var selectedCell = _grid.getActiveCell();
      var ranges = _grid.getSelectionModel().getSelectedRanges();
      var selectedRange = ranges && ranges.length ? ranges[0] : null;   // pick only one selection
      var activeRow = null;
      var activeCell = null;

      if (selectedRange) {
        activeRow = selectedRange.fromRow;
        activeCell = selectedRange.fromCell;
      } else if (selectedCell) {
        activeRow = selectedCell.row;
        activeCell = selectedCell.cell;
      } else {
        // we don't know where to paste
        return;
      }

      var oneCellToMultiple = false;
      var destH = clippedRange.length;
      var destW = clippedRange.length ? clippedRange[0].length : 0;
      if (clippedRange.length == 1 && clippedRange[0].length == 1 && selectedRange){
        oneCellToMultiple = true;
        destH = selectedRange.toRow - selectedRange.fromRow +1;
        destW = selectedRange.toCell - selectedRange.fromCell +1;
      }
      var availableRows = _grid.getData().length - activeRow;
      var addRows = 0;
      if(availableRows < destH) {
        var d = _grid.getData();
        for(addRows = 1; addRows <= destH - availableRows; addRows++)
            d.push({});
        _grid.setData(d);
        _grid.render();
      }
      var clipCommand = {
        isClipboardCommand: true,
        clippedRange: clippedRange,
        oldValues: [],
        cellExternalCopyManager: _self,
        _options: _options,
        setDataItemValueForColumn: setDataItemValueForColumn,
        markCopySelection: markCopySelection,
        oneCellToMultiple: oneCellToMultiple,
        activeRow: activeRow,
        activeCell: activeCell,
        destH: destH,
        destW: destW,
        desty: activeRow,
        destx: activeCell,
        maxDestY: _grid.getDataLength(),
        maxDestX: _grid.getColumns().length,
        h: 0,
        w: 0,

        execute: function() {
          this.h = 0;
          for (var y = 0; y < destH; y++) {
            this.oldValues[y] = [];
            this.w = 0;
            this.h++;
            for (var x = 0; x < destW; x++) {
              this.w++;
              var desty = activeRow + y;
              var destx = activeCell + x;

              if (desty < this.maxDestY && destx < this.maxDestX) {
                var nd = _grid.getCellNode(desty, destx);
                var dt = _grid.getDataItem(desty);
                this.oldValues[y][x] = dt[columns[destx]['id']];
                if (oneCellToMultiple)
                  this.setDataItemValueForColumn(dt, columns[destx], clippedRange[0][0]);
                else
                  this.setDataItemValueForColumn(dt, columns[destx], clippedRange[y] ? clippedRange[y][x] : '');
                _grid.updateCell(desty, destx);
              }
            }
          }

          var bRange = {
            'fromCell': activeCell,
            'fromRow': activeRow,
            'toCell': activeCell+this.w-1,
            'toRow': activeRow+this.h-1
          }

          this.markCopySelection([bRange]);
          _grid.getSelectionModel().setSelectedRanges([bRange]);
          this.cellExternalCopyManager.onPasteCells.notify({ranges: [bRange]});
        },

        undo: function() {
          for (var y = 0; y < destH; y++) {
            for (var x = 0; x < destW; x++) {
              var desty = activeRow + y;
              var destx = activeCell + x;

              if (desty < this.maxDestY && destx < this.maxDestX ) {
                var nd = _grid.getCellNode(desty, destx);
                var dt = _grid.getDataItem(desty);
                if (oneCellToMultiple)
                  this.setDataItemValueForColumn(dt, columns[destx], this.oldValues[0][0]);
                else
                  this.setDataItemValueForColumn(dt, columns[destx], this.oldValues[y][x]);
                _grid.updateCell(desty, destx);
              }
            }
          }

          var bRange = {
            'fromCell': activeCell,
            'fromRow': activeRow,
            'toCell': activeCell + this.w - 1,
            'toRow': activeRow + this.h - 1
          }

          this.markCopySelection([bRange]);
          _grid.getSelectionModel().setSelectedRanges([bRange]);
          this.cellExternalCopyManager.onPasteCells.notify({ranges: [bRange]});

          if (addRows > 1) {
            var d = _grid.getData();
            for ( ; addRows > 1; addRows--)
              d.splice(d.length - 1, 1);
            _grid.setData(d);
            _grid.render();
          }
        }
      };

      if (_options.clipboardCommandHandler) {
        _options.clipboardCommandHandler(clipCommand);
      }
      else {
        clipCommand.execute();
      }
    }

    function handleKeyDown(e, args) {
      var ranges;
      if (!_grid.getEditorLock().isActive() || _grid.getOptions().autoEdit) {
        if (e.which == keyCodes.ESC) {
          if (_copiedRanges) {
            e.preventDefault();
            clearCopySelection();
            _self.onCopyCancelled.notify({ranges: _copiedRanges});
            _copiedRanges = null;
          }
        }

        if (e.which == keyCodes.C && (e.ctrlKey || e.metaKey)) {    // CTRL + C
          ranges = _grid.getSelectionModel().getSelectedRanges();
          if (ranges.length != 0) {
            _copiedRanges = ranges;
            markCopySelection(ranges);
            _self.onCopyCells.notify({ranges: ranges});

            var columns = _grid.getColumns();
            var clipText = "";

            for (var rg = 0; rg < ranges.length; rg++) {
                var range = ranges[rg];
                var clipTextRows = [];
                for (var i = range.fromRow; i < range.toRow+1 ; i++) {
                    var clipTextCells = [];
                    var dt = _grid.getDataItem(i);

                    if (clipText == "" && _options.includeHeaderWhenCopying) {
                        var clipTextHeaders = [];
                        for (var j = range.fromCell; j < range.toCell + 1 ; j++) {
                            if (columns[j].name.length > 0)
                                clipTextHeaders.push(columns[j].name);
                        }
                        clipTextRows.push(clipTextHeaders.join("\t"));
                    }

                    for (var j=range.fromCell; j< range.toCell+1 ; j++){
                        clipTextCells.push(getDataItemValueForColumn(dt, columns[j]));
                    }
                    clipTextRows.push(clipTextCells.join("\t"));
                }
                clipText += clipTextRows.join("\r\n") + "\r\n";
            }

            var $focus = $(_grid.getActiveCellNode());
            var ta = _createTextBox(clipText);

            ta.focus();

            setTimeout(function(){
                _bodyElement.removeChild(ta);
                // restore focus
                if ($focus && $focus.length > 0) {
                    $focus.attr('tabIndex', '-1');
                    $focus.focus();
                    $focus.removeAttr('tabIndex');
                }
            }, 100);

            return false;
          }
        }

        if (e.which == keyCodes.V && (e.ctrlKey || e.metaKey)) {    // CTRL + V
            var ta = _createTextBox('');

            setTimeout(function() {
                _decodeTabularData(_grid, ta);
            }, 100);

            return false;
        }
      }
    }

    function markCopySelection(ranges) {
      clearCopySelection();

      var columns = _grid.getColumns();
      var hash = {};
      for (var i = 0; i < ranges.length; i++) {
        for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
          hash[j] = {};
          for (var k = ranges[i].fromCell; k <= ranges[i].toCell && k<columns.length; k++) {
            hash[j][columns[k].id] = _copiedCellStyle;
          }
        }
      }
      _grid.setCellCssStyles(_copiedCellStyleLayerKey, hash);
      clearTimeout(_clearCopyTI);
      _clearCopyTI = setTimeout(function() {
        _self.clearCopySelection();
      }, 2000);
    }

    function clearCopySelection() {
      _grid.removeCellCssStyles(_copiedCellStyleLayerKey);
    }

    $.extend(this, {
      "init": init,
      "destroy": destroy,
      "clearCopySelection": clearCopySelection,
      "handleKeyDown":handleKeyDown,

      "onCopyCells": new Slick.Event(),
      "onCopyCancelled": new Slick.Event(),
      "onPasteCells": new Slick.Event()
    });
  }
})(jQuery);
<commit_msg>Using an actual dummy container<commit_after>(function ($) {
  // register namespace
  $.extend(true, window, {
    "Slick": {
      "CellExternalCopyManager": CellExternalCopyManager
    }
  });

  function CellExternalCopyManager(options) {
    /*
      This manager enables users to copy/paste data from/to an external Spreadsheet application
      such as MS-Excel® or OpenOffice-Spreadsheet.

      Since it is not possible to access directly the clipboard in javascript, the plugin uses
      a trick to do it's job. After detecting the keystroke, we dynamically create a textarea
      where the browser copies/pastes the serialized data.

      options:
        copiedCellStyle : sets the css className used for copied cells. default : "copied"
        copiedCellStyleLayerKey : sets the layer key for setting css values of copied cells. default : "copy-manager"
        dataItemColumnValueExtractor : option to specify a custom column value extractor function
        dataItemColumnValueSetter : option to specify a custom column value setter function
        clipboardCommandHandler : option to specify a custom handler for paste actions
        includeHeaderWhenCopying : set to true and the plugin will take the name property from each column (which is usually what appears in your header) and put that as the first row of the text that's copied to the clipboard
        bodyElement: option to specify a custom DOM element which to will be added the hidden textbox. It's useful if the grid is inside a modal dialog.
    */
    var _grid;
    var _self = this;
    var _copiedRanges;
    var _options = options || {};
    var _copiedCellStyleLayerKey = _options.copiedCellStyleLayerKey || "copy-manager";
    var _copiedCellStyle = _options.copiedCellStyle || "copied";
    var _clearCopyTI = 0;
    var _bodyElement = _options.bodyElement || document.body;

    var keyCodes = {
      'C':67,
      'V':86,
      'ESC':27
    }

    function init(grid) {
      _grid = grid;
      _grid.onKeyDown.subscribe(handleKeyDown);

      // we need a cell selection model
      var cellSelectionModel = grid.getSelectionModel();
      if (!cellSelectionModel) {
        throw new Error("Selection model is mandatory for this plugin. Please set a selection model on the grid before adding this plugin: grid.setSelectionModel(new Slick.CellSelectionModel())");
      }
      // we give focus on the grid when a selection is done on it.
      // without this, if the user selects a range of cell without giving focus on a particular cell, the grid doesn't get the focus and key stroke handles (ctrl+c) don't work
      cellSelectionModel.onSelectedRangesChanged.subscribe(function(e, args){
        _grid.focus();
      });
    }

    function destroy() {
      _grid.onKeyDown.unsubscribe(handleKeyDown);
    }

    function getDataItemValueForColumn(item, columnDef) {
      if (_options.dataItemColumnValueExtractor) {
        return _options.dataItemColumnValueExtractor(item, columnDef);
      }

      var retVal = '';

      // use formatter if available; much faster than editor
      if (columnDef.formatter) {
          return columnDef.formatter(0, 0, item[columnDef.field], columnDef, item);
      }

      // if a custom getter is not defined, we call serializeValue of the editor to serialize
      if (columnDef.editor) {
        var editorArgs = {
          'container':$("<p>"),  // a dummy container
          'column':columnDef,
          'position':{'top':0, 'left':0}  // a dummy position required by some editors
        };
        var editor = new columnDef.editor(editorArgs);
        editor.loadValue(item);
        retVal = editor.serializeValue();
        editor.destroy();
      }
      else {
        retVal = item[columnDef.field];
      }

      return retVal;
    }

    function setDataItemValueForColumn(item, columnDef, value) {
      if (_options.dataItemColumnValueSetter) {
        return _options.dataItemColumnValueSetter(item, columnDef, value);
      }

      // if a custom setter is not defined, we call applyValue of the editor to unserialize
      if (columnDef.editor){
        var editorArgs = {
          'container':$("body"),  // a dummy container
          'column':columnDef,
          'position':{'top':0, 'left':0}  // a dummy position required by some editors
        };
        var editor = new columnDef.editor(editorArgs);
        editor.loadValue(item);
        editor.applyValue(item, value);
        editor.destroy();
      }
    }

    function _createTextBox(innerText){
      var ta = document.createElement('textarea');
      ta.style.position = 'absolute';
      ta.style.left = '-1000px';
      ta.style.top = document.body.scrollTop + 'px';
      ta.value = innerText;
      _bodyElement.appendChild(ta);
      ta.select();

      return ta;
    }

    function _decodeTabularData(_grid, ta){
      var columns = _grid.getColumns();
      var clipText = ta.value;
      var clipRows = clipText.split(/[\n\f\r]/);
      var clippedRange = [];

      _bodyElement.removeChild(ta);

      for (var i=0; i<clipRows.length; i++) {
        if (clipRows[i]!="")
          clippedRange[i] = clipRows[i].split("\t");
      }

      var selectedCell = _grid.getActiveCell();
      var ranges = _grid.getSelectionModel().getSelectedRanges();
      var selectedRange = ranges && ranges.length ? ranges[0] : null;   // pick only one selection
      var activeRow = null;
      var activeCell = null;

      if (selectedRange) {
        activeRow = selectedRange.fromRow;
        activeCell = selectedRange.fromCell;
      } else if (selectedCell) {
        activeRow = selectedCell.row;
        activeCell = selectedCell.cell;
      } else {
        // we don't know where to paste
        return;
      }

      var oneCellToMultiple = false;
      var destH = clippedRange.length;
      var destW = clippedRange.length ? clippedRange[0].length : 0;
      if (clippedRange.length == 1 && clippedRange[0].length == 1 && selectedRange){
        oneCellToMultiple = true;
        destH = selectedRange.toRow - selectedRange.fromRow +1;
        destW = selectedRange.toCell - selectedRange.fromCell +1;
      }
      var availableRows = _grid.getData().length - activeRow;
      var addRows = 0;
      if(availableRows < destH) {
        var d = _grid.getData();
        for(addRows = 1; addRows <= destH - availableRows; addRows++)
            d.push({});
        _grid.setData(d);
        _grid.render();
      }
      var clipCommand = {
        isClipboardCommand: true,
        clippedRange: clippedRange,
        oldValues: [],
        cellExternalCopyManager: _self,
        _options: _options,
        setDataItemValueForColumn: setDataItemValueForColumn,
        markCopySelection: markCopySelection,
        oneCellToMultiple: oneCellToMultiple,
        activeRow: activeRow,
        activeCell: activeCell,
        destH: destH,
        destW: destW,
        desty: activeRow,
        destx: activeCell,
        maxDestY: _grid.getDataLength(),
        maxDestX: _grid.getColumns().length,
        h: 0,
        w: 0,

        execute: function() {
          this.h = 0;
          for (var y = 0; y < destH; y++) {
            this.oldValues[y] = [];
            this.w = 0;
            this.h++;
            for (var x = 0; x < destW; x++) {
              this.w++;
              var desty = activeRow + y;
              var destx = activeCell + x;

              if (desty < this.maxDestY && destx < this.maxDestX) {
                var nd = _grid.getCellNode(desty, destx);
                var dt = _grid.getDataItem(desty);
                this.oldValues[y][x] = dt[columns[destx]['id']];
                if (oneCellToMultiple)
                  this.setDataItemValueForColumn(dt, columns[destx], clippedRange[0][0]);
                else
                  this.setDataItemValueForColumn(dt, columns[destx], clippedRange[y] ? clippedRange[y][x] : '');
                _grid.updateCell(desty, destx);
              }
            }
          }

          var bRange = {
            'fromCell': activeCell,
            'fromRow': activeRow,
            'toCell': activeCell+this.w-1,
            'toRow': activeRow+this.h-1
          }

          this.markCopySelection([bRange]);
          _grid.getSelectionModel().setSelectedRanges([bRange]);
          this.cellExternalCopyManager.onPasteCells.notify({ranges: [bRange]});
        },

        undo: function() {
          for (var y = 0; y < destH; y++) {
            for (var x = 0; x < destW; x++) {
              var desty = activeRow + y;
              var destx = activeCell + x;

              if (desty < this.maxDestY && destx < this.maxDestX ) {
                var nd = _grid.getCellNode(desty, destx);
                var dt = _grid.getDataItem(desty);
                if (oneCellToMultiple)
                  this.setDataItemValueForColumn(dt, columns[destx], this.oldValues[0][0]);
                else
                  this.setDataItemValueForColumn(dt, columns[destx], this.oldValues[y][x]);
                _grid.updateCell(desty, destx);
              }
            }
          }

          var bRange = {
            'fromCell': activeCell,
            'fromRow': activeRow,
            'toCell': activeCell + this.w - 1,
            'toRow': activeRow + this.h - 1
          }

          this.markCopySelection([bRange]);
          _grid.getSelectionModel().setSelectedRanges([bRange]);
          this.cellExternalCopyManager.onPasteCells.notify({ranges: [bRange]});

          if (addRows > 1) {
            var d = _grid.getData();
            for ( ; addRows > 1; addRows--)
              d.splice(d.length - 1, 1);
            _grid.setData(d);
            _grid.render();
          }
        }
      };

      if (_options.clipboardCommandHandler) {
        _options.clipboardCommandHandler(clipCommand);
      }
      else {
        clipCommand.execute();
      }
    }

    function handleKeyDown(e, args) {
      var ranges;
      if (!_grid.getEditorLock().isActive() || _grid.getOptions().autoEdit) {
        if (e.which == keyCodes.ESC) {
          if (_copiedRanges) {
            e.preventDefault();
            clearCopySelection();
            _self.onCopyCancelled.notify({ranges: _copiedRanges});
            _copiedRanges = null;
          }
        }

        if (e.which == keyCodes.C && (e.ctrlKey || e.metaKey)) {    // CTRL + C
          ranges = _grid.getSelectionModel().getSelectedRanges();
          if (ranges.length != 0) {
            _copiedRanges = ranges;
            markCopySelection(ranges);
            _self.onCopyCells.notify({ranges: ranges});

            var columns = _grid.getColumns();
            var clipText = "";

            for (var rg = 0; rg < ranges.length; rg++) {
                var range = ranges[rg];
                var clipTextRows = [];
                for (var i = range.fromRow; i < range.toRow+1 ; i++) {
                    var clipTextCells = [];
                    var dt = _grid.getDataItem(i);

                    if (clipText == "" && _options.includeHeaderWhenCopying) {
                        var clipTextHeaders = [];
                        for (var j = range.fromCell; j < range.toCell + 1 ; j++) {
                            if (columns[j].name.length > 0)
                                clipTextHeaders.push(columns[j].name);
                        }
                        clipTextRows.push(clipTextHeaders.join("\t"));
                    }

                    for (var j=range.fromCell; j< range.toCell+1 ; j++){
                        clipTextCells.push(getDataItemValueForColumn(dt, columns[j]));
                    }
                    clipTextRows.push(clipTextCells.join("\t"));
                }
                clipText += clipTextRows.join("\r\n") + "\r\n";
            }

            var $focus = $(_grid.getActiveCellNode());
            var ta = _createTextBox(clipText);

            ta.focus();

            setTimeout(function(){
                _bodyElement.removeChild(ta);
                // restore focus
                if ($focus && $focus.length > 0) {
                    $focus.attr('tabIndex', '-1');
                    $focus.focus();
                    $focus.removeAttr('tabIndex');
                }
            }, 100);

            return false;
          }
        }

        if (e.which == keyCodes.V && (e.ctrlKey || e.metaKey)) {    // CTRL + V
            var ta = _createTextBox('');

            setTimeout(function() {
                _decodeTabularData(_grid, ta);
            }, 100);

            return false;
        }
      }
    }

    function markCopySelection(ranges) {
      clearCopySelection();

      var columns = _grid.getColumns();
      var hash = {};
      for (var i = 0; i < ranges.length; i++) {
        for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
          hash[j] = {};
          for (var k = ranges[i].fromCell; k <= ranges[i].toCell && k<columns.length; k++) {
            hash[j][columns[k].id] = _copiedCellStyle;
          }
        }
      }
      _grid.setCellCssStyles(_copiedCellStyleLayerKey, hash);
      clearTimeout(_clearCopyTI);
      _clearCopyTI = setTimeout(function() {
        _self.clearCopySelection();
      }, 2000);
    }

    function clearCopySelection() {
      _grid.removeCellCssStyles(_copiedCellStyleLayerKey);
    }

    $.extend(this, {
      "init": init,
      "destroy": destroy,
      "clearCopySelection": clearCopySelection,
      "handleKeyDown":handleKeyDown,

      "onCopyCells": new Slick.Event(),
      "onCopyCancelled": new Slick.Event(),
      "onPasteCells": new Slick.Event()
    });
  }
})(jQuery);
<|endoftext|>


<commit_before>$(function() {
    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }

    var csrftoken = getCookie('csrftoken');
    var $button = $('.js-subscribe');

    $button.click(function() {
        $.ajax({
            url: '/subscribe/',
            data: JSON.stringify({
                subscribe: $button.html() === 'Subscribe',
                podcast: 1
            }),
            contentType: 'application/json',
            type: 'POST',
            headers: {
                'X-CSRFToken': csrftoken
            },
            success: function(data) {
                if (data.status === 'subscribed') {
                    $button.html('Unsubscribe');
                    $button.removeClass('btn-primary');
                    $button.addClass('btn-default');
                } else if (data.status === 'unsubscribed') {
                    $button.html('Subscribe');
                    $button.removeClass('btn-default');
                    $button.addClass('btn-primary');
                }
            }
        });
    });
});<commit_msg>Fix so the Subscribe button sends the right podcast ID<commit_after>$(function() {
    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }

    var csrftoken = getCookie('csrftoken');
    var $button = $('.js-subscribe');

    $button.click(function() {
        $.ajax({
            url: '/subscribe/',
            data: JSON.stringify({
                subscribe: $button.html() === 'Subscribe',
                podcast: window.location.pathname.split('/')[2]
            }),
            contentType: 'application/json',
            type: 'POST',
            headers: {
                'X-CSRFToken': csrftoken
            },
            success: function(data) {
                if (data.status === 'subscribed') {
                    $button.html('Unsubscribe');
                    $button.removeClass('btn-primary');
                    $button.addClass('btn-default');
                } else if (data.status === 'unsubscribed') {
                    $button.html('Subscribe');
                    $button.removeClass('btn-default');
                    $button.addClass('btn-primary');
                }
            }
        });
    });
});<|endoftext|>


<commit_before>// @flow

import React, { Fragment, useContext, useEffect } from 'react';
import { BridgeContext } from './context';
import { ModalDialogContext } from './ModalDialog';

import styles from './WarnIfLegacyBackendDetected.css';

export default function WarnIfLegacyBackendDetected(_: {||}) {
  const bridge = useContext(BridgeContext);
  const { dispatch } = useContext(ModalDialogContext);

  // Detect pairing with legacy v3 backend.
  // We do this by listening to a message that it broadcasts but the v4 backend doesn't.
  // In this case the frontend should show upgrade instructions.
  useEffect(() => {
    // Wall.listen returns a cleanup function
    let unlisten = bridge.wall.listen(event => {
      switch (event.type) {
        case 'call':
        case 'event':
        case 'many-events':
          // Any of these types indicate the v3 backend.
          dispatch({
            canBeDismissed: false,
            type: 'SHOW',
            title: 'DevTools v4 is incompatible with this version of React',
            content: <InvalidBackendDetected />,
          });

          if (typeof unlisten === 'function') {
            unlisten();
            unlisten = null;
          }
          break;
        default:
          break;
      }
    });

    return () => {
      if (typeof unlisten === 'function') {
        unlisten();
        unlisten = null;
      }
    };
  }, [bridge, dispatch]);

  return null;
}

function InvalidBackendDetected(_: {||}) {
  return (
    <Fragment>

Either upgrade React or install React DevTools v3:

      npm install -d react-devtools@^3
    </Fragment>
  );
}
<commit_msg>Cleanup legacy backend event listener if modern backend is detected<commit_after>// @flow

import React, { Fragment, useContext, useEffect } from 'react';
import { BridgeContext } from './context';
import { ModalDialogContext } from './ModalDialog';

import styles from './WarnIfLegacyBackendDetected.css';

export default function WarnIfLegacyBackendDetected(_: {||}) {
  const bridge = useContext(BridgeContext);
  const { dispatch } = useContext(ModalDialogContext);

  // Detect pairing with legacy v3 backend.
  // We do this by listening to a message that it broadcasts but the v4 backend doesn't.
  // In this case the frontend should show upgrade instructions.
  useEffect(() => {
    // Wall.listen returns a cleanup function
    let unlisten = bridge.wall.listen(message => {
      switch (message.type) {
        case 'call':
        case 'event':
        case 'many-events':
          // Any of these types indicate the v3 backend.
          dispatch({
            canBeDismissed: false,
            type: 'SHOW',
            title: 'DevTools v4 is incompatible with this version of React',
            content: <InvalidBackendDetected />,
          });

          // Once we've identified the backend version, it's safe to unsubscribe.
          if (typeof unlisten === 'function') {
            unlisten();
            unlisten = null;
          }
          break;
        default:
          break;
      }

      switch (message.event) {
        case 'isBackendStorageAPISupported':
        case 'isNativeStyleEditorSupported':
        case 'operations':
        case 'overrideComponentFilters':
          // Any of these is sufficient to indicate a v4 backend.
          // Once we've identified the backend version, it's safe to unsubscribe.
          if (typeof unlisten === 'function') {
            unlisten();
            unlisten = null;
          }
          break;
        default:
          break;
      }
    });

    return () => {
      if (typeof unlisten === 'function') {
        unlisten();
        unlisten = null;
      }
    };
  }, [bridge, dispatch]);

  return null;
}

function InvalidBackendDetected(_: {||}) {
  return (
    <Fragment>

Either upgrade React or install React DevTools v3:

      npm install -d react-devtools@^3
    </Fragment>
  );
}
<|endoftext|>