<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import Editor from '../editor/Editor'
import PostContainer from '../../containers/PostContainer'
import StreamContainer from '../../containers/StreamContainer'
import { MainView } from '../views/MainView'
import { loadRelatedPosts } from '../../actions/posts'
import { LaunchMobileCommentEditorButton, LaunchNativeCommentEditorButton } from '../posts/PostRenderables'
import { css, hover, media, modifier, select } from '../../styles/jss'
import * as s from '../../styles/jso'
import * as ElloAndroidInterface from '../../lib/android_interface'

const postDetailStyle = css(
  s.relative,
  select('& .ViewsTool.isPill > a', s.colorA, { backgroundColor: 'transparent' }, hover({ backgroundColor: 'transparent' })),
  select('& .PostDetails.Posts.asList aside .PostDetailAsideTop',
    select('& header', s.px20, { borderBottom: '1px solid #f2f2f2' }),
    select('& footer', s.px20, { borderBottom: '1px solid #f2f2f2' }),
  ),
  select('& .PostDetails.Posts.asList .PostList > .Post, & .PostDetails.Posts.asList > aside .PostDetailAsideTop',
    select('& .PostToolValue', s.fontSize18, s.ml10),
    select('& .PostTool .SVGIcon + .PostToolValue', s.fontSize18, { marginLeft: 15 }),
    select('& .PostHeaderTimeAgoLink',
      media(s.minBreak3,
        s.block,
        s.pointerNone,
        { right: 20 },
      ),
    ),
    select('& .PostTools',
      s.flex, s.justifySpaceBetween, s.itemsCenter, s.p0, { maxWidth: 310 },
      media(s.minBreak3, s.px10, { maxWidth: 640 }),
      select('& .ShyTool',
        s.absolute,
        s.pointerAuto,
        s.opacity1,
        { top: -50 },
        modifier('.EditTool', { right: 110 }),
        modifier('.DeleteTool', { right: 70 }),
      ),
      select('& .SVGIcon', { transform: 'scale(1.5)' }, select('& > g', { strokeWidth: 0.9375 })),
      select('& .CommentTool', s.displayNone),
      select('& .WatchTool', s.displayNone),
      select('& .FlagTool', s.displayNone),
      select('& .TimeAgoTool', s.displayNone),
      select('& .ShareTool', { position: 'static' }),
      select('& .ViewsTool', s.pointerNone, modifier('.isPill', { marginRight: '0 !important' })),
    ),
  ),
  select('& .PostDetails.Posts.asList .PostDetailAsideBottom',
    select('& .PostTools', s.px0, s.mt40, s.flex, s.justifyStart, media(s.minBreak3, s.justifyEnd, s.px10)),
    select('& .PostTool', s.displayNone,
      modifier('.WatchTool', s.block),
      modifier('.FlagTool', s.block, s.opacity1, s.pointerAuto),
    ),
  ),
)

const streamStyle = css(
  s.px10,
  s.overflowScroll,
  { height: 'calc(100vh - 80px)', paddingBottom: 80 },
  media(
    s.minBreak2,
    s.px20,
  ),
  media(
    s.minBreak3,
    { width: 'calc(100vw - 360px)' },
    select('& .PostBody > div', s.flex, s.flexColumn, s.justifyCenter, s.itemsCenter, s.pt20),
  ),
  select('.PostDetails & .TabListStreamContainer', s.px0),
)

const relatedPostsStyle = css(
  select('& .Column',
    media(s.minBreak3, { width: 'calc(50% - 20px)' }),
    media(s.minBreak4, { width: 'calc(33.33333% - 40px)' }),
    media(s.minBreak5, { width: 'calc(25% - 40px)' }),
    media(s.minBreak6, { width: 'calc(20% - 40px)' }),
    media(s.minBreak7, { width: 'calc(16.66666% - 40px)' }),
  ),
)

const asideStyle = css(
  s.absolute,
  s.fullHeight,
  { width: 360, borderLeft: '1px solid #f2f2f2', top: 0, right: 0, overflowY: 'scroll', paddingBottom: 80 },
  select('& .CommentContent', s.m20),
  select('.PostDetails & .TabListStreamContainer', s.px0),
  select('& .UserProfileCard',
    media(s.minBreak3, s.mt20, { width: 'calc(100% - 40px)' }),
  ),
)

const CommentContent = (
  { activeType, avatar, hasEditor, isInlineCommenting, isLoggedIn, post, streamAction },
  { onToggleInlineCommenting },
  ) => (

      {!isInlineCommenting &&
        <LaunchMobileCommentEditorButton avatar={avatar} post={post} />
      }
      {isInlineCommenting && hasEditor && activeType === 'comments' && !ElloAndroidInterface.supportsNativeEditor() &&
        <Editor post={post} isComment onCancel={onToggleInlineCommenting} />
      }
      {isLoggedIn && ElloAndroidInterface.supportsNativeEditor() &&
        <LaunchNativeCommentEditorButton avatar={avatar} post={post} />
      }
      {streamAction &&
        <StreamContainer
          action={streamAction}
          className="TabListStreamContainer"
          key={`TabListStreamContainer_${activeType}`}
          paginatorText="Load More"
          shouldInfiniteScroll={false}
        />
      }

  )
CommentContent.propTypes = {
  activeType: PropTypes.string.isRequired,
  avatar: PropTypes.object,
  hasEditor: PropTypes.bool.isRequired,
  isInlineCommenting: PropTypes.bool,
  isLoggedIn: PropTypes.bool.isRequired,
  post: PropTypes.object.isRequired,
  streamAction: PropTypes.object,
}
CommentContent.defaultProps = {
  avatar: null,
  isInlineCommenting: false,
  streamAction: null,
}
CommentContent.contextTypes = {
  onToggleInlineCommenting: PropTypes.func.isRequired,
}

// TODO: Remove references to the PostDetailStreamContainer styles
export const PostDetail = (props) => {
  const { columnCount, post, shouldInlineComments } = props
  return (
    <MainView className={`PostDetail ${postDetailStyle}`}>

        <article className={`PostList ${streamStyle}`} id={`Post_${post.get('id')}`}>
          <PostContainer type={shouldInlineComments ? null : 'PostDetailBody'} postId={post.get('id')} />
          {shouldInlineComments && <CommentContent {...props} />}
          <StreamContainer
            action={loadRelatedPosts(`~${post.get('token')}`, columnCount > 2 ? columnCount - 1 : columnCount)}
            className={`RelatedPostsStreamContainer ${relatedPostsStyle}`}
            shouldInfiniteScroll={false}
          />
          {shouldInlineComments && <PostContainer type="PostDetailAsideBottom" postId={post.get('id')} />}

        {!shouldInlineComments &&

            <PostContainer type="PostDetailAsideTop" postId={post.get('id')} />
            <CommentContent {...props} />
            <PostContainer type="PostDetailAsideBottom" postId={post.get('id')} />

        }

    </MainView>
  )
}
PostDetail.propTypes = {
  columnCount: PropTypes.number.isRequired,
  post: PropTypes.object.isRequired,
  shouldInlineComments: PropTypes.bool.isRequired,
}

export const PostDetailError = ({ children }) =>
  (<MainView className="PostDetail">

      {children}

  </MainView>)
PostDetailError.propTypes = {
  children: PropTypes.node.isRequired,
}

<commit_msg>refactor to clarify editor/show-editor-button logic<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import Editor from '../editor/Editor'
import PostContainer from '../../containers/PostContainer'
import StreamContainer from '../../containers/StreamContainer'
import { MainView } from '../views/MainView'
import { loadRelatedPosts } from '../../actions/posts'
import { LaunchMobileCommentEditorButton, LaunchNativeCommentEditorButton } from '../posts/PostRenderables'
import { css, hover, media, modifier, select } from '../../styles/jss'
import * as s from '../../styles/jso'
import * as ElloAndroidInterface from '../../lib/android_interface'

const postDetailStyle = css(
  s.relative,
  select('& .ViewsTool.isPill > a', s.colorA, { backgroundColor: 'transparent' }, hover({ backgroundColor: 'transparent' })),
  select('& .PostDetails.Posts.asList aside .PostDetailAsideTop',
    select('& header', s.px20, { borderBottom: '1px solid #f2f2f2' }),
    select('& footer', s.px20, { borderBottom: '1px solid #f2f2f2' }),
  ),
  select('& .PostDetails.Posts.asList .PostList > .Post, & .PostDetails.Posts.asList > aside .PostDetailAsideTop',
    select('& .PostToolValue', s.fontSize18, s.ml10),
    select('& .PostTool .SVGIcon + .PostToolValue', s.fontSize18, { marginLeft: 15 }),
    select('& .PostHeaderTimeAgoLink',
      media(s.minBreak3,
        s.block,
        s.pointerNone,
        { right: 20 },
      ),
    ),
    select('& .PostTools',
      s.flex, s.justifySpaceBetween, s.itemsCenter, s.p0, { maxWidth: 310 },
      media(s.minBreak3, s.px10, { maxWidth: 640 }),
      select('& .ShyTool',
        s.absolute,
        s.pointerAuto,
        s.opacity1,
        { top: -50 },
        modifier('.EditTool', { right: 110 }),
        modifier('.DeleteTool', { right: 70 }),
      ),
      select('& .SVGIcon', { transform: 'scale(1.5)' }, select('& > g', { strokeWidth: 0.9375 })),
      select('& .CommentTool', s.displayNone),
      select('& .WatchTool', s.displayNone),
      select('& .FlagTool', s.displayNone),
      select('& .TimeAgoTool', s.displayNone),
      select('& .ShareTool', { position: 'static' }),
      select('& .ViewsTool', s.pointerNone, modifier('.isPill', { marginRight: '0 !important' })),
    ),
  ),
  select('& .PostDetails.Posts.asList .PostDetailAsideBottom',
    select('& .PostTools', s.px0, s.mt40, s.flex, s.justifyStart, media(s.minBreak3, s.justifyEnd, s.px10)),
    select('& .PostTool', s.displayNone,
      modifier('.WatchTool', s.block),
      modifier('.FlagTool', s.block, s.opacity1, s.pointerAuto),
    ),
  ),
)

const streamStyle = css(
  s.px10,
  s.overflowScroll,
  { height: 'calc(100vh - 80px)', paddingBottom: 80 },
  media(
    s.minBreak2,
    s.px20,
  ),
  media(
    s.minBreak3,
    { width: 'calc(100vw - 360px)' },
    select('& .PostBody > div', s.flex, s.flexColumn, s.justifyCenter, s.itemsCenter, s.pt20),
  ),
  select('.PostDetails & .TabListStreamContainer', s.px0),
)

const relatedPostsStyle = css(
  select('& .Column',
    media(s.minBreak3, { width: 'calc(50% - 20px)' }),
    media(s.minBreak4, { width: 'calc(33.33333% - 40px)' }),
    media(s.minBreak5, { width: 'calc(25% - 40px)' }),
    media(s.minBreak6, { width: 'calc(20% - 40px)' }),
    media(s.minBreak7, { width: 'calc(16.66666% - 40px)' }),
  ),
)

const asideStyle = css(
  s.absolute,
  s.fullHeight,
  { width: 360, borderLeft: '1px solid #f2f2f2', top: 0, right: 0, overflowY: 'scroll', paddingBottom: 80 },
  select('& .CommentContent', s.m20),
  select('.PostDetails & .TabListStreamContainer', s.px0),
  select('& .UserProfileCard',
    media(s.minBreak3, s.mt20, { width: 'calc(100% - 40px)' }),
  ),
)

const CommentContent = (
  { activeType, avatar, hasEditor, isInlineCommenting, isLoggedIn, post, streamAction },
  { onToggleInlineCommenting },
  ) => {
  let editorOrButton = null
  if (isLoggedIn && ElloAndroidInterface.supportsNativeEditor()) {
    editorOrButton = <LaunchNativeCommentEditorButton avatar={avatar} post={post} />
  } else if (hasEditor && activeType === 'comments') {
    if (isInlineCommenting) {
      editorOrButton = <Editor post={post} isComment onCancel={onToggleInlineCommenting} />
    } else {
      editorOrButton = <LaunchMobileCommentEditorButton avatar={avatar} post={post} />
    }
  }
  return (

      {editorOrButton}
      {streamAction &&
        <StreamContainer
          action={streamAction}
          className="TabListStreamContainer"
          key={`TabListStreamContainer_${activeType}`}
          paginatorText="Load More"
          shouldInfiniteScroll={false}
        />
      }

  )
}
CommentContent.propTypes = {
  activeType: PropTypes.string.isRequired,
  avatar: PropTypes.object,
  hasEditor: PropTypes.bool.isRequired,
  isInlineCommenting: PropTypes.bool,
  isLoggedIn: PropTypes.bool.isRequired,
  post: PropTypes.object.isRequired,
  streamAction: PropTypes.object,
}
CommentContent.defaultProps = {
  avatar: null,
  isInlineCommenting: false,
  streamAction: null,
}
CommentContent.contextTypes = {
  onToggleInlineCommenting: PropTypes.func.isRequired,
}

// TODO: Remove references to the PostDetailStreamContainer styles
export const PostDetail = (props) => {
  const { columnCount, post, shouldInlineComments } = props
  return (
    <MainView className={`PostDetail ${postDetailStyle}`}>

        <article className={`PostList ${streamStyle}`} id={`Post_${post.get('id')}`}>
          <PostContainer type={shouldInlineComments ? null : 'PostDetailBody'} postId={post.get('id')} />
          {shouldInlineComments && <CommentContent {...props} />}
          <StreamContainer
            action={loadRelatedPosts(`~${post.get('token')}`, columnCount > 2 ? columnCount - 1 : columnCount)}
            className={`RelatedPostsStreamContainer ${relatedPostsStyle}`}
            shouldInfiniteScroll={false}
          />
          {shouldInlineComments && <PostContainer type="PostDetailAsideBottom" postId={post.get('id')} />}

        {!shouldInlineComments &&

            <PostContainer type="PostDetailAsideTop" postId={post.get('id')} />
            <CommentContent {...props} isInlineCommenting />
            <PostContainer type="PostDetailAsideBottom" postId={post.get('id')} />

        }

    </MainView>
  )
}
PostDetail.propTypes = {
  columnCount: PropTypes.number.isRequired,
  post: PropTypes.object.isRequired,
  shouldInlineComments: PropTypes.bool.isRequired,
}

export const PostDetailError = ({ children }) =>
  (<MainView className="PostDetail">

      {children}

  </MainView>)
PostDetailError.propTypes = {
  children: PropTypes.node.isRequired,
}

<|endoftext|>


<commit_before>'use strict';
const electron = require('electron');
const elementReady = require('element-ready');
const config = require('./config');

const {ipcRenderer: ipc} = electron;

const listSelector = 'div[role="navigation"] > div > ul';
const conversationSelector = '._4u-c._1wfr > ._5f0v.uiScrollableArea';
const selectedConversationSelector = '._5l-3._1ht1._1ht2';

function showSettingsMenu() {
	document.querySelector('._30yy._2fug._p').click();
}

function selectMenuItem(itemNumber) {
	const selector = document.querySelector(`._54nq._2i-c._558b._2n_z li:nth-child(${itemNumber}) a`);
	selector.click();
}

function selectOtherListViews(itemNumber) {
	// In case one of other views is shown
	clickBackButton();

	// Create the menu for the below
	showSettingsMenu();

	selectMenuItem(itemNumber);
}

function clickBackButton() {
	const backButton = document.querySelector('._30yy._2oc9');
	if (backButton) {
		backButton.click();
	}
}

ipc.on('show-preferences', async () => {
	if (isPreferencesOpen()) {
		return;
	}

	await openPreferences();
});

ipc.on('new-conversation', () => {
	document.querySelector('._30yy[data-href$=\'/new\']').click();
});

ipc.on('log-out', () => {
	if (config.get('useWorkChat')) {
		// Create the menu for the below
		document.querySelector('._5lxs._3qct._p').click();
		// Menu creation is slow
		setTimeout(() => {
			const nodes = document.querySelectorAll('._54nq._9jo._558b._2n_z li:last-child a');
			nodes[nodes.length - 1].click();
		}, 250);
	} else {
		showSettingsMenu();
		const nodes = document.querySelectorAll('._54nq._2i-c._558b._2n_z li:last-child a');
		nodes[nodes.length - 1].click();
	}
});

ipc.on('find', () => {
	document.querySelector('._58al').focus();
});

ipc.on('search', () => {
	document.querySelector('._3szn:nth-of-type(1)').click();
});

ipc.on('insert-gif', () => {
	document.querySelector('._yht').click();
});

ipc.on('insert-emoji', () => {
	document.querySelector('._5s2p').click();
});

ipc.on('insert-text', () => {
	document.querySelector('._5rpu').focus();
});

ipc.on('next-conversation', nextConversation);

ipc.on('previous-conversation', previousConversation);

ipc.on('mute-conversation', () => {
	openMuteModal();
});

ipc.on('delete-conversation', () => {
	openDeleteModal();
});

ipc.on('archive-conversation', () => {
	openArchiveModal();
});

function setSidebarVisibility() {
	document.documentElement.classList.toggle('sidebar-hidden', config.get('sidebarHidden'));
	ipc.send('set-sidebar-visibility');
}

ipc.on('toggle-mute-notifications', async (event, defaultStatus) => {
	const preferencesAreOpen = isPreferencesOpen();
	if (!preferencesAreOpen) {
		await openPreferences();
	}

	const notificationCheckbox = document.querySelector('._374b:nth-of-type(3) ._4ng2 input');

	if (defaultStatus === undefined) {
		notificationCheckbox.click();
	} else if ((defaultStatus && notificationCheckbox.checked) || (!defaultStatus && !notificationCheckbox.checked)) {
		notificationCheckbox.click();
	}

	ipc.send('mute-notifications-toggled', !notificationCheckbox.checked);

	if (!preferencesAreOpen) {
		closePreferences();
	}
});

ipc.on('toggle-message-buttons', async () => {
	const messageButtons = await elementReady('._39bj');
	messageButtons.style.display = config.get('showMessageButtons') ? 'flex' : 'none';
});

ipc.on('show-active-contacts-view', () => {
	selectOtherListViews(3);
});

ipc.on('show-message-requests-view', () => {
	selectOtherListViews(4);
});

ipc.on('show-archived-threads-view', () => {
	selectOtherListViews(5);
});

ipc.on('toggle-unread-threads-view', () => {
	selectOtherListViews(6);
});

function setDarkMode() {
	document.documentElement.classList.toggle('dark-mode', config.get('darkMode'));
	/// ipc.send('set-vibrancy');
}

/// function setVibrancy() {
// 	document.documentElement.classList.toggle('vibrancy', config.get('vibrancy'));
// 	ipc.send('set-vibrancy');
// }

function renderOverlayIcon(messageCount) {
	const canvas = document.createElement('canvas');
	canvas.height = 128;
	canvas.width = 128;
	canvas.style.letterSpacing = '-5px';
	const ctx = canvas.getContext('2d');
	ctx.fillStyle = '#f42020';
	ctx.beginPath();
	ctx.ellipse(64, 64, 64, 64, 0, 0, 2 * Math.PI);
	ctx.fill();
	ctx.textAlign = 'center';
	ctx.fillStyle = 'white';
	ctx.font = '90px sans-serif';
	ctx.fillText(String(Math.min(99, messageCount)), 64, 96);
	return canvas;
}

ipc.on('toggle-sidebar', () => {
	config.set('sidebarHidden', !config.get('sidebarHidden'));
	setSidebarVisibility();
});

ipc.on('set-dark-mode', setDarkMode);

// Disabled because of https://github.com/electron/electron/issues/10886
// and other vibrancy bugs with Electron v2
/// ipc.on('toggle-vibrancy', () => {
// 	config.set('vibrancy', !config.get('vibrancy'));
// 	setVibrancy();

// 	document.documentElement.style.backgroundColor = 'transparent';
// });

ipc.on('render-overlay-icon', (event, messageCount) => {
	ipc.send('update-overlay-icon', renderOverlayIcon(messageCount).toDataURL(), String(messageCount));
});

ipc.on('zoom-reset', () => {
	setZoom(1.0);
});

ipc.on('zoom-in', () => {
	const zoomFactor = config.get('zoomFactor') + 0.1;

	if (zoomFactor < 1.6) {
		setZoom(zoomFactor);
	}
});

ipc.on('zoom-out', () => {
	const zoomFactor = config.get('zoomFactor') - 0.1;

	if (zoomFactor >= 0.8) {
		setZoom(zoomFactor);
	}
});

ipc.on('jump-to-conversation', (event, index) => {
	jumpToConversation(index);
});

function nextConversation() {
	const index = getNextIndex(true);
	selectConversation(index);
}

function previousConversation() {
	const index = getNextIndex(false);
	selectConversation(index);
}

function jumpToConversation(key) {
	const index = key - 1;
	selectConversation(index);
}

// Focus on the conversation with the given index
function selectConversation(index) {
	document.querySelector(listSelector).children[index].firstChild.firstChild.click();
}

// Returns the index of the selected conversation.
// If no conversation is selected, returns null.
function getIndex() {
	const selected = document.querySelector(selectedConversationSelector);
	if (!selected) {
		return null;
	}

	const list = [...selected.parentNode.children];

	return list.indexOf(selected);
}

// Return the index for next node if next is true,
// else returns index for the previous node
function getNextIndex(next) {
	const selected = document.querySelector(selectedConversationSelector);
	if (!selected) {
		return 0;
	}

	const list = [...selected.parentNode.children];
	const index = list.indexOf(selected) + (next ? 1 : -1);

	return ((index % list.length) + list.length) % list.length;
}

function setZoom(zoomFactor) {
	const node = document.getElementById('zoomFactor');
	node.textContent = `${conversationSelector} {zoom: ${zoomFactor} !important}`;
	config.set('zoomFactor', zoomFactor);
}

function openConversationMenu() {
	const index = getIndex();
	if (index === null) {
		return false;
	}

	// Open and close the menu for the below
	const menu = document.querySelectorAll('._2j6._5l-3 ._3d85')[index].firstChild;
	menu.click();

	return true;
}

function openMuteModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(1);
}

function openArchiveModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(3);
}

function openDeleteModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(4);
}

async function openPreferences() {
	// Create the menu for the below
	(await elementReady('._30yy._2fug._p')).click();

	selectMenuItem(1);
}

function isPreferencesOpen() {
	return Boolean(document.querySelector('._3quh._30yy._2t_._5ixy'));
}

function closePreferences() {
	const doneButton = document.querySelector('._3quh._30yy._2t_._5ixy');
	doneButton.click();
}
async function sendConversationList() {
	const sidebar = document.querySelector('[role=navigation]');

	const conversations = await Promise.all(
		[...sidebar.querySelectorAll('._1ht1')]
			.splice(0, 10)
			.map(async el => ({
				label: el.querySelector('._1ht6').textContent,
				selected: el.classList.contains('_1ht2'),
				unread: el.classList.contains('_1ht3'),
				icon: await getDataUrlFromImg(
					el.querySelector('._55lt img'),
					el.classList.contains('_1ht3')
				)
			}))
	);

	ipc.send('conversations', conversations);
}

// Return canvas with rounded image
function urlToCanvas(url, size) {
	return new Promise(resolve => {
		const img = new Image();
		img.crossOrigin = 'anonymous';
		img.addEventListener('load', () => {
			const canvas = document.createElement('canvas');
			const padding = {
				top: 3,
				right: 0,
				bottom: 3,
				left: 0
			};

			canvas.width = size + padding.left + padding.right;
			canvas.height = size + padding.top + padding.bottom;

			const ctx = canvas.getContext('2d');

			ctx.save();
			ctx.beginPath();
			ctx.arc((size / 2) + padding.left, (size / 2) + padding.top, size / 2, 0, Math.PI * 2, true);
			ctx.closePath();
			ctx.clip();

			ctx.drawImage(img, padding.left, padding.top, size, size);

			ctx.restore();

			resolve(canvas);
		});
		img.src = url;
	});
}

// Return data url for user avatar
function getDataUrlFromImg(img, unread) {
	return new Promise(async resolve => {
		if (!unread) {
			if (img.dataUrl) {
				return resolve(img.dataUrl);
			}
		} else if (img.dataUnreadUrl) {
			return resolve(img.dataUnreadUrl);
		}

		const canvas = await urlToCanvas(img.src, 30);
		const ctx = canvas.getContext('2d');
		img.dataUrl = canvas.toDataURL();

		if (!unread) {
			return resolve(img.dataUrl);
		}

		const markerSize = 8;
		ctx.fillStyle = '#f42020';
		ctx.beginPath();
		ctx.ellipse(canvas.width - markerSize, markerSize, markerSize, markerSize, 0, 0, 2 * Math.PI);
		ctx.fill();
		img.dataUnreadUrl = canvas.toDataURL();
		resolve(img.dataUnreadUrl);
	});
}

// Inject a global style node to maintain custom appearance after conversation change or startup
document.addEventListener('DOMContentLoaded', () => {
	const style = document.createElement('style');
	style.id = 'zoomFactor';
	document.body.appendChild(style);

	// Set the zoom factor if it was set before quitting
	const zoomFactor = config.get('zoomFactor') || 1.0;
	setZoom(zoomFactor);

	// Enable OS specific styles
	document.documentElement.classList.add(`os-${process.platform}`);

	// Hide sidebar if it was hidden before quitting
	setSidebarVisibility();

	// Activate Dark Mode if it was set before quitting
	setDarkMode();

	// Prevent flash of white on startup when in dark mode
	// TODO: find a CSS-only solution
	if (config.get('darkMode')) {
		document.documentElement.style.backgroundColor = '#192633';
	}

	// Activate vibrancy effect if it was set before quitting
	// setVibrancy();
});

window.addEventListener('load', () => {
	const sidebar = document.querySelector('[role=navigation]');

	sendConversationList();

	const conversationListObserver = new MutationObserver(sendConversationList);
	conversationListObserver.observe(sidebar, {
		subtree: true,
		childList: true,
		attributes: true,
		attributeFilter: ['class']
	});
});

// It's not possible to add multiple accelerators
// so this needs to be done the old-school way
document.addEventListener('keydown', event => {
	const combineKey = process.platform === 'darwin' ? event.metaKey : event.ctrlKey;

	if (!combineKey) {
		return;
	}

	if (event.key === ']') {
		nextConversation();
	}

	if (event.key === '[') {
		previousConversation();
	}

	const num = parseInt(event.key, 10);

	if (num >= 1 && num <= 9) {
		jumpToConversation(num);
	}
});
<commit_msg>Fixed sound/notifications option saving (#523)<commit_after>'use strict';
const electron = require('electron');
const elementReady = require('element-ready');
const config = require('./config');

const {ipcRenderer: ipc} = electron;

const listSelector = 'div[role="navigation"] > div > ul';
const conversationSelector = '._4u-c._1wfr > ._5f0v.uiScrollableArea';
const selectedConversationSelector = '._5l-3._1ht1._1ht2';

function showSettingsMenu() {
	document.querySelector('._30yy._2fug._p').click();
}

function selectMenuItem(itemNumber) {
	const selector = document.querySelector(`._54nq._2i-c._558b._2n_z li:nth-child(${itemNumber}) a`);
	selector.click();
}

function selectOtherListViews(itemNumber) {
	// In case one of other views is shown
	clickBackButton();

	// Create the menu for the below
	showSettingsMenu();

	selectMenuItem(itemNumber);
}

function clickBackButton() {
	const backButton = document.querySelector('._30yy._2oc9');
	if (backButton) {
		backButton.click();
	}
}

ipc.on('show-preferences', async () => {
	if (isPreferencesOpen()) {
		return;
	}

	await openPreferences();
});

ipc.on('new-conversation', () => {
	document.querySelector('._30yy[data-href$=\'/new\']').click();
});

ipc.on('log-out', () => {
	if (config.get('useWorkChat')) {
		// Create the menu for the below
		document.querySelector('._5lxs._3qct._p').click();
		// Menu creation is slow
		setTimeout(() => {
			const nodes = document.querySelectorAll('._54nq._9jo._558b._2n_z li:last-child a');
			nodes[nodes.length - 1].click();
		}, 250);
	} else {
		showSettingsMenu();
		const nodes = document.querySelectorAll('._54nq._2i-c._558b._2n_z li:last-child a');
		nodes[nodes.length - 1].click();
	}
});

ipc.on('find', () => {
	document.querySelector('._58al').focus();
});

ipc.on('search', () => {
	document.querySelector('._3szn:nth-of-type(1)').click();
});

ipc.on('insert-gif', () => {
	document.querySelector('._yht').click();
});

ipc.on('insert-emoji', () => {
	document.querySelector('._5s2p').click();
});

ipc.on('insert-text', () => {
	document.querySelector('._5rpu').focus();
});

ipc.on('next-conversation', nextConversation);

ipc.on('previous-conversation', previousConversation);

ipc.on('mute-conversation', () => {
	openMuteModal();
});

ipc.on('delete-conversation', () => {
	openDeleteModal();
});

ipc.on('archive-conversation', () => {
	openArchiveModal();
});

function setSidebarVisibility() {
	document.documentElement.classList.toggle('sidebar-hidden', config.get('sidebarHidden'));
	ipc.send('set-sidebar-visibility');
}

ipc.on('toggle-mute-notifications', async (event, defaultStatus) => {
	const preferencesAreOpen = isPreferencesOpen();
	if (!preferencesAreOpen) {
		await openPreferences();
	}

	const notificationCheckbox = document.querySelector('._374b:nth-of-type(4) ._4ng2 input');

	if (defaultStatus === undefined) {
		notificationCheckbox.click();
	} else if ((defaultStatus && notificationCheckbox.checked) || (!defaultStatus && !notificationCheckbox.checked)) {
		notificationCheckbox.click();
	}

	ipc.send('mute-notifications-toggled', !notificationCheckbox.checked);

	if (!preferencesAreOpen) {
		closePreferences();
	}
});

ipc.on('toggle-message-buttons', async () => {
	const messageButtons = await elementReady('._39bj');
	messageButtons.style.display = config.get('showMessageButtons') ? 'flex' : 'none';
});

ipc.on('show-active-contacts-view', () => {
	selectOtherListViews(3);
});

ipc.on('show-message-requests-view', () => {
	selectOtherListViews(4);
});

ipc.on('show-archived-threads-view', () => {
	selectOtherListViews(5);
});

ipc.on('toggle-unread-threads-view', () => {
	selectOtherListViews(6);
});

function setDarkMode() {
	document.documentElement.classList.toggle('dark-mode', config.get('darkMode'));
	/// ipc.send('set-vibrancy');
}

/// function setVibrancy() {
// 	document.documentElement.classList.toggle('vibrancy', config.get('vibrancy'));
// 	ipc.send('set-vibrancy');
// }

function renderOverlayIcon(messageCount) {
	const canvas = document.createElement('canvas');
	canvas.height = 128;
	canvas.width = 128;
	canvas.style.letterSpacing = '-5px';
	const ctx = canvas.getContext('2d');
	ctx.fillStyle = '#f42020';
	ctx.beginPath();
	ctx.ellipse(64, 64, 64, 64, 0, 0, 2 * Math.PI);
	ctx.fill();
	ctx.textAlign = 'center';
	ctx.fillStyle = 'white';
	ctx.font = '90px sans-serif';
	ctx.fillText(String(Math.min(99, messageCount)), 64, 96);
	return canvas;
}

ipc.on('toggle-sidebar', () => {
	config.set('sidebarHidden', !config.get('sidebarHidden'));
	setSidebarVisibility();
});

ipc.on('set-dark-mode', setDarkMode);

// Disabled because of https://github.com/electron/electron/issues/10886
// and other vibrancy bugs with Electron v2
/// ipc.on('toggle-vibrancy', () => {
// 	config.set('vibrancy', !config.get('vibrancy'));
// 	setVibrancy();

// 	document.documentElement.style.backgroundColor = 'transparent';
// });

ipc.on('render-overlay-icon', (event, messageCount) => {
	ipc.send('update-overlay-icon', renderOverlayIcon(messageCount).toDataURL(), String(messageCount));
});

ipc.on('zoom-reset', () => {
	setZoom(1.0);
});

ipc.on('zoom-in', () => {
	const zoomFactor = config.get('zoomFactor') + 0.1;

	if (zoomFactor < 1.6) {
		setZoom(zoomFactor);
	}
});

ipc.on('zoom-out', () => {
	const zoomFactor = config.get('zoomFactor') - 0.1;

	if (zoomFactor >= 0.8) {
		setZoom(zoomFactor);
	}
});

ipc.on('jump-to-conversation', (event, index) => {
	jumpToConversation(index);
});

function nextConversation() {
	const index = getNextIndex(true);
	selectConversation(index);
}

function previousConversation() {
	const index = getNextIndex(false);
	selectConversation(index);
}

function jumpToConversation(key) {
	const index = key - 1;
	selectConversation(index);
}

// Focus on the conversation with the given index
function selectConversation(index) {
	document.querySelector(listSelector).children[index].firstChild.firstChild.click();
}

// Returns the index of the selected conversation.
// If no conversation is selected, returns null.
function getIndex() {
	const selected = document.querySelector(selectedConversationSelector);
	if (!selected) {
		return null;
	}

	const list = [...selected.parentNode.children];

	return list.indexOf(selected);
}

// Return the index for next node if next is true,
// else returns index for the previous node
function getNextIndex(next) {
	const selected = document.querySelector(selectedConversationSelector);
	if (!selected) {
		return 0;
	}

	const list = [...selected.parentNode.children];
	const index = list.indexOf(selected) + (next ? 1 : -1);

	return ((index % list.length) + list.length) % list.length;
}

function setZoom(zoomFactor) {
	const node = document.getElementById('zoomFactor');
	node.textContent = `${conversationSelector} {zoom: ${zoomFactor} !important}`;
	config.set('zoomFactor', zoomFactor);
}

function openConversationMenu() {
	const index = getIndex();
	if (index === null) {
		return false;
	}

	// Open and close the menu for the below
	const menu = document.querySelectorAll('._2j6._5l-3 ._3d85')[index].firstChild;
	menu.click();

	return true;
}

function openMuteModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(1);
}

function openArchiveModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(3);
}

function openDeleteModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(4);
}

async function openPreferences() {
	// Create the menu for the below
	(await elementReady('._30yy._2fug._p')).click();

	selectMenuItem(1);
}

function isPreferencesOpen() {
	return Boolean(document.querySelector('._3quh._30yy._2t_._5ixy'));
}

function closePreferences() {
	const doneButton = document.querySelector('._3quh._30yy._2t_._5ixy');
	doneButton.click();
}
async function sendConversationList() {
	const sidebar = document.querySelector('[role=navigation]');

	const conversations = await Promise.all(
		[...sidebar.querySelectorAll('._1ht1')]
			.splice(0, 10)
			.map(async el => ({
				label: el.querySelector('._1ht6').textContent,
				selected: el.classList.contains('_1ht2'),
				unread: el.classList.contains('_1ht3'),
				icon: await getDataUrlFromImg(
					el.querySelector('._55lt img'),
					el.classList.contains('_1ht3')
				)
			}))
	);

	ipc.send('conversations', conversations);
}

// Return canvas with rounded image
function urlToCanvas(url, size) {
	return new Promise(resolve => {
		const img = new Image();
		img.crossOrigin = 'anonymous';
		img.addEventListener('load', () => {
			const canvas = document.createElement('canvas');
			const padding = {
				top: 3,
				right: 0,
				bottom: 3,
				left: 0
			};

			canvas.width = size + padding.left + padding.right;
			canvas.height = size + padding.top + padding.bottom;

			const ctx = canvas.getContext('2d');

			ctx.save();
			ctx.beginPath();
			ctx.arc((size / 2) + padding.left, (size / 2) + padding.top, size / 2, 0, Math.PI * 2, true);
			ctx.closePath();
			ctx.clip();

			ctx.drawImage(img, padding.left, padding.top, size, size);

			ctx.restore();

			resolve(canvas);
		});
		img.src = url;
	});
}

// Return data url for user avatar
function getDataUrlFromImg(img, unread) {
	return new Promise(async resolve => {
		if (!unread) {
			if (img.dataUrl) {
				return resolve(img.dataUrl);
			}
		} else if (img.dataUnreadUrl) {
			return resolve(img.dataUnreadUrl);
		}

		const canvas = await urlToCanvas(img.src, 30);
		const ctx = canvas.getContext('2d');
		img.dataUrl = canvas.toDataURL();

		if (!unread) {
			return resolve(img.dataUrl);
		}

		const markerSize = 8;
		ctx.fillStyle = '#f42020';
		ctx.beginPath();
		ctx.ellipse(canvas.width - markerSize, markerSize, markerSize, markerSize, 0, 0, 2 * Math.PI);
		ctx.fill();
		img.dataUnreadUrl = canvas.toDataURL();
		resolve(img.dataUnreadUrl);
	});
}

// Inject a global style node to maintain custom appearance after conversation change or startup
document.addEventListener('DOMContentLoaded', () => {
	const style = document.createElement('style');
	style.id = 'zoomFactor';
	document.body.appendChild(style);

	// Set the zoom factor if it was set before quitting
	const zoomFactor = config.get('zoomFactor') || 1.0;
	setZoom(zoomFactor);

	// Enable OS specific styles
	document.documentElement.classList.add(`os-${process.platform}`);

	// Hide sidebar if it was hidden before quitting
	setSidebarVisibility();

	// Activate Dark Mode if it was set before quitting
	setDarkMode();

	// Prevent flash of white on startup when in dark mode
	// TODO: find a CSS-only solution
	if (config.get('darkMode')) {
		document.documentElement.style.backgroundColor = '#192633';
	}

	// Activate vibrancy effect if it was set before quitting
	// setVibrancy();
});

window.addEventListener('load', () => {
	const sidebar = document.querySelector('[role=navigation]');

	sendConversationList();

	const conversationListObserver = new MutationObserver(sendConversationList);
	conversationListObserver.observe(sidebar, {
		subtree: true,
		childList: true,
		attributes: true,
		attributeFilter: ['class']
	});
});

// It's not possible to add multiple accelerators
// so this needs to be done the old-school way
document.addEventListener('keydown', event => {
	const combineKey = process.platform === 'darwin' ? event.metaKey : event.ctrlKey;

	if (!combineKey) {
		return;
	}

	if (event.key === ']') {
		nextConversation();
	}

	if (event.key === '[') {
		previousConversation();
	}

	const num = parseInt(event.key, 10);

	if (num >= 1 && num <= 9) {
		jumpToConversation(num);
	}
});
<|endoftext|>


<commit_before>'use strict';
const electron = require('electron');
const elementReady = require('element-ready');
const config = require('./config');

const {ipcRenderer: ipc} = electron;

const listSelector = 'div[role="navigation"] > div > ul';
const conversationSelector = '._4u-c._1wfr > ._5f0v.uiScrollableArea';
const selectedConversationSelector = '._5l-3._1ht1._1ht2';

function showSettingsMenu() {
	document.querySelector('._30yy._2fug._p').click();
}

function selectMenuItem(itemNumber) {
	const selector = document.querySelector(`._54nq._2i-c._558b._2n_z li:nth-child(${itemNumber}) a`);
	selector.click();
}

function selectOtherListViews(itemNumber) {
	// In case one of other views is shown
	clickBackButton();

	// Create the menu for the below
	showSettingsMenu();

	selectMenuItem(itemNumber);
}

function clickBackButton() {
	const backButton = document.querySelector('._30yy._2oc9');
	if (backButton) {
		backButton.click();
	}
}

ipc.on('show-preferences', async () => {
	if (isPreferencesOpen()) {
		return;
	}

	await openPreferences();
});

ipc.on('new-conversation', () => {
	document.querySelector('._30yy[data-href$=\'/new\']').click();
});

ipc.on('log-out', () => {
	if (config.get('useWorkChat')) {
		// Create the menu for the below
		document.querySelector('._5lxs._3qct._p').click();
		// Menu creation is slow
		setTimeout(() => {
			const nodes = document.querySelectorAll('._54nq._9jo._558b._2n_z li:last-child a');
			nodes[nodes.length - 1].click();
		}, 250);
	} else {
		showSettingsMenu();
		const nodes = document.querySelectorAll('._54nq._2i-c._558b._2n_z li:last-child a');
		nodes[nodes.length - 1].click();
	}
});

ipc.on('find', () => {
	document.querySelector('._58al').focus();
});

ipc.on('search', () => {
	document.querySelector('._3szn:nth-of-type(1)').click();
});

ipc.on('insert-gif', () => {
	document.querySelector('._yht').click();
});

ipc.on('insert-emoji', () => {
	document.querySelector('._5s2p').click();
});

ipc.on('insert-text', () => {
	document.querySelector('._5rpu').focus();
});

ipc.on('next-conversation', nextConversation);

ipc.on('previous-conversation', previousConversation);

ipc.on('mute-conversation', () => {
	openMuteModal();
});

ipc.on('delete-conversation', () => {
	openDeleteModal();
});

ipc.on('archive-conversation', () => {
	openArchiveModal();
});

function setSidebarVisibility() {
	document.documentElement.classList.toggle('sidebar-hidden', config.get('sidebarHidden'));
	ipc.send('set-sidebar-visibility');
}

ipc.on('toggle-mute-notifications', async (event, defaultStatus) => {
	const preferencesAreOpen = isPreferencesOpen();
	if (!preferencesAreOpen) {
		await openPreferences();
	}

	const notificationCheckbox = document.querySelector('._374b:nth-of-type(4) ._4ng2 input');

	if (defaultStatus === undefined) {
		notificationCheckbox.click();
	} else if ((defaultStatus && notificationCheckbox.checked) || (!defaultStatus && !notificationCheckbox.checked)) {
		notificationCheckbox.click();
	}

	ipc.send('mute-notifications-toggled', !notificationCheckbox.checked);

	if (!preferencesAreOpen) {
		closePreferences();
	}
});

ipc.on('toggle-message-buttons', async () => {
	const messageButtons = await elementReady('._39bj');
	messageButtons.style.display = config.get('showMessageButtons') ? 'flex' : 'none';
});

ipc.on('show-active-contacts-view', () => {
	selectOtherListViews(3);
});

ipc.on('show-message-requests-view', () => {
	selectOtherListViews(4);
});

ipc.on('show-archived-threads-view', () => {
	selectOtherListViews(5);
});

ipc.on('toggle-unread-threads-view', () => {
	selectOtherListViews(6);
});

function setDarkMode() {
	document.documentElement.classList.toggle('dark-mode', config.get('darkMode'));
	/// ipc.send('set-vibrancy');
}

/// function setVibrancy() {
// 	document.documentElement.classList.toggle('vibrancy', config.get('vibrancy'));
// 	ipc.send('set-vibrancy');
// }

function renderOverlayIcon(messageCount) {
	const canvas = document.createElement('canvas');
	canvas.height = 128;
	canvas.width = 128;
	canvas.style.letterSpacing = '-5px';
	const ctx = canvas.getContext('2d');
	ctx.fillStyle = '#f42020';
	ctx.beginPath();
	ctx.ellipse(64, 64, 64, 64, 0, 0, 2 * Math.PI);
	ctx.fill();
	ctx.textAlign = 'center';
	ctx.fillStyle = 'white';
	ctx.font = '90px sans-serif';
	ctx.fillText(String(Math.min(99, messageCount)), 64, 96);
	return canvas;
}

ipc.on('toggle-sidebar', () => {
	config.set('sidebarHidden', !config.get('sidebarHidden'));
	setSidebarVisibility();
});

ipc.on('set-dark-mode', setDarkMode);

// Disabled because of https://github.com/electron/electron/issues/10886
// and other vibrancy bugs with Electron v2
/// ipc.on('toggle-vibrancy', () => {
// 	config.set('vibrancy', !config.get('vibrancy'));
// 	setVibrancy();

// 	document.documentElement.style.backgroundColor = 'transparent';
// });

ipc.on('render-overlay-icon', (event, messageCount) => {
	ipc.send('update-overlay-icon', renderOverlayIcon(messageCount).toDataURL(), String(messageCount));
});

ipc.on('zoom-reset', () => {
	setZoom(1.0);
});

ipc.on('zoom-in', () => {
	const zoomFactor = config.get('zoomFactor') + 0.1;

	if (zoomFactor < 1.6) {
		setZoom(zoomFactor);
	}
});

ipc.on('zoom-out', () => {
	const zoomFactor = config.get('zoomFactor') - 0.1;

	if (zoomFactor >= 0.8) {
		setZoom(zoomFactor);
	}
});

ipc.on('jump-to-conversation', (event, index) => {
	jumpToConversation(index);
});

function nextConversation() {
	const index = getNextIndex(true);
	selectConversation(index);
}

function previousConversation() {
	const index = getNextIndex(false);
	selectConversation(index);
}

function jumpToConversation(key) {
	const index = key - 1;
	selectConversation(index);
}

// Focus on the conversation with the given index
function selectConversation(index) {
	document.querySelector(listSelector).children[index].firstChild.firstChild.click();
}

// Returns the index of the selected conversation.
// If no conversation is selected, returns null.
function getIndex() {
	const selected = document.querySelector(selectedConversationSelector);
	if (!selected) {
		return null;
	}

	const list = [...selected.parentNode.children];

	return list.indexOf(selected);
}

// Return the index for next node if next is true,
// else returns index for the previous node
function getNextIndex(next) {
	const selected = document.querySelector(selectedConversationSelector);
	if (!selected) {
		return 0;
	}

	const list = [...selected.parentNode.children];
	const index = list.indexOf(selected) + (next ? 1 : -1);

	return ((index % list.length) + list.length) % list.length;
}

function setZoom(zoomFactor) {
	const node = document.getElementById('zoomFactor');
	node.textContent = `${conversationSelector} {zoom: ${zoomFactor} !important}`;
	config.set('zoomFactor', zoomFactor);
}

function openConversationMenu() {
	const index = getIndex();
	if (index === null) {
		return false;
	}

	// Open and close the menu for the below
	const menu = document.querySelectorAll('._2j6._5l-3 ._3d85')[index].firstChild;
	menu.click();

	return true;
}

function openMuteModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(1);
}

function openArchiveModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(3);
}

function openDeleteModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(4);
}

async function openPreferences() {
	// Create the menu for the below
	(await elementReady('._30yy._2fug._p')).click();

	selectMenuItem(1);
}

function isPreferencesOpen() {
	return Boolean(document.querySelector('._3quh._30yy._2t_._5ixy'));
}

function closePreferences() {
	const doneButton = document.querySelector('._3quh._30yy._2t_._5ixy');
	doneButton.click();
}
async function sendConversationList() {
	const sidebar = document.querySelector('[role=navigation]');

	const conversations = await Promise.all(
		[...sidebar.querySelectorAll('._1ht1')]
			.splice(0, 10)
			.map(async el => ({
				label: el.querySelector('._1ht6').textContent,
				selected: el.classList.contains('_1ht2'),
				unread: el.classList.contains('_1ht3'),
				icon: await getDataUrlFromImg(
					el.querySelector('._55lt img'),
					el.classList.contains('_1ht3')
				)
			}))
	);

	ipc.send('conversations', conversations);
}

// Return canvas with rounded image
function urlToCanvas(url, size) {
	return new Promise(resolve => {
		const img = new Image();
		img.crossOrigin = 'anonymous';
		img.addEventListener('load', () => {
			const canvas = document.createElement('canvas');
			const padding = {
				top: 3,
				right: 0,
				bottom: 3,
				left: 0
			};

			canvas.width = size + padding.left + padding.right;
			canvas.height = size + padding.top + padding.bottom;

			const ctx = canvas.getContext('2d');

			ctx.save();
			ctx.beginPath();
			ctx.arc((size / 2) + padding.left, (size / 2) + padding.top, size / 2, 0, Math.PI * 2, true);
			ctx.closePath();
			ctx.clip();

			ctx.drawImage(img, padding.left, padding.top, size, size);

			ctx.restore();

			resolve(canvas);
		});
		img.src = url;
	});
}

// Return data url for user avatar
function getDataUrlFromImg(img, unread) {
	return new Promise(async resolve => {
		if (!unread) {
			if (img.dataUrl) {
				return resolve(img.dataUrl);
			}
		} else if (img.dataUnreadUrl) {
			return resolve(img.dataUnreadUrl);
		}

		const canvas = await urlToCanvas(img.src, 30);
		const ctx = canvas.getContext('2d');
		img.dataUrl = canvas.toDataURL();

		if (!unread) {
			return resolve(img.dataUrl);
		}

		const markerSize = 8;
		ctx.fillStyle = '#f42020';
		ctx.beginPath();
		ctx.ellipse(canvas.width - markerSize, markerSize, markerSize, markerSize, 0, 0, 2 * Math.PI);
		ctx.fill();
		img.dataUnreadUrl = canvas.toDataURL();
		resolve(img.dataUnreadUrl);
	});
}

// Inject a global style node to maintain custom appearance after conversation change or startup
document.addEventListener('DOMContentLoaded', () => {
	const style = document.createElement('style');
	style.id = 'zoomFactor';
	document.body.appendChild(style);

	// Set the zoom factor if it was set before quitting
	const zoomFactor = config.get('zoomFactor') || 1.0;
	setZoom(zoomFactor);

	// Enable OS specific styles
	document.documentElement.classList.add(`os-${process.platform}`);

	// Hide sidebar if it was hidden before quitting
	setSidebarVisibility();

	// Activate Dark Mode if it was set before quitting
	setDarkMode();

	// Prevent flash of white on startup when in dark mode
	// TODO: find a CSS-only solution
	if (config.get('darkMode')) {
		document.documentElement.style.backgroundColor = '#192633';
	}

	// Activate vibrancy effect if it was set before quitting
	// setVibrancy();
});

window.addEventListener('load', () => {
	const sidebar = document.querySelector('[role=navigation]');

	sendConversationList();

	const conversationListObserver = new MutationObserver(sendConversationList);
	conversationListObserver.observe(sidebar, {
		subtree: true,
		childList: true,
		attributes: true,
		attributeFilter: ['class']
	});
});

// It's not possible to add multiple accelerators
// so this needs to be done the old-school way
document.addEventListener('keydown', event => {
	const combineKey = process.platform === 'darwin' ? event.metaKey : event.ctrlKey;

	if (!combineKey) {
		return;
	}

	if (event.key === ']') {
		nextConversation();
	}

	if (event.key === '[') {
		previousConversation();
	}

	const num = parseInt(event.key, 10);

	if (num >= 1 && num <= 9) {
		jumpToConversation(num);
	}
});
<commit_msg>Let Ctrl+1..Ctrl+9 work for Azerty keyboards too (#506)<commit_after>'use strict';
const electron = require('electron');
const elementReady = require('element-ready');
const config = require('./config');

const {ipcRenderer: ipc} = electron;

const listSelector = 'div[role="navigation"] > div > ul';
const conversationSelector = '._4u-c._1wfr > ._5f0v.uiScrollableArea';
const selectedConversationSelector = '._5l-3._1ht1._1ht2';

function showSettingsMenu() {
	document.querySelector('._30yy._2fug._p').click();
}

function selectMenuItem(itemNumber) {
	const selector = document.querySelector(`._54nq._2i-c._558b._2n_z li:nth-child(${itemNumber}) a`);
	selector.click();
}

function selectOtherListViews(itemNumber) {
	// In case one of other views is shown
	clickBackButton();

	// Create the menu for the below
	showSettingsMenu();

	selectMenuItem(itemNumber);
}

function clickBackButton() {
	const backButton = document.querySelector('._30yy._2oc9');
	if (backButton) {
		backButton.click();
	}
}

ipc.on('show-preferences', async () => {
	if (isPreferencesOpen()) {
		return;
	}

	await openPreferences();
});

ipc.on('new-conversation', () => {
	document.querySelector('._30yy[data-href$=\'/new\']').click();
});

ipc.on('log-out', () => {
	if (config.get('useWorkChat')) {
		// Create the menu for the below
		document.querySelector('._5lxs._3qct._p').click();
		// Menu creation is slow
		setTimeout(() => {
			const nodes = document.querySelectorAll('._54nq._9jo._558b._2n_z li:last-child a');
			nodes[nodes.length - 1].click();
		}, 250);
	} else {
		showSettingsMenu();
		const nodes = document.querySelectorAll('._54nq._2i-c._558b._2n_z li:last-child a');
		nodes[nodes.length - 1].click();
	}
});

ipc.on('find', () => {
	document.querySelector('._58al').focus();
});

ipc.on('search', () => {
	document.querySelector('._3szn:nth-of-type(1)').click();
});

ipc.on('insert-gif', () => {
	document.querySelector('._yht').click();
});

ipc.on('insert-emoji', () => {
	document.querySelector('._5s2p').click();
});

ipc.on('insert-text', () => {
	document.querySelector('._5rpu').focus();
});

ipc.on('next-conversation', nextConversation);

ipc.on('previous-conversation', previousConversation);

ipc.on('mute-conversation', () => {
	openMuteModal();
});

ipc.on('delete-conversation', () => {
	openDeleteModal();
});

ipc.on('archive-conversation', () => {
	openArchiveModal();
});

function setSidebarVisibility() {
	document.documentElement.classList.toggle('sidebar-hidden', config.get('sidebarHidden'));
	ipc.send('set-sidebar-visibility');
}

ipc.on('toggle-mute-notifications', async (event, defaultStatus) => {
	const preferencesAreOpen = isPreferencesOpen();
	if (!preferencesAreOpen) {
		await openPreferences();
	}

	const notificationCheckbox = document.querySelector('._374b:nth-of-type(4) ._4ng2 input');

	if (defaultStatus === undefined) {
		notificationCheckbox.click();
	} else if ((defaultStatus && notificationCheckbox.checked) || (!defaultStatus && !notificationCheckbox.checked)) {
		notificationCheckbox.click();
	}

	ipc.send('mute-notifications-toggled', !notificationCheckbox.checked);

	if (!preferencesAreOpen) {
		closePreferences();
	}
});

ipc.on('toggle-message-buttons', async () => {
	const messageButtons = await elementReady('._39bj');
	messageButtons.style.display = config.get('showMessageButtons') ? 'flex' : 'none';
});

ipc.on('show-active-contacts-view', () => {
	selectOtherListViews(3);
});

ipc.on('show-message-requests-view', () => {
	selectOtherListViews(4);
});

ipc.on('show-archived-threads-view', () => {
	selectOtherListViews(5);
});

ipc.on('toggle-unread-threads-view', () => {
	selectOtherListViews(6);
});

function setDarkMode() {
	document.documentElement.classList.toggle('dark-mode', config.get('darkMode'));
	/// ipc.send('set-vibrancy');
}

/// function setVibrancy() {
// 	document.documentElement.classList.toggle('vibrancy', config.get('vibrancy'));
// 	ipc.send('set-vibrancy');
// }

function renderOverlayIcon(messageCount) {
	const canvas = document.createElement('canvas');
	canvas.height = 128;
	canvas.width = 128;
	canvas.style.letterSpacing = '-5px';
	const ctx = canvas.getContext('2d');
	ctx.fillStyle = '#f42020';
	ctx.beginPath();
	ctx.ellipse(64, 64, 64, 64, 0, 0, 2 * Math.PI);
	ctx.fill();
	ctx.textAlign = 'center';
	ctx.fillStyle = 'white';
	ctx.font = '90px sans-serif';
	ctx.fillText(String(Math.min(99, messageCount)), 64, 96);
	return canvas;
}

ipc.on('toggle-sidebar', () => {
	config.set('sidebarHidden', !config.get('sidebarHidden'));
	setSidebarVisibility();
});

ipc.on('set-dark-mode', setDarkMode);

// Disabled because of https://github.com/electron/electron/issues/10886
// and other vibrancy bugs with Electron v2
/// ipc.on('toggle-vibrancy', () => {
// 	config.set('vibrancy', !config.get('vibrancy'));
// 	setVibrancy();

// 	document.documentElement.style.backgroundColor = 'transparent';
// });

ipc.on('render-overlay-icon', (event, messageCount) => {
	ipc.send('update-overlay-icon', renderOverlayIcon(messageCount).toDataURL(), String(messageCount));
});

ipc.on('zoom-reset', () => {
	setZoom(1.0);
});

ipc.on('zoom-in', () => {
	const zoomFactor = config.get('zoomFactor') + 0.1;

	if (zoomFactor < 1.6) {
		setZoom(zoomFactor);
	}
});

ipc.on('zoom-out', () => {
	const zoomFactor = config.get('zoomFactor') - 0.1;

	if (zoomFactor >= 0.8) {
		setZoom(zoomFactor);
	}
});

ipc.on('jump-to-conversation', (event, index) => {
	jumpToConversation(index);
});

function nextConversation() {
	const index = getNextIndex(true);
	selectConversation(index);
}

function previousConversation() {
	const index = getNextIndex(false);
	selectConversation(index);
}

function jumpToConversation(key) {
	const index = key - 1;
	selectConversation(index);
}

// Focus on the conversation with the given index
function selectConversation(index) {
	document.querySelector(listSelector).children[index].firstChild.firstChild.click();
}

// Returns the index of the selected conversation.
// If no conversation is selected, returns null.
function getIndex() {
	const selected = document.querySelector(selectedConversationSelector);
	if (!selected) {
		return null;
	}

	const list = [...selected.parentNode.children];

	return list.indexOf(selected);
}

// Return the index for next node if next is true,
// else returns index for the previous node
function getNextIndex(next) {
	const selected = document.querySelector(selectedConversationSelector);
	if (!selected) {
		return 0;
	}

	const list = [...selected.parentNode.children];
	const index = list.indexOf(selected) + (next ? 1 : -1);

	return ((index % list.length) + list.length) % list.length;
}

function setZoom(zoomFactor) {
	const node = document.getElementById('zoomFactor');
	node.textContent = `${conversationSelector} {zoom: ${zoomFactor} !important}`;
	config.set('zoomFactor', zoomFactor);
}

function openConversationMenu() {
	const index = getIndex();
	if (index === null) {
		return false;
	}

	// Open and close the menu for the below
	const menu = document.querySelectorAll('._2j6._5l-3 ._3d85')[index].firstChild;
	menu.click();

	return true;
}

function openMuteModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(1);
}

function openArchiveModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(3);
}

function openDeleteModal() {
	if (!openConversationMenu()) {
		return;
	}

	selectMenuItem(4);
}

async function openPreferences() {
	// Create the menu for the below
	(await elementReady('._30yy._2fug._p')).click();

	selectMenuItem(1);
}

function isPreferencesOpen() {
	return Boolean(document.querySelector('._3quh._30yy._2t_._5ixy'));
}

function closePreferences() {
	const doneButton = document.querySelector('._3quh._30yy._2t_._5ixy');
	doneButton.click();
}
async function sendConversationList() {
	const sidebar = document.querySelector('[role=navigation]');

	const conversations = await Promise.all(
		[...sidebar.querySelectorAll('._1ht1')]
			.splice(0, 10)
			.map(async el => ({
				label: el.querySelector('._1ht6').textContent,
				selected: el.classList.contains('_1ht2'),
				unread: el.classList.contains('_1ht3'),
				icon: await getDataUrlFromImg(
					el.querySelector('._55lt img'),
					el.classList.contains('_1ht3')
				)
			}))
	);

	ipc.send('conversations', conversations);
}

// Return canvas with rounded image
function urlToCanvas(url, size) {
	return new Promise(resolve => {
		const img = new Image();
		img.crossOrigin = 'anonymous';
		img.addEventListener('load', () => {
			const canvas = document.createElement('canvas');
			const padding = {
				top: 3,
				right: 0,
				bottom: 3,
				left: 0
			};

			canvas.width = size + padding.left + padding.right;
			canvas.height = size + padding.top + padding.bottom;

			const ctx = canvas.getContext('2d');

			ctx.save();
			ctx.beginPath();
			ctx.arc((size / 2) + padding.left, (size / 2) + padding.top, size / 2, 0, Math.PI * 2, true);
			ctx.closePath();
			ctx.clip();

			ctx.drawImage(img, padding.left, padding.top, size, size);

			ctx.restore();

			resolve(canvas);
		});
		img.src = url;
	});
}

// Return data url for user avatar
function getDataUrlFromImg(img, unread) {
	return new Promise(async resolve => {
		if (!unread) {
			if (img.dataUrl) {
				return resolve(img.dataUrl);
			}
		} else if (img.dataUnreadUrl) {
			return resolve(img.dataUnreadUrl);
		}

		const canvas = await urlToCanvas(img.src, 30);
		const ctx = canvas.getContext('2d');
		img.dataUrl = canvas.toDataURL();

		if (!unread) {
			return resolve(img.dataUrl);
		}

		const markerSize = 8;
		ctx.fillStyle = '#f42020';
		ctx.beginPath();
		ctx.ellipse(canvas.width - markerSize, markerSize, markerSize, markerSize, 0, 0, 2 * Math.PI);
		ctx.fill();
		img.dataUnreadUrl = canvas.toDataURL();
		resolve(img.dataUnreadUrl);
	});
}

// Inject a global style node to maintain custom appearance after conversation change or startup
document.addEventListener('DOMContentLoaded', () => {
	const style = document.createElement('style');
	style.id = 'zoomFactor';
	document.body.appendChild(style);

	// Set the zoom factor if it was set before quitting
	const zoomFactor = config.get('zoomFactor') || 1.0;
	setZoom(zoomFactor);

	// Enable OS specific styles
	document.documentElement.classList.add(`os-${process.platform}`);

	// Hide sidebar if it was hidden before quitting
	setSidebarVisibility();

	// Activate Dark Mode if it was set before quitting
	setDarkMode();

	// Prevent flash of white on startup when in dark mode
	// TODO: find a CSS-only solution
	if (config.get('darkMode')) {
		document.documentElement.style.backgroundColor = '#192633';
	}

	// Activate vibrancy effect if it was set before quitting
	// setVibrancy();
});

window.addEventListener('load', () => {
	const sidebar = document.querySelector('[role=navigation]');

	sendConversationList();

	const conversationListObserver = new MutationObserver(sendConversationList);
	conversationListObserver.observe(sidebar, {
		subtree: true,
		childList: true,
		attributes: true,
		attributeFilter: ['class']
	});
});

// It's not possible to add multiple accelerators
// so this needs to be done the old-school way
document.addEventListener('keydown', event => {
	const combineKey = process.platform === 'darwin' ? event.metaKey : event.ctrlKey;

	if (!combineKey) {
		return;
	}

	if (event.key === ']') {
		nextConversation();
	}

	if (event.key === '[') {
		previousConversation();
	}

	const num = parseInt(event.code.slice(-1), 10);

	if (num >= 1 && num <= 9) {
		jumpToConversation(num);
	}
});
<|endoftext|>


<commit_before>define(function(require, exports, module) {
    main.consumes = [
        "Previewer", "preview", "vfs", "tabManager", "remote.PostMessage", "c9",
        "CSSDocument", "HTMLDocument", "JSDocument", "MenuItem", "commands"
    ];
    main.provides = ["preview.browser"];
    return main;

    function main(options, imports, register) {
        var c9 = imports.c9;
        var Previewer = imports.Previewer;
        var tabManager = imports.tabManager;
        var preview = imports.preview;
        var PostMessage = imports["remote.PostMessage"];
        var CSSDocument = imports.CSSDocument;
        var HTMLDocument = imports.HTMLDocument;
        var JSDocument = imports.JSDocument;
        var MenuItem = imports.MenuItem;
        var commands = imports.commands;

        // var join = require("path").join;
        // var dirname = require("path").dirname;

        /***** Initialization *****/

        var plugin = new Previewer("Ajax.org", main.consumes, {
            caption: "Browser",
            index: 10,
            divider: true,
            selector: function(path) {
                return /(?:\.html|\.htm|\.xhtml)$|^https?\:\/\//.test(path);
            }
        });

        var BASEPATH = preview.previewUrl;
        var counter = 0;

        /***** Methods *****/

        function calcRootedPath(url) {
            if (url.substr(0, BASEPATH.length) == BASEPATH)
                return url.substr(BASEPATH.length);
            return url;
        }

        function getIframeSrc(iframe){
            var src;
            try { src = iframe.contentWindow.location.href; }
            catch(e) { src = iframe.src }
            if (src == "about:blank")
                src = "";
            return src;
        }

        function cleanIframeSrc(src) {
            return src
                .replace(/([&?])_c9_id=\w+\&_c9_host=[^&#]+/g, "$1")
                .replace(/[\?\&]$/, "");
        }

        function calcAbsolutepath(url) {
            if (url[0] == "/") return BASEPATH + url;
            return url;
        }

        function loadPreviewSession(session, allowAutoFocus) {
            var iframe = session.iframe;

            session.suspended = false;

            var tab = session.tab;
            tab.classList.add("loading");

            if (!allowAutoFocus) {
                window.addEventListener("blur", function onblur(x) {
                    window.removeEventListener("blur", onblur, false);
                    if (document.activeElement == iframe)
                        setTimeout(function() { window.focus(); });
                }, false);
            }
            var url = calcAbsolutepath(session.url);

            if (!session.disableInjection && !/^(data|blob):/.test(url)) {
                var parts = url.split("#");
                url = parts[0] + (~parts[0].indexOf("?") ? "&" : "?")
                    + "_c9_id=" + session.id
                    + "&_c9_host=" + (options.local ? "local" : location.origin)
                    + (parts.length > 1 ? "#" + parts.slice(1).join("") : "");
            }

            var mainPath = url.match(/^[^?#]*/)[0];
            var noSandbox = /\.(pdf)$/.test(mainPath);

            // dissallow top navigation
            if (!options.local) {
                if (noSandbox) {
                    iframe.removeAttribute("sandbox");
                } else {
                    iframe.setAttribute("sandbox", "allow-forms allow-pointer-lock allow-popups"
                        + " allow-same-origin allow-scripts allow-modals allow-popups-to-escape-sandbox");
                }
            }

            if (!options.local && window.location.protocol == "https:" && url.startsWith("http://"))
                url = options.staticPrefix + "/nohttps.html#" + url;

            iframe.src = url;
        }

        /***** Lifecycle *****/

        plugin.on("load", function(){
            commands.addCommand({
                name: "scrollPreviewElementIntoView",
                displayName: "Preview:scroll element into view",
                bindKey: {win: "Ctrl-I", mac: "Ctrl-I"},
                exec: function(editor) {
                    if (editor.type == "preview")
                        plugin.activeSession.transport.reveal();
                    else
                        editor.ace.session.htmldocument.scrollIntoView();
                },
                isAvailable: function(editor) {
                    return editor
                        && (editor.ace && editor.ace.session.htmldocument
                        || editor.type == "preview");
                }
            }, plugin);

            var item = new MenuItem({
                caption: "Enable Highlighting",
                type: "check",
                onclick: function(){
                    var session = plugin.activeSession;
                    (session.transport || 0).enableHighlighting = item.checked;
                },
                isAvailable: function(){
                    item.checked = ((plugin.activeSession || 0).transport || 0).enableHighlighting;
                    return true;
                }
            }, plugin);
            preview.settingsMenu.append(item);

            var item2 = new MenuItem({
                caption: "Disable Live Preview Injection",
                type: "check",
                onclick: function(){
                    var session = plugin.activeSession || 0;
                    session.disableInjection = item2.checked;
                    plugin.navigate({ url: session.path });
                },
                isAvailable: function(){
                    item2.checked = (plugin.activeSession || 0).disableInjection;
                    return true;
                }
            }, plugin);
            preview.settingsMenu.append(item2);

            preview.settingsMenu.append(new MenuItem({
                caption: "Scroll Preview Element Into View",
                command: "scrollPreviewElementIntoView"
            }, plugin));
        });

        plugin.on("sessionStart", function(e) {
            var doc = e.doc;
            var session = e.session;
            var tab = e.tab;
            var editor = e.editor;

            if (session.iframe) {
                session.editor = editor;
                editor.container.appendChild(session.iframe);
                return;
            }

            // do not restore untrusted tabs at startup, since they may contain infinite loops
            if (!tabManager.isReady && session.trustedPath != session.initPath)
                session.suspended = true;

            var iframe = document.createElement("iframe");
            iframe.setAttribute("nwfaketop", true);
            iframe.setAttribute("nwdisable", true);

            iframe.style.width = "100%";
            iframe.style.height = "100%";
            iframe.style.border = 0;
            iframe.style.backgroundColor = "rgba(255, 255, 255, 0.88)";

            iframe.addEventListener("load", function(){
                if (!iframe.src) return;

                var src = getIframeSrc(iframe);
                var path = calcRootedPath(cleanIframeSrc(src));

                tab.title =
                tab.tooltip = "[B] " + path;
                session.lastSrc = src;

                if (options.local) {
                    var url = cleanIframeSrc(getIframeSrc(iframe));
                    if (url.indexOf("data:") === 0) {
                        editor.setLocation(path);
                    }
                    else {
                        editor.setLocation(url);
                        session.currentLocation = url;
                    }

                    iframe.contentWindow.opener = window;
                    if (iframe.contentWindow.start)
                        iframe.contentWindow.start(window);
                }
                else if (!~path.indexOf(options.staticPrefix)) {
                    editor.setLocation(path);
                }

                tab.classList.remove("loading");
            });

            session.id = "livepreview" + counter++;
            session.iframe = iframe;
            session.editor = editor;
            session.transport = new PostMessage(iframe, session.id);

            session.transport.on("ready", function(){
                session.transport.getSources(function(err, sources) {
                    session.styleSheets = sources.styleSheets.map(function(path) {
                        return new CSSDocument(path).addTransport(session.transport);
                    });
                    session.scripts = sources.scripts.map(function(path) {
                        return new JSDocument(path).addTransport(session.transport);
                    });
                    session.html = new HTMLDocument(sources.html)
                        .addTransport(session.transport);
                });
            }, doc);
            session.transport.on("focus", function(){
                tabManager.focusTab(doc.tab);
            }, doc);

            session.destroy = function(){
                if (session.transport)
                    session.transport.unload();
                delete session.editor;
                delete session.transport;
                delete session.iframe;
                delete session.id;
            };

            // Lets only destroy when the doc is destroyed
            doc.addOther(function(){ session.destroy() });

            doc.on("canUnload", function(e) {
                if (!session.transport) return;

                var count = session.transport.getWindows().length;
                if (count <= 1) return true;

                session.transport.once("empty", function(){
                    doc.unload();
                });

                return false;
            }, session);

            editor.container.appendChild(session.iframe);
        });
        plugin.on("sessionEnd", function(e) {
            var tab = e.tab;
            var session = e.session;
            var iframe = session.iframe;

            iframe.remove();
            tab.classList.remove("loading");
        });
        plugin.on("sessionActivate", function(e) {
            var session = e.session;
            var path = calcRootedPath(session.url || session.path || session.initPath);

            session.iframe.style.display = "block";
            session.editor.setLocation(path, true);
            session.editor.setButtonStyle("Browser", "page_white.png");
        });
        plugin.on("sessionDeactivate", function(e) {
            var session = e.session;
            session.iframe.style.display = "none";
        });
        plugin.on("navigate", function navigate(e) {
            var tab = plugin.activeDocument.tab;
            var session = plugin.activeSession;

            var iframe = session.iframe;
            if (!iframe) // happens when save is called from collab see also previewer navigate
                return;

            var nurl = e.url.replace(/^~/, c9.home);
            var url = nurl.match(/^[a-z]\w{1,4}\:/)
                ? nurl
                : BASEPATH + nurl;

            var base = (session.url || "").split("#")[0];
            if (url.indexOf(base + "#") == -1 && url != base)
                tab.classList.add("loading");
            session.url = url;

            var path = calcRootedPath(url);
            tab.title =
            tab.tooltip = "[B] " + path;

            plugin.activeSession.editor.setLocation(path, true);

            if (session.suspended) {
                var staticPrefix = options.staticPrefix;
                if (staticPrefix.indexOf("https://") !== 0)
                    staticPrefix = location.protocol + "//" + location.host + staticPrefix;

                iframe.contentDocument.body.innerHTML = require("text!./suspended.html")
                    .replace(/\{\{staticPrefix\}\}/g, staticPrefix);

                iframe.contentWindow.onclick = function() {
                    session.suspended = false;
                    loadPreviewSession(session);
                };
                session.suspended = false;
            } else {
                loadPreviewSession(session);
            }
        });
        plugin.on("update", function(e) {

            // var iframe = plugin.activeSession.iframe;
            // if (e.saved)
            //     iframe.src = iframe.src;
        });
        plugin.on("reload", function() {
            var session = plugin.activeSession;
            loadPreviewSession(session, tabManager.focussedTab == session.tab);
        });
        plugin.on("popout", function(){
            var src = getIframeSrc(plugin.activeSession.iframe);
            if (!src)
                src = plugin.activeSession.url;
            window.open(src);
        });
        plugin.on("getState", function(e) {
            var session = e.doc.getSession();
            var state = e.state;

            state.currentLocation = session.currentLocation;
            state.disableInjection = session.disableInjection;
            state.trustedPath = session.trustedPath;
        });
        plugin.on("setState", function(e) {
            var session = e.doc.getSession();
            var state = e.state;

            if (state.currentLocation) {
                if (session.initPath)
                    session.initPath = state.currentLocation;
                // else
                //     plugin.navigate({ url: state.currentLocation, doc: e.doc });
            }
            session.disableInjection = state.disableInjection;
        });
        plugin.on("unload", function(){
        });

        /***** Register and define API *****/

        /**
         * Previewer for content that the browser can display natively.
         **/
        plugin.freezePublicAPI({

        });

        register(null, {
            "preview.browser": plugin
        });
    }
});<commit_msg>always open pdf in browser previewer instead of raw<commit_after>define(function(require, exports, module) {
    main.consumes = [
        "Previewer", "preview", "vfs", "tabManager", "remote.PostMessage", "c9",
        "CSSDocument", "HTMLDocument", "JSDocument", "MenuItem", "commands"
    ];
    main.provides = ["preview.browser"];
    return main;

    function main(options, imports, register) {
        var c9 = imports.c9;
        var Previewer = imports.Previewer;
        var tabManager = imports.tabManager;
        var preview = imports.preview;
        var PostMessage = imports["remote.PostMessage"];
        var CSSDocument = imports.CSSDocument;
        var HTMLDocument = imports.HTMLDocument;
        var JSDocument = imports.JSDocument;
        var MenuItem = imports.MenuItem;
        var commands = imports.commands;

        // var join = require("path").join;
        // var dirname = require("path").dirname;

        /***** Initialization *****/

        var plugin = new Previewer("Ajax.org", main.consumes, {
            caption: "Browser",
            index: 10,
            divider: true,
            selector: function(path) {
                return /\.(?:html|htm|xhtml|pdf|svg)$|^https?\:\/\//.test(path);
            }
        });

        var BASEPATH = preview.previewUrl;
        var counter = 0;

        /***** Methods *****/

        function calcRootedPath(url) {
            if (url.substr(0, BASEPATH.length) == BASEPATH)
                return url.substr(BASEPATH.length);
            return url;
        }

        function getIframeSrc(iframe){
            var src;
            try { src = iframe.contentWindow.location.href; }
            catch(e) { src = iframe.src }
            if (src == "about:blank")
                src = "";
            return src;
        }

        function cleanIframeSrc(src) {
            return src
                .replace(/([&?])_c9_id=\w+\&_c9_host=[^&#]+/g, "$1")
                .replace(/[\?\&]$/, "");
        }

        function calcAbsolutepath(url) {
            if (url[0] == "/") return BASEPATH + url;
            return url;
        }

        function loadPreviewSession(session, allowAutoFocus) {
            var iframe = session.iframe;

            session.suspended = false;

            var tab = session.tab;
            tab.classList.add("loading");

            if (!allowAutoFocus) {
                window.addEventListener("blur", function onblur(x) {
                    window.removeEventListener("blur", onblur, false);
                    if (document.activeElement == iframe)
                        setTimeout(function() { window.focus(); });
                }, false);
            }
            var url = calcAbsolutepath(session.url);

            if (!session.disableInjection && !/^(data|blob):/.test(url)) {
                var parts = url.split("#");
                url = parts[0] + (~parts[0].indexOf("?") ? "&" : "?")
                    + "_c9_id=" + session.id
                    + "&_c9_host=" + (options.local ? "local" : location.origin)
                    + (parts.length > 1 ? "#" + parts.slice(1).join("") : "");
            }

            var mainPath = url.match(/^[^?#]*/)[0];
            var noSandbox = /\.(pdf)$/.test(mainPath);

            // dissallow top navigation
            if (!options.local) {
                if (noSandbox) {
                    iframe.removeAttribute("sandbox");
                } else {
                    iframe.setAttribute("sandbox", "allow-forms allow-pointer-lock allow-popups"
                        + " allow-same-origin allow-scripts allow-modals allow-popups-to-escape-sandbox");
                }
            }

            if (!options.local && window.location.protocol == "https:" && url.startsWith("http://"))
                url = options.staticPrefix + "/nohttps.html#" + url;

            iframe.src = url;
        }

        /***** Lifecycle *****/

        plugin.on("load", function(){
            commands.addCommand({
                name: "scrollPreviewElementIntoView",
                displayName: "Preview:scroll element into view",
                bindKey: {win: "Ctrl-I", mac: "Ctrl-I"},
                exec: function(editor) {
                    if (editor.type == "preview")
                        plugin.activeSession.transport.reveal();
                    else
                        editor.ace.session.htmldocument.scrollIntoView();
                },
                isAvailable: function(editor) {
                    return editor
                        && (editor.ace && editor.ace.session.htmldocument
                        || editor.type == "preview");
                }
            }, plugin);

            var item = new MenuItem({
                caption: "Enable Highlighting",
                type: "check",
                onclick: function(){
                    var session = plugin.activeSession;
                    (session.transport || 0).enableHighlighting = item.checked;
                },
                isAvailable: function(){
                    item.checked = ((plugin.activeSession || 0).transport || 0).enableHighlighting;
                    return true;
                }
            }, plugin);
            preview.settingsMenu.append(item);

            var item2 = new MenuItem({
                caption: "Disable Live Preview Injection",
                type: "check",
                onclick: function(){
                    var session = plugin.activeSession || 0;
                    session.disableInjection = item2.checked;
                    plugin.navigate({ url: session.path });
                },
                isAvailable: function(){
                    item2.checked = (plugin.activeSession || 0).disableInjection;
                    return true;
                }
            }, plugin);
            preview.settingsMenu.append(item2);

            preview.settingsMenu.append(new MenuItem({
                caption: "Scroll Preview Element Into View",
                command: "scrollPreviewElementIntoView"
            }, plugin));
        });

        plugin.on("sessionStart", function(e) {
            var doc = e.doc;
            var session = e.session;
            var tab = e.tab;
            var editor = e.editor;

            if (session.iframe) {
                session.editor = editor;
                editor.container.appendChild(session.iframe);
                return;
            }

            // do not restore untrusted tabs at startup, since they may contain infinite loops
            if (!tabManager.isReady && session.trustedPath != session.initPath)
                session.suspended = true;

            var iframe = document.createElement("iframe");
            iframe.setAttribute("nwfaketop", true);
            iframe.setAttribute("nwdisable", true);

            iframe.style.width = "100%";
            iframe.style.height = "100%";
            iframe.style.border = 0;
            iframe.style.backgroundColor = "rgba(255, 255, 255, 0.88)";

            iframe.addEventListener("load", function(){
                if (!iframe.src) return;

                var src = getIframeSrc(iframe);
                var path = calcRootedPath(cleanIframeSrc(src));

                tab.title =
                tab.tooltip = "[B] " + path;
                session.lastSrc = src;

                if (options.local) {
                    var url = cleanIframeSrc(getIframeSrc(iframe));
                    if (url.indexOf("data:") === 0) {
                        editor.setLocation(path);
                    }
                    else {
                        editor.setLocation(url);
                        session.currentLocation = url;
                    }

                    iframe.contentWindow.opener = window;
                    if (iframe.contentWindow.start)
                        iframe.contentWindow.start(window);
                }
                else if (!~path.indexOf(options.staticPrefix)) {
                    editor.setLocation(path);
                }

                tab.classList.remove("loading");
            });

            session.id = "livepreview" + counter++;
            session.iframe = iframe;
            session.editor = editor;
            session.transport = new PostMessage(iframe, session.id);

            session.transport.on("ready", function(){
                session.transport.getSources(function(err, sources) {
                    session.styleSheets = sources.styleSheets.map(function(path) {
                        return new CSSDocument(path).addTransport(session.transport);
                    });
                    session.scripts = sources.scripts.map(function(path) {
                        return new JSDocument(path).addTransport(session.transport);
                    });
                    session.html = new HTMLDocument(sources.html)
                        .addTransport(session.transport);
                });
            }, doc);
            session.transport.on("focus", function(){
                tabManager.focusTab(doc.tab);
            }, doc);

            session.destroy = function(){
                if (session.transport)
                    session.transport.unload();
                delete session.editor;
                delete session.transport;
                delete session.iframe;
                delete session.id;
            };

            // Lets only destroy when the doc is destroyed
            doc.addOther(function(){ session.destroy() });

            doc.on("canUnload", function(e) {
                if (!session.transport) return;

                var count = session.transport.getWindows().length;
                if (count <= 1) return true;

                session.transport.once("empty", function(){
                    doc.unload();
                });

                return false;
            }, session);

            editor.container.appendChild(session.iframe);
        });
        plugin.on("sessionEnd", function(e) {
            var tab = e.tab;
            var session = e.session;
            var iframe = session.iframe;

            iframe.remove();
            tab.classList.remove("loading");
        });
        plugin.on("sessionActivate", function(e) {
            var session = e.session;
            var path = calcRootedPath(session.url || session.path || session.initPath);

            session.iframe.style.display = "block";
            session.editor.setLocation(path, true);
            session.editor.setButtonStyle("Browser", "page_white.png");
        });
        plugin.on("sessionDeactivate", function(e) {
            var session = e.session;
            session.iframe.style.display = "none";
        });
        plugin.on("navigate", function navigate(e) {
            var tab = plugin.activeDocument.tab;
            var session = plugin.activeSession;

            var iframe = session.iframe;
            if (!iframe) // happens when save is called from collab see also previewer navigate
                return;

            var nurl = e.url.replace(/^~/, c9.home);
            var url = nurl.match(/^[a-z]\w{1,4}\:/)
                ? nurl
                : BASEPATH + nurl;

            var base = (session.url || "").split("#")[0];
            if (url.indexOf(base + "#") == -1 && url != base)
                tab.classList.add("loading");
            session.url = url;

            var path = calcRootedPath(url);
            tab.title =
            tab.tooltip = "[B] " + path;

            plugin.activeSession.editor.setLocation(path, true);

            if (session.suspended) {
                var staticPrefix = options.staticPrefix;
                if (staticPrefix.indexOf("https://") !== 0)
                    staticPrefix = location.protocol + "//" + location.host + staticPrefix;

                iframe.contentDocument.body.innerHTML = require("text!./suspended.html")
                    .replace(/\{\{staticPrefix\}\}/g, staticPrefix);

                iframe.contentWindow.onclick = function() {
                    session.suspended = false;
                    loadPreviewSession(session);
                };
                session.suspended = false;
            } else {
                loadPreviewSession(session);
            }
        });
        plugin.on("update", function(e) {

            // var iframe = plugin.activeSession.iframe;
            // if (e.saved)
            //     iframe.src = iframe.src;
        });
        plugin.on("reload", function() {
            var session = plugin.activeSession;
            loadPreviewSession(session, tabManager.focussedTab == session.tab);
        });
        plugin.on("popout", function(){
            var src = getIframeSrc(plugin.activeSession.iframe);
            if (!src)
                src = plugin.activeSession.url;
            window.open(src);
        });
        plugin.on("getState", function(e) {
            var session = e.doc.getSession();
            var state = e.state;

            state.currentLocation = session.currentLocation;
            state.disableInjection = session.disableInjection;
            state.trustedPath = session.trustedPath;
        });
        plugin.on("setState", function(e) {
            var session = e.doc.getSession();
            var state = e.state;

            if (state.currentLocation) {
                if (session.initPath)
                    session.initPath = state.currentLocation;
                // else
                //     plugin.navigate({ url: state.currentLocation, doc: e.doc });
            }
            session.disableInjection = state.disableInjection;
        });
        plugin.on("unload", function(){
        });

        /***** Register and define API *****/

        /**
         * Previewer for content that the browser can display natively.
         **/
        plugin.freezePublicAPI({

        });

        register(null, {
            "preview.browser": plugin
        });
    }
});<|endoftext|>


<commit_before>// event stream -.-
window.Buffer = require("buffer").Buffer

var shoe = require("mux-demux-shoe")
    , through = require("through")
    , PauseStream = require("pause-stream")
    , es = require("event-stream")
    , Backoff = require("backoff").fibonnaci
    , BACKOFF_OPTIONS = {
        initialDelay: 500,
        maxDelay: 10000
    }

module.exports = reconnecter

function reconnecter(uri) {
    var proxyWrite = through()
        , proxyRead = through()
        , proxy = es.duplex(proxyWrite, proxyRead)
        , metaStreams = []
        , stream
        , backoff = Backoff(BACKOFF_OPTIONS)

    proxy.createStream = createStream
    proxy.createWriteStream = createWriteStream
    proxy.createReadStream = createReadStream

    createShoeStream()

    return proxy

    function createShoeStream() {
        stream = shoe(uri)

        metaStreams.forEach(proxyMdmStream)

        stream.once("connect", onconnect)

        proxyWrite.pipe(stream).pipe(proxyRead)

        stream.once("end", onend)
    }

    function onconnect() {
        backoff.reset()
        proxy.emit("connect")
    }

    function onend() {
        proxy.emit("disconnect")

        // wait a second otherwise it spin locks
        var delay = backoff.backoffStrategy_.next()
        setTimeout(createShoeStream, delay)
    }

    function proxyMdmStream(details) {
        var proxyWrite = details.proxyWrite
            , proxyRead = details.proxyRead

        var mdm = stream.createStream(details.meta, details.opts)

        proxyWrite.pipe(mdm).pipe(proxyRead)

        stream.once("end", mdm.end.bind(mdm))
    }

    function createStream(meta, opts) {
        var proxyWrite = through()
            , proxyRead = through()
            , proxy = es.duplex(proxyWrite, proxyRead)

        var mdm = stream.createStream(meta, opts)

        proxyWrite.pipe(mdm).pipe(proxyRead)

        metaStreams.push({
            proxy: proxy
            , proxyRead: proxyRead
            , proxyWrite: proxyWrite
            , meta: meta
            , opts: opts
        })

        stream.once("end", mdm.end.bind(mdm))

        return proxy
    }

    function createWriteStream(meta) {
        createStream(meta, {
            writable: true
            , readable: false
        })
    }

    function createReadStream(meta) {
        createStream(meta, {
            writable: false
            , readable: true
        })
    }
}<commit_msg>return those streams<commit_after>// event stream -.-
window.Buffer = require("buffer").Buffer

var shoe = require("mux-demux-shoe")
    , through = require("through")
    , PauseStream = require("pause-stream")
    , es = require("event-stream")
    , Backoff = require("backoff").fibonnaci
    , BACKOFF_OPTIONS = {
        initialDelay: 500,
        maxDelay: 10000
    }

module.exports = reconnecter

function reconnecter(uri) {
    var proxyWrite = through()
        , proxyRead = through()
        , proxy = es.duplex(proxyWrite, proxyRead)
        , metaStreams = []
        , stream
        , backoff = Backoff(BACKOFF_OPTIONS)

    proxy.createStream = createStream
    proxy.createWriteStream = createWriteStream
    proxy.createReadStream = createReadStream

    createShoeStream()

    return proxy

    function createShoeStream() {
        stream = shoe(uri)

        metaStreams.forEach(proxyMdmStream)

        stream.once("connect", onconnect)

        proxyWrite.pipe(stream).pipe(proxyRead)

        stream.once("end", onend)
    }

    function onconnect() {
        backoff.reset()
        proxy.emit("connect")
    }

    function onend() {
        proxy.emit("disconnect")

        // wait a second otherwise it spin locks
        var delay = backoff.backoffStrategy_.next()
        setTimeout(createShoeStream, delay)
    }

    function proxyMdmStream(details) {
        var proxyWrite = details.proxyWrite
            , proxyRead = details.proxyRead

        var mdm = stream.createStream(details.meta, details.opts)

        proxyWrite.pipe(mdm).pipe(proxyRead)

        stream.once("end", mdm.end.bind(mdm))
    }

    function createStream(meta, opts) {
        var proxyWrite = through()
            , proxyRead = through()
            , proxy = es.duplex(proxyWrite, proxyRead)

        var mdm = stream.createStream(meta, opts)

        proxyWrite.pipe(mdm).pipe(proxyRead)

        metaStreams.push({
            proxy: proxy
            , proxyRead: proxyRead
            , proxyWrite: proxyWrite
            , meta: meta
            , opts: opts
        })

        stream.once("end", mdm.end.bind(mdm))

        return proxy
    }

    function createWriteStream(meta) {
        return createStream(meta, {
            writable: true
            , readable: false
        })
    }

    function createReadStream(meta) {
        return createStream(meta, {
            writable: false
            , readable: true
        })
    }
}<|endoftext|>


<commit_before>if (typeof biojs === 'undefined') {
  biojs = {};
}
if (typeof biojs.vis === 'undefined') {
  biojs.vis = {};
}
// use two namespaces
window.msa = biojs.vis.msa = module.exports = require('./index');

// TODO: how should this be bundled
biojs.vis.easy_features = require("biojs-vis-easy_features");

if (typeof biojs.io === 'undefined') {
  biojs.io = {};
}
// just bundle the two parsers
window.biojs.io.fasta = require("biojs-io-fasta");
window.biojs.io.clustal = require("biojs-io-clustal");

// simulate standalone flag
window.biojsVisMsa = window.msa;
<commit_msg>removed unecessary easy features<commit_after>if (typeof biojs === 'undefined') {
  biojs = {};
}
if (typeof biojs.vis === 'undefined') {
  biojs.vis = {};
}
// use two namespaces
window.msa = biojs.vis.msa = module.exports = require('./index');

// TODO: how should this be bundled

if (typeof biojs.io === 'undefined') {
  biojs.io = {};
}
// just bundle the two parsers
window.biojs.io.fasta = require("biojs-io-fasta");
window.biojs.io.clustal = require("biojs-io-clustal");

// simulate standalone flag
window.biojsVisMsa = window.msa;
<|endoftext|>