Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
15
assets/webapps/calendar.gaiamobile.org/assets.json
Normal file
15
assets/webapps/calendar.gaiamobile.org/assets.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"css": [
|
||||
{
|
||||
"src": "https://raw.github.com/mozilla-b2g/Gaia-UI-Building-Blocks/master/widgets/drawer/style.css",
|
||||
"prefix": "building_blocks/drawer/",
|
||||
"grep": [
|
||||
"body\\[role=\"application\"\\] \\> nav"
|
||||
]
|
||||
}
|
||||
|
||||
],
|
||||
"js": [],
|
||||
|
||||
"cssTarget": "style/building_blocks.css"
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<element name="advanced-settings" extends="section">
|
||||
<template>
|
||||
<gaia-header id="advanced-settings-header">
|
||||
<h1 data-l10n-id="advanced-settings-header"></h1>
|
||||
<a href="/settings/" data-l10n-id="done"></a>
|
||||
</gaia-header>
|
||||
|
||||
<section class="content">
|
||||
<div class="errors"></div>
|
||||
|
||||
<gaia-subheader id="account-list-header" class="account-list-header" skin="organic" role="heading" aria-level="2">
|
||||
<span data-l10n-id="account-list-header">Accounts</span>
|
||||
</gaia-subheader>
|
||||
|
||||
<ol class="account-list link-list" aria-labelledby="account-list-header" role="listbox">
|
||||
</ol>
|
||||
|
||||
<ol role="presentation">
|
||||
<li class="create-account-item" role="presentation">
|
||||
<button class="create-account" href="/select-preset/"
|
||||
data-l10n-id="advanced-settings-add-account">
|
||||
Add Account
|
||||
</button>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<gaia-subheader skin="organic" role="heading" aria-level="2">
|
||||
<span data-l10n-id="advanced-settings-personalization">Personalization</span>
|
||||
</gaia-subheader>
|
||||
|
||||
<ol class="settings-list link-list" role="presentation">
|
||||
<li role="presentation">
|
||||
<label>
|
||||
<span data-l10n-id="sync-frequency">Sync Calendar</span>
|
||||
<span class="button icon icon-dialog">
|
||||
<select id="setting-sync-frequency" name="syncFrequency">
|
||||
<option data-l10n-id="sync-frequency-15min" value="15">15 min</option>
|
||||
<option data-l10n-id="sync-frequency-30min" value="30">30 min</option>
|
||||
<option data-l10n-id="sync-frequency-manual" value="null">Manual</option>
|
||||
</select>
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<gaia-subheader skin="organic" role="heading" aria-level="2">
|
||||
<span data-l10n-id="advanced-settings-reminders">Reminders</span>
|
||||
</gaia-subheader>
|
||||
|
||||
<ol class="settings-list link-list" role="presentation">
|
||||
<li role="presentation">
|
||||
<label id="default-event-alarm">
|
||||
<span data-l10n-id="events">Events</span>
|
||||
<span class="button icon icon-dialog"></span>
|
||||
</label>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<label id="default-allday-alarm">
|
||||
<span data-l10n-id="allday-events">All-day Events</span>
|
||||
<span class="button icon icon-dialog"></span>
|
||||
</label>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
</template>
|
||||
</element>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<element name="create-account" extends="section">
|
||||
<template>
|
||||
<gaia-header id="create-account-header" action="back">
|
||||
<h1 data-l10n-id="add-account"></h1>
|
||||
</gaia-header>
|
||||
|
||||
<ol id="create-account-presets" class="link-list" role="listbox"></ol>
|
||||
</template>
|
||||
</element>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<element name="errors" extends="section">
|
||||
<template>
|
||||
<p class="errors"></p>
|
||||
</template>
|
||||
</element>
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<element name="modify-account" extends="section">
|
||||
<template>
|
||||
<gaia-header id="modify-account-header" action="back">
|
||||
<h1 data-l10n-id="account-header"></h1>
|
||||
<button class="save" data-l10n-id="save">Save</button>
|
||||
</gaia-header>
|
||||
|
||||
<section class="during-progress" aria-live="assertive" aria-atomic="true">
|
||||
<div>
|
||||
<progress class="small" data-l10n-id="during-progress-loading"></progress>
|
||||
<span data-l10n-id="modify-account-setting-up-2">SeTtiNg uP ACcOunt</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<form class="modify-account-form">
|
||||
<fieldset>
|
||||
<ol role="presentation">
|
||||
<li class="user" role="presentation">
|
||||
<input name="user" data-l10n-id="field-user" x-inputmode="verbatim" placeholder="Username" />
|
||||
</li>
|
||||
<li class="password" role="presentation">
|
||||
<input name="password" data-l10n-id="field-password" placeholder="Password" type="password" />
|
||||
</li>
|
||||
<li class="full-url" role="presentation">
|
||||
<input name="fullUrl"
|
||||
type="url"
|
||||
data-l10n-id="field-full-url"
|
||||
placeholder="URL" />
|
||||
|
||||
</li>
|
||||
<li class="sync-errors" role="presentation">
|
||||
<p data-l10n-id="modify-account-unable-to-sync">>uNaBle to SynC</p>
|
||||
</li>
|
||||
<li class="oauth2-sign-in" role="presentation">
|
||||
<button data-l10n-id="modify-account-sign-in" class="force-oauth2"></button>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a data-l10n-id="remove-account-data" role="button" href="#remove-account-dialog" class="danger delete-record bb-button">
|
||||
Remove Local Data
|
||||
</a>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<section role="status" aria-live="assertive" aria-atomic="true">
|
||||
<p class="errors"></p>
|
||||
</section>
|
||||
|
||||
<form role="dialog" data-type="confirm" id="remove-account-dialog">
|
||||
<section>
|
||||
<h1 data-l10n-id="remove-account-dialog">Remove Account</h1>
|
||||
<p data-l10n-id="remove-account-dialog-details">
|
||||
Removing this account will also remove your local copy of associated calendars and events.
|
||||
</p>
|
||||
</section>
|
||||
<menu>
|
||||
<button class="delete-cancel" data-l10n-id="cancel">Cancel</button>
|
||||
<button class="danger delete-confirm" data-l10n-id="confirm">Confirm</button>
|
||||
</menu>
|
||||
</form>
|
||||
</template>
|
||||
</element>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<element name="modify-event" extends="section">
|
||||
<template>
|
||||
<gaia-header id="modify-event-header" action="close">
|
||||
<h1 data-l10n-id="new-event-header" class="save-text"></h1>
|
||||
<h1 data-l10n-id="edit-event-header" class="done-text"></h1>
|
||||
<button class="save">
|
||||
<span data-l10n-id="save" class="save-text">Save</span>
|
||||
<span data-l10n-id="done" class="done-text">Done</span>
|
||||
</button>
|
||||
</gaia-header>
|
||||
|
||||
<form>
|
||||
<ol>
|
||||
<li class="title">
|
||||
<input type="text" name="title" data-l10n-id="event-title" x-inputmode="latin-prose" placeholder="Title" />
|
||||
</li>
|
||||
|
||||
<li class="location">
|
||||
<input type="text" name="location" data-l10n-id="event-location" x-inputmode="latin-prose" placeholder="Where?" />
|
||||
</li>
|
||||
|
||||
<li class="allday">
|
||||
<gaia-switch name="allday">
|
||||
<label data-l10n-id="event-is-allday"></label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
|
||||
<li class="start-date">
|
||||
<span class="button icon icon-dialog" role="combobox" aria-autocomplete="none" data-l10n-id="start-date">
|
||||
<input name="startDate" data-l10n-id="event-start-date" placeholder="Date" type="date" class="hidden-input" aria-hidden="true"/>
|
||||
<span class="datetime" id="start-date-locale" data-type="date"></span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="start-time">
|
||||
<span class="button icon icon-dialog" role="combobox" aria-autocomplete="none" data-l10n-id="start-time">
|
||||
<input type="time" data-l10n-id="event-start-time" placeholder="Start Time" name="startTime" class="hidden-input" aria-hidden="true"/>
|
||||
<span class="datetime" id="start-time-locale" data-type="time"></span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="end-date">
|
||||
<span class="button icon icon-dialog" role="combobox" aria-autocomplete="none" data-l10n-id="end-date">
|
||||
<input name="endDate" data-l10n-id="event-end-date" placeholder="Date" type="date" class="hidden-input" aria-hidden="true"/>
|
||||
<span class="datetime" id="end-date-locale" data-type="date"></span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="end-time">
|
||||
<span class="button icon icon-dialog" role="combobox" aria-autocomplete="none" data-l10n-id="end-time">
|
||||
<input type="time" data-l10n-id="event-end-time" placeholder="End Time" name="endTime" class="hidden-input" aria-hidden="true"/>
|
||||
<span class="datetime" id="end-time-locale" data-type="time"></span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="current-calendar">
|
||||
<span class="button icon icon-dialog" aria-disabled="true">
|
||||
<input name="currentCalendar" />
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="calendar-id">
|
||||
<span class="button icon icon-dialog">
|
||||
<select name="calendarId" class="loading">
|
||||
</select>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h3 data-l10n-id="remind-me" role="heading" aria-level="2">Remind me</h3>
|
||||
<div class="alarms">
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="notes">
|
||||
<textarea name="description" data-l10n-id="event-description" x-inputmode="latin-prose" placeholder="Notes"></textarea>
|
||||
</li>
|
||||
|
||||
</ol>
|
||||
<div class="delete">
|
||||
<a role="button" data-l10n-id="event-delete" class="danger delete-record bb-button">
|
||||
Delete Event
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<section role="status">
|
||||
<p class="errors"></p>
|
||||
</section>
|
||||
</form>
|
||||
</template>
|
||||
</element>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<element name="multi-day" extends="section">
|
||||
<template>
|
||||
<div class="md__sticky">
|
||||
<span class="md__all-day" data-icon="all-day" aria-hidden="true" data-l10n-id="all-day"></span>
|
||||
<div class="md__alldays-wrapper">
|
||||
<section class="md__alldays"></section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md__main">
|
||||
<div class="md__main-content">
|
||||
<ol class="md__sidebar"></ol>
|
||||
<div class="md__days-wrapper">
|
||||
<section class="md__days"></section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</element>
|
||||
11
assets/webapps/calendar.gaiamobile.org/elements/oauth.html
Normal file
11
assets/webapps/calendar.gaiamobile.org/elements/oauth.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<element name="oauth" extends="section">
|
||||
<template>
|
||||
<gaia-header id="oauth-header" action="back">
|
||||
<h1 class="oauth-browser-title">
|
||||
|
||||
</h1>
|
||||
</gaia-header>
|
||||
<div class="browser-container">
|
||||
</div>
|
||||
</template>
|
||||
</element>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<element name="settings" extends="section">
|
||||
<template>
|
||||
<gaia-header id="settings-header" action="menu">
|
||||
<h1></h1>
|
||||
</gaia-header>
|
||||
|
||||
<div class="settings-shield"></div>
|
||||
|
||||
<div class="settings-drawer-container">
|
||||
<div class="settings-drawer">
|
||||
<ol class="calendars" role="listbox" aria-multiselectable="true" data-l10n-id="calendars">
|
||||
</ol>
|
||||
|
||||
<ul role="tablist" class="bb-tablist">
|
||||
<li class="settings">
|
||||
<span class="icon"
|
||||
data-l10n-id="advanced-settings-short-icon-button"></span>
|
||||
</li>
|
||||
<li class="sync update">
|
||||
<span class="icon"
|
||||
data-l10n-id="drawer-sync-icon-button"></span>
|
||||
<span class="sync-progress" role="progressbar"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</element>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<element name="show-event" extends="section">
|
||||
<template>
|
||||
<gaia-header id="show-event-header" action="back">
|
||||
<h1 id="view-event-header" data-l10n-id="view-event-header"></h1>
|
||||
<button class="edit">
|
||||
<span data-l10n-id="edit">Edit</span>
|
||||
</button>
|
||||
</gaia-header>
|
||||
|
||||
<section data-type="list">
|
||||
<ul aria-labelledby="view-event-header">
|
||||
<li class="title">
|
||||
<h1 role="presentation"><span data-l10n-id="title" class="content"></span></h1>
|
||||
</li>
|
||||
|
||||
<li class="location">
|
||||
<span class="content" data-l10n-id="location"></span>
|
||||
</li>
|
||||
|
||||
<li class="duration-time">
|
||||
<span class="content" data-l10n-id="duration"></span>
|
||||
</li>
|
||||
|
||||
<li class="current-calendar">
|
||||
<div class="gaia-icon icon-calendar-dot" aria-hidden="true"></div>
|
||||
<span class="content" data-l10n-id="calendar"></span>
|
||||
</li>
|
||||
|
||||
<li class="alarms">
|
||||
<span data-l10n-id="reminder">Reminder:</span>
|
||||
<span class="content" role="list" data-l10n-id="alarms"></span>
|
||||
</li>
|
||||
|
||||
<li class="description">
|
||||
<span class="content" data-l10n-id="description"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
</element>
|
||||
1450
assets/webapps/calendar.gaiamobile.org/gaia_build_defer_index.js
Normal file
1450
assets/webapps/calendar.gaiamobile.org/gaia_build_defer_index.js
Normal file
File diff suppressed because it is too large
Load diff
436
assets/webapps/calendar.gaiamobile.org/index.html
Normal file
436
assets/webapps/calendar.gaiamobile.org/index.html
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-US" dir="ltr">
|
||||
<head>
|
||||
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<meta name="theme-group" content="theme-productivity">
|
||||
<meta name="theme-color" content="var(--header-background)" data-default="var(--header-background)" data-settings="#eeeeee">
|
||||
<title>Calendar</title>
|
||||
|
||||
<!-- stylesheets -->
|
||||
<!-- need to keep shared styles on the markup so it gets copied to build_stage
|
||||
<link rel="stylesheet" type="text/css" href="/shared/style/tabs.css">
|
||||
<link rel="stylesheet" type="text/css" href="/shared/style/icons.css">
|
||||
-->
|
||||
<!-- lazy-loaded styles
|
||||
<link rel="stylesheet" type="text/css" href="/shared/style/buttons.css">
|
||||
<link rel="stylesheet" type="text/css" href="/shared/style/confirm.css">
|
||||
<link rel="stylesheet" type="text/css" href="/shared/style/status.css">
|
||||
<link rel="stylesheet" type="text/css" href="/shared/style/progress_activity.css">
|
||||
<link rel="stylesheet" type="text/css" href="/shared/style/input_areas.css">
|
||||
-->
|
||||
<link rel="stylesheet" type="text/css" href="/style/base.css">
|
||||
|
||||
<!-- localization -->
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="ar:201601260729, bg:201601260729, bn-BD:201601260729, ca:201601260729, cs:201601260729, da:201601260729, de:201601260729, el:201601260729, en-US:201601260729, es:201601260729, eu:201601260729, fr:201601260729, gl:201601260729, hr:201601260729, hu:201601260729, it:201601260729, ja:201601260729, km:201601260729, ko:201601260729, lt:201601260729, mk:201601260729, ms:201601260729, ne-NP:201601260729, nl:201601260729, pa:201601260729, pl:201601260729, pt-BR:201601260729, ro:201601260729, ru:201601260729, sk:201601260729, sq:201601260729, sr-Cyrl:201601260729, sr-Latn:201601260729, sv-SE:201601260729, tr:201601260729, zh-CN:201601260729, zh-TW:201601260729">
|
||||
<link rel="localization" href="/locales-obj/index.{locale}.json">
|
||||
|
||||
<!-- <script defer="" src="/shared/js/date_time_helper.js"></script> -->
|
||||
<!-- <script defer="" src="/shared/js/intl_helper.js"></script> -->
|
||||
<!-- <script defer="" src="/shared/js/moz_intl.js"></script> -->
|
||||
<!-- <script defer="" src="/shared/js/l10n.js"></script> -->
|
||||
|
||||
<!-- The following scripts are lazy loaded but left here to ensure
|
||||
they get copied over from shared during the build -->
|
||||
<!-- <script defer src="/shared/js/gesture_detector.js"></script> -->
|
||||
<!-- <script defer src="/shared/js/input_parser.js"></script> -->
|
||||
<!-- <script defer src="/shared/js/lazy_loader.js"></script> -->
|
||||
<!-- <script defer src="/shared/js/notification_helper.js"></script> -->
|
||||
|
||||
<!-- Web Components -->
|
||||
<!-- <script defer="" src="shared/js/component_utils.js"></script> -->
|
||||
<!-- <script defer="" src="shared/elements/gaia_subheader/script.js"></script> -->
|
||||
<!-- <script defer="" src="/shared/elements/gaia_checkbox/script.js"></script> -->
|
||||
<!-- <script defer="" src="/shared/elements/gaia_switch/script.js"></script> -->
|
||||
<!-- <script defer="" src="shared/elements/config.js"></script> -->
|
||||
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css">
|
||||
<!-- <script defer="" src="shared/elements/gaia-header/dist/gaia-header.js"></script> -->
|
||||
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/style.css">
|
||||
<!-- Icons are lazy loaded by gaia-header -->
|
||||
<!-- <link rel="stylesheet" type="text/css" href="shared/elements/gaia-icons/gaia-icons.css" /> -->
|
||||
|
||||
<!-- <script defer="" src="/js/bundle.js"></script> --><script src="./gaia_build_defer_index.js" defer="" type="text/javascript"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<meta content="2.5" name="appVersion"></head>
|
||||
|
||||
<body role="application" class="loading theme-productivity">
|
||||
|
||||
<section id="errors" role="status" aria-live="assertive" aria-atomic="true"><!--
|
||||
<p class="errors"></p>
|
||||
--></section>
|
||||
<section id="settings" role="region"><!--
|
||||
<gaia-header id="settings-header" action="menu">
|
||||
<h1></h1>
|
||||
</gaia-header>
|
||||
|
||||
<div class="settings-shield"></div>
|
||||
|
||||
<div class="settings-drawer-container">
|
||||
<div class="settings-drawer">
|
||||
<ol class="calendars" role="listbox" aria-multiselectable="true" data-l10n-id="calendars">
|
||||
</ol>
|
||||
|
||||
<ul role="tablist" class="bb-tablist">
|
||||
<li class="settings">
|
||||
<span class="icon" data-l10n-id="advanced-settings-short-icon-button"></span>
|
||||
</li>
|
||||
<li class="sync update">
|
||||
<span class="icon" data-l10n-id="drawer-sync-icon-button"></span>
|
||||
<span class="sync-progress" role="progressbar"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
--></section>
|
||||
|
||||
<section id="time-views" role="region">
|
||||
<gaia-header id="time-header" action="menu">
|
||||
<h1 id="current-month-year" aria-labelledby="current-time-indicator"></h1>
|
||||
<a aria-label="Add event" href="/event/add/" data-icon="add" data-l10n-id="new-event-link"></a>
|
||||
</gaia-header>
|
||||
|
||||
<div id="progress-indicator">
|
||||
<div id="throbber"></div>
|
||||
</div>
|
||||
|
||||
<section id="month-view" role="tabpanel" aria-labelledby="month"></section>
|
||||
|
||||
<!---
|
||||
When a day is selected you have
|
||||
access to the details here
|
||||
-->
|
||||
<section id="month-day-agenda" role="tabpanel">
|
||||
<div id="event-list-date"></div>
|
||||
<div id="empty-message" data-l10n-id="no-events" class="active">No Events</div>
|
||||
<section id="event-list" class="day-events" role="listbox" aria-labelledby="event-list-date">
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="day-view" class="multi-day day-view" role="tabpanel" aria-labelledby="day"><!--
|
||||
<div class="md__sticky">
|
||||
<span class="md__all-day" data-icon="all-day" aria-hidden="true" data-l10n-id="all-day"></span>
|
||||
<div class="md__alldays-wrapper">
|
||||
<section class="md__alldays"></section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md__main">
|
||||
<div class="md__main-content">
|
||||
<ol class="md__sidebar"></ol>
|
||||
<div class="md__days-wrapper">
|
||||
<section class="md__days"></section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
--></section>
|
||||
|
||||
<section id="week-view" class="multi-day week-view" role="tabpanel" aria-labelledby="week"><!--
|
||||
<div class="md__sticky">
|
||||
<span class="md__all-day" data-icon="all-day" aria-hidden="true" data-l10n-id="all-day"></span>
|
||||
<div class="md__alldays-wrapper">
|
||||
<section class="md__alldays"></section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md__main">
|
||||
<div class="md__main-content">
|
||||
<ol class="md__sidebar"></ol>
|
||||
<div class="md__days-wrapper">
|
||||
<section class="md__days"></section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
--></section>
|
||||
|
||||
<ul id="view-selector" role="tablist" class="bb-tablist">
|
||||
<li class="today" role="presentation" dir="ltr">
|
||||
<a aria-label="Today" id="today" href="#today" data-l10n-id="present"><span class="gaia-icon icon-calendar-today"></span></a>
|
||||
</li>
|
||||
<li class="month" role="presentation"><a role="tab" aria-selected="true" aria-controls="month-view" id="month" data-l10n-id="show-month-view" href="/month/">Month</a></li>
|
||||
<li class="week" role="presentation"><a role="tab" aria-selected="false" aria-controls="week-view" id="week" data-l10n-id="show-week-view" href="/week/">Week</a></li>
|
||||
<li class="day" role="presentation"><a role="tab" aria-selected="false" aria-controls="day-view" id="day" data-l10n-id="show-day-view" href="/day/">Day</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="fullscreen-view theme-settings" id="create-account-view" role="region"><!--
|
||||
<gaia-header id="create-account-header" action="back">
|
||||
<h1 data-l10n-id="add-account"></h1>
|
||||
</gaia-header>
|
||||
|
||||
<ol id="create-account-presets" class="link-list" role="listbox"></ol>
|
||||
--></section>
|
||||
<section class="fullscreen-view in-progress theme-settings" role="region" id="advanced-settings-view"><!--
|
||||
<gaia-header id="advanced-settings-header">
|
||||
<h1 data-l10n-id="advanced-settings-header"></h1>
|
||||
<a href="/settings/" data-l10n-id="done"></a>
|
||||
</gaia-header>
|
||||
|
||||
<section class="content">
|
||||
<div class="errors"></div>
|
||||
|
||||
<gaia-subheader id="account-list-header" class="account-list-header" skin="organic" role="heading" aria-level="2">
|
||||
<span data-l10n-id="account-list-header">Accounts</span>
|
||||
</gaia-subheader>
|
||||
|
||||
<ol class="account-list link-list" aria-labelledby="account-list-header" role="listbox">
|
||||
</ol>
|
||||
|
||||
<ol role="presentation">
|
||||
<li class="create-account-item" role="presentation">
|
||||
<button class="create-account" href="/select-preset/" data-l10n-id="advanced-settings-add-account">
|
||||
Add Account
|
||||
</button>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<gaia-subheader skin="organic" role="heading" aria-level="2">
|
||||
<span data-l10n-id="advanced-settings-personalization">Personalization</span>
|
||||
</gaia-subheader>
|
||||
|
||||
<ol class="settings-list link-list" role="presentation">
|
||||
<li role="presentation">
|
||||
<label>
|
||||
<span data-l10n-id="sync-frequency">Sync Calendar</span>
|
||||
<span class="button icon icon-dialog">
|
||||
<select id="setting-sync-frequency" name="syncFrequency">
|
||||
<option data-l10n-id="sync-frequency-15min" value="15">15 min</option>
|
||||
<option data-l10n-id="sync-frequency-30min" value="30">30 min</option>
|
||||
<option data-l10n-id="sync-frequency-manual" value="null">Manual</option>
|
||||
</select>
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<gaia-subheader skin="organic" role="heading" aria-level="2">
|
||||
<span data-l10n-id="advanced-settings-reminders">Reminders</span>
|
||||
</gaia-subheader>
|
||||
|
||||
<ol class="settings-list link-list" role="presentation">
|
||||
<li role="presentation">
|
||||
<label id="default-event-alarm">
|
||||
<span data-l10n-id="events">Events</span>
|
||||
<span class="button icon icon-dialog"></span>
|
||||
</label>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<label id="default-allday-alarm">
|
||||
<span data-l10n-id="allday-events">All-day Events</span>
|
||||
<span class="button icon icon-dialog"></span>
|
||||
</label>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
--></section>
|
||||
<section class="fullscreen-view" id="modify-event-view" role="region"><!--
|
||||
<gaia-header id="modify-event-header" action="close">
|
||||
<h1 data-l10n-id="new-event-header" class="save-text"></h1>
|
||||
<h1 data-l10n-id="edit-event-header" class="done-text"></h1>
|
||||
<button class="save">
|
||||
<span data-l10n-id="save" class="save-text">Save</span>
|
||||
<span data-l10n-id="done" class="done-text">Done</span>
|
||||
</button>
|
||||
</gaia-header>
|
||||
|
||||
<form>
|
||||
<ol>
|
||||
<li class="title">
|
||||
<input name="title" data-l10n-id="event-title" x-inputmode="latin-prose" placeholder="Title" type="text">
|
||||
</li>
|
||||
|
||||
<li class="location">
|
||||
<input name="location" data-l10n-id="event-location" x-inputmode="latin-prose" placeholder="Where?" type="text">
|
||||
</li>
|
||||
|
||||
<li class="allday">
|
||||
<gaia-switch name="allday">
|
||||
<label data-l10n-id="event-is-allday"></label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
|
||||
<li class="start-date">
|
||||
<span class="button icon icon-dialog" role="combobox" aria-autocomplete="none" data-l10n-id="start-date">
|
||||
<input name="startDate" data-l10n-id="event-start-date" placeholder="Date" class="hidden-input" aria-hidden="true" type="date">
|
||||
<span class="datetime" id="start-date-locale" data-type="date"></span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="start-time">
|
||||
<span class="button icon icon-dialog" role="combobox" aria-autocomplete="none" data-l10n-id="start-time">
|
||||
<input data-l10n-id="event-start-time" placeholder="Start Time" name="startTime" class="hidden-input" aria-hidden="true" type="time">
|
||||
<span class="datetime" id="start-time-locale" data-type="time"></span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="end-date">
|
||||
<span class="button icon icon-dialog" role="combobox" aria-autocomplete="none" data-l10n-id="end-date">
|
||||
<input name="endDate" data-l10n-id="event-end-date" placeholder="Date" class="hidden-input" aria-hidden="true" type="date">
|
||||
<span class="datetime" id="end-date-locale" data-type="date"></span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="end-time">
|
||||
<span class="button icon icon-dialog" role="combobox" aria-autocomplete="none" data-l10n-id="end-time">
|
||||
<input data-l10n-id="event-end-time" placeholder="End Time" name="endTime" class="hidden-input" aria-hidden="true" type="time">
|
||||
<span class="datetime" id="end-time-locale" data-type="time"></span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="current-calendar">
|
||||
<span class="button icon icon-dialog" aria-disabled="true">
|
||||
<input name="currentCalendar">
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="calendar-id">
|
||||
<span class="button icon icon-dialog">
|
||||
<select name="calendarId" class="loading">
|
||||
</select>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h3 data-l10n-id="remind-me" role="heading" aria-level="2">Remind me</h3>
|
||||
<div class="alarms">
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="notes">
|
||||
<textarea name="description" data-l10n-id="event-description" x-inputmode="latin-prose" placeholder="Notes"></textarea>
|
||||
</li>
|
||||
|
||||
</ol>
|
||||
<div class="delete">
|
||||
<a role="button" data-l10n-id="event-delete" class="danger delete-record bb-button">
|
||||
Delete Event
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<section role="status">
|
||||
<p class="errors"></p>
|
||||
</section>
|
||||
</form>
|
||||
--></section>
|
||||
<section class="fullscreen-view" id="event-view" role="region"><!--
|
||||
<gaia-header id="show-event-header" action="back">
|
||||
<h1 id="view-event-header" data-l10n-id="view-event-header"></h1>
|
||||
<button class="edit">
|
||||
<span data-l10n-id="edit">Edit</span>
|
||||
</button>
|
||||
</gaia-header>
|
||||
|
||||
<section data-type="list">
|
||||
<ul aria-labelledby="view-event-header">
|
||||
<li class="title">
|
||||
<h1 role="presentation"><span data-l10n-id="title" class="content"></span></h1>
|
||||
</li>
|
||||
|
||||
<li class="location">
|
||||
<span class="content" data-l10n-id="location"></span>
|
||||
</li>
|
||||
|
||||
<li class="duration-time">
|
||||
<span class="content" data-l10n-id="duration"></span>
|
||||
</li>
|
||||
|
||||
<li class="current-calendar">
|
||||
<div class="gaia-icon icon-calendar-dot" aria-hidden="true"></div>
|
||||
<span class="content" data-l10n-id="calendar"></span>
|
||||
</li>
|
||||
|
||||
<li class="alarms">
|
||||
<span data-l10n-id="reminder">Reminder:</span>
|
||||
<span class="content" role="list" data-l10n-id="alarms"></span>
|
||||
</li>
|
||||
|
||||
<li class="description">
|
||||
<span class="content" data-l10n-id="description"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
--></section>
|
||||
<section class="fullscreen-view skin-dark" id="oauth2" role="region"><!--
|
||||
<gaia-header id="oauth-header" action="back">
|
||||
<h1 class="oauth-browser-title">
|
||||
|
||||
</h1>
|
||||
</gaia-header>
|
||||
<div class="browser-container">
|
||||
</div>
|
||||
--></section>
|
||||
<section class="fullscreen-view skin-organic" id="modify-account-view" role="region"><!--
|
||||
<gaia-header id="modify-account-header" action="back">
|
||||
<h1 data-l10n-id="account-header"></h1>
|
||||
<button class="save" data-l10n-id="save">Save</button>
|
||||
</gaia-header>
|
||||
|
||||
<section class="during-progress" aria-live="assertive" aria-atomic="true">
|
||||
<div>
|
||||
<progress class="small" data-l10n-id="during-progress-loading"></progress>
|
||||
<span data-l10n-id="modify-account-setting-up-2">SeTtiNg uP ACcOunt</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<form class="modify-account-form">
|
||||
<fieldset>
|
||||
<ol role="presentation">
|
||||
<li class="user" role="presentation">
|
||||
<input name="user" data-l10n-id="field-user" x-inputmode="verbatim" placeholder="Username">
|
||||
</li>
|
||||
<li class="password" role="presentation">
|
||||
<input name="password" data-l10n-id="field-password" placeholder="Password" type="password">
|
||||
</li>
|
||||
<li class="full-url" role="presentation">
|
||||
<input name="fullUrl" data-l10n-id="field-full-url" placeholder="URL" type="url">
|
||||
|
||||
</li>
|
||||
<li class="sync-errors" role="presentation">
|
||||
<p data-l10n-id="modify-account-unable-to-sync">>uNaBle to SynC</p>
|
||||
</li>
|
||||
<li class="oauth2-sign-in" role="presentation">
|
||||
<button data-l10n-id="modify-account-sign-in" class="force-oauth2"></button>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a data-l10n-id="remove-account-data" role="button" href="#remove-account-dialog" class="danger delete-record bb-button">
|
||||
Remove Local Data
|
||||
</a>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<section role="status" aria-live="assertive" aria-atomic="true">
|
||||
<p class="errors"></p>
|
||||
</section>
|
||||
|
||||
<form role="dialog" data-type="confirm" id="remove-account-dialog">
|
||||
<section>
|
||||
<h1 data-l10n-id="remove-account-dialog">Remove Account</h1>
|
||||
<p data-l10n-id="remove-account-dialog-details">
|
||||
Removing this account will also remove your local copy of associated calendars and events.
|
||||
</p>
|
||||
</section>
|
||||
<menu>
|
||||
<button class="delete-cancel" data-l10n-id="cancel">Cancel</button>
|
||||
<button class="danger delete-confirm" data-l10n-id="confirm">Confirm</button>
|
||||
</menu>
|
||||
</form>
|
||||
--></section>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
define(['require','exports','module','db','ext/co','core','ext/threads','provider/factory','store/factory'],function(require, exports) {
|
||||
'use strict';
|
||||
|
||||
var Db = require('db');
|
||||
var co = require('ext/co');
|
||||
var core = require('core');
|
||||
//var object = require('common/object');
|
||||
var threads = require('ext/threads');
|
||||
|
||||
var service = threads.service('calendar');
|
||||
var loadDb;
|
||||
|
||||
core.db = new Db('b2g-calendar');
|
||||
core.providerFactory = require('provider/factory');
|
||||
core.storeFactory = require('store/factory');
|
||||
|
||||
function start() {
|
||||
if (loadDb != null) {
|
||||
return loadDb;
|
||||
}
|
||||
|
||||
loadDb = core.db.load();
|
||||
return loadDb;
|
||||
}
|
||||
|
||||
function method(endpoint, handler) {
|
||||
service.method(endpoint, (...args) => {
|
||||
return co(function *() {
|
||||
yield start();
|
||||
return handler.apply(null, args);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function stream(endpoint, handler) {
|
||||
service.stream(endpoint, function *() {
|
||||
var args = Array.slice(arguments);
|
||||
yield start();
|
||||
handler.apply(null, args);
|
||||
});
|
||||
}
|
||||
|
||||
function echo() {
|
||||
return Array.slice(arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will not work until the sync controller
|
||||
* (1) is not a class anymore
|
||||
* (2) doesn't need the app ns/object
|
||||
*/
|
||||
function saveAccount(details) {
|
||||
return co(function *() {
|
||||
var store = core.storeFactory.get('Account');
|
||||
var account;
|
||||
try {
|
||||
account = yield store.verifyAndPersist(details);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
yield syncAccount(account);
|
||||
return account;
|
||||
});
|
||||
}
|
||||
|
||||
function removeAccount(id) {
|
||||
return co(function *() {
|
||||
var store = core.storeFactory.get('Account');
|
||||
yield store.remove(id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will not work until the sync controller
|
||||
* (1) is not a class anymore
|
||||
* (2) doesn't need the app ns/object
|
||||
*/
|
||||
function syncAccount(account) {
|
||||
console.log('syncAccount', account);
|
||||
/*
|
||||
return co(function *() {
|
||||
var store = core.storeFactory.get('Calendar');
|
||||
var calendars;
|
||||
try {
|
||||
calendars = yield store.remotesByAccount(account._id);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
var syncCalendar = syncController.calendar.bind(null, account);
|
||||
yield Promise.all(object.map(calendars, syncCalendar));
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
function saveEvent(exists, event) {
|
||||
return co(function *() {
|
||||
var store = core.storeFactory.get('Event');
|
||||
var provider = yield store.providerFor(event);
|
||||
var capabilities;
|
||||
try {
|
||||
capabilities = yield provider.eventCapabilities(event.data);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
var capability = exists ? 'canUpdate' : 'canCreate';
|
||||
if (!capabilities[capability]) {
|
||||
return Promise.reject(new Error('User not allowed to perform operation'));
|
||||
}
|
||||
|
||||
var method = exists ? 'updateEvent' : 'createEvent';
|
||||
try {
|
||||
yield provider[method](event.data);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeEvent(event) {
|
||||
return co(function *() {
|
||||
var store = core.storeFactory.get('Event');
|
||||
var provider = yield store.providerFor(event);
|
||||
var capabilities;
|
||||
try {
|
||||
yield provider.eventCapabilities(event.data);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (!capabilities.canDelete) {
|
||||
return Promise.reject(new Error('User not allowed to perform operation'));
|
||||
}
|
||||
|
||||
yield provider.deleteEvent(event.data);
|
||||
});
|
||||
}
|
||||
|
||||
function setSetting(key, value) {
|
||||
var store = core.storeFactory.get('Setting');
|
||||
return store.set(key, value);
|
||||
}
|
||||
|
||||
function listAccounts(stream) {
|
||||
// TODO
|
||||
console.log('accounts/list', stream);
|
||||
}
|
||||
|
||||
function getAccount(stream, id) {
|
||||
// TODO
|
||||
console.log('accounts/get', stream, id);
|
||||
}
|
||||
|
||||
function listCalendars(stream) {
|
||||
// TODO
|
||||
console.log('calendars/list', stream);
|
||||
}
|
||||
|
||||
function getEvent(stream, id) {
|
||||
// TODO
|
||||
console.log('events/get', stream, id);
|
||||
}
|
||||
|
||||
function listBusytimes(stream, day) {
|
||||
// TODO
|
||||
console.log('busytimes/list', stream, day);
|
||||
}
|
||||
|
||||
function getSetting(stream, key) {
|
||||
// TODO
|
||||
console.log('settings/set', stream, key);
|
||||
}
|
||||
|
||||
method('echo', echo);
|
||||
method('accounts/create', saveAccount);
|
||||
method('accounts/update', saveAccount);
|
||||
method('accounts/remove', removeAccount);
|
||||
method('accounts/sync', syncAccount);
|
||||
method('events/create', saveEvent.bind(null, true));
|
||||
method('events/update', saveEvent.bind(null, true));
|
||||
method('events/remove', removeEvent);
|
||||
method('settings/set', setSetting);
|
||||
stream('accounts/list', listAccounts);
|
||||
stream('accounts/get', getAccount);
|
||||
stream('calendars/list', listCalendars);
|
||||
stream('events/get', getEvent);
|
||||
stream('busytimes/list', listBusytimes);
|
||||
stream('settings/get', getSetting);
|
||||
|
||||
exports.start = start;
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
importScripts('../ext/alameda.js');
|
||||
|
||||
require.config({
|
||||
baseUrl: '/js/backend',
|
||||
paths: {
|
||||
common: '/js/common',
|
||||
ext: '/js/ext'
|
||||
}
|
||||
});
|
||||
|
||||
require(['calendar_service'], service => service.start());
|
||||
14
assets/webapps/calendar.gaiamobile.org/js/backend/core.js
Normal file
14
assets/webapps/calendar.gaiamobile.org/js/backend/core.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
define(['require','exports','module'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
// this module is a "temporary" solution for all the circular dependencies
|
||||
// that we have on the calendar app at the moment. we inject all the
|
||||
// dependencies since stores, providers and db are tight coupled.
|
||||
// the plan is to not rely on the "app" namespace since we are splitting the
|
||||
// code into "backend" (worker) and "frontend" (main thread)
|
||||
|
||||
exports.db = null;
|
||||
exports.providerFactory = null;
|
||||
exports.storeFactory = null;
|
||||
|
||||
});
|
||||
457
assets/webapps/calendar.gaiamobile.org/js/backend/db.js
Normal file
457
assets/webapps/calendar.gaiamobile.org/js/backend/db.js
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
/* jshint loopfunc: true */
|
||||
define(['require','exports','module','models/account','provider/local','common/presets','common/responder','core','common/debug','common/promise','common/constants','common/next_tick','common/probably_parse_int','ext/uuid'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Account = require('models/account');
|
||||
var Local = require('provider/local');
|
||||
var Presets = require('common/presets');
|
||||
var Responder = require('common/responder');
|
||||
var core = require('core');
|
||||
var debug = require('common/debug')('db');
|
||||
var denodeifyAll = require('common/promise').denodeifyAll;
|
||||
var localCalendarId = require('common/constants').localCalendarId;
|
||||
var nextTick = require('common/next_tick');
|
||||
var probablyParseInt = require('common/probably_parse_int');
|
||||
var uuid = require('ext/uuid');
|
||||
|
||||
var idb = self.indexedDB;
|
||||
|
||||
const VERSION = 15;
|
||||
|
||||
var store = Object.freeze({
|
||||
events: 'events',
|
||||
accounts: 'accounts',
|
||||
calendars: 'calendars',
|
||||
busytimes: 'busytimes',
|
||||
settings: 'settings',
|
||||
alarms: 'alarms',
|
||||
icalComponents: 'icalComponents'
|
||||
});
|
||||
|
||||
function Db(name) {
|
||||
this.name = name;
|
||||
Responder.call(this);
|
||||
this._upgradeOperations = [];
|
||||
|
||||
denodeifyAll(this, ['load']);
|
||||
}
|
||||
module.exports = Db;
|
||||
|
||||
Db.prototype = {
|
||||
__proto__: Responder.prototype,
|
||||
|
||||
/**
|
||||
* Database connection
|
||||
*/
|
||||
connection: null,
|
||||
|
||||
load: function(callback) {
|
||||
debug(`Will load ${this.name} db.`);
|
||||
|
||||
var self = this;
|
||||
function setupDefaults() {
|
||||
if (self.oldVersion < 8) {
|
||||
self._setupDefaults(callback);
|
||||
} else {
|
||||
nextTick(callback);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isOpen) {
|
||||
return setupDefaults();
|
||||
}
|
||||
|
||||
this.open(VERSION, setupDefaults);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Opens connection to database.
|
||||
*
|
||||
* @param {Numeric} [version] version of database to open.
|
||||
* default to current version.
|
||||
* Should _only_ be used in testing.
|
||||
*
|
||||
* @param {Function} [callback] first argument is error, second
|
||||
* is result of operation or null
|
||||
* in the error case.
|
||||
*/
|
||||
open: function(version, callback) {
|
||||
if (typeof(version) === 'function') {
|
||||
callback = version;
|
||||
version = VERSION;
|
||||
}
|
||||
|
||||
var req = idb.open(this.name, version);
|
||||
this.version = version;
|
||||
|
||||
var self = this;
|
||||
|
||||
req.onsuccess = function() {
|
||||
self.isOpen = true;
|
||||
self.connection = req.result;
|
||||
|
||||
// if we have pending upgrade operations
|
||||
if (self._upgradeOperations.length) {
|
||||
var pending = self._upgradeOperations.length;
|
||||
|
||||
var operation;
|
||||
while ((operation = self._upgradeOperations.shift())) {
|
||||
operation.call(self, function next() {
|
||||
if (!(--pending)) {
|
||||
callback(null, self);
|
||||
self.emit('open', self);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
callback(null, self);
|
||||
self.emit('open', self);
|
||||
}
|
||||
};
|
||||
|
||||
req.onblocked = function(error) {
|
||||
callback(error, null);
|
||||
self.emit('error', error);
|
||||
};
|
||||
|
||||
req.onupgradeneeded = function(event) {
|
||||
self._handleVersionChange(req.result, event);
|
||||
};
|
||||
|
||||
req.onerror = function(error) {
|
||||
//TODO: steal asuth's error handling...
|
||||
callback(error, null);
|
||||
self.emit('error', error);
|
||||
};
|
||||
},
|
||||
|
||||
transaction: function(list, state) {
|
||||
var names;
|
||||
var self = this;
|
||||
|
||||
if (typeof(list) === 'string') {
|
||||
names = [];
|
||||
names.push(this.store[list] || list);
|
||||
} else {
|
||||
names = list.map(function(name) {
|
||||
return self.store[name] || name;
|
||||
});
|
||||
}
|
||||
|
||||
return this.connection.transaction(names, state || 'readonly');
|
||||
},
|
||||
|
||||
_handleVersionChange: function(db, event) {
|
||||
var newVersion = event.newVersion;
|
||||
var curVersion = event.oldVersion;
|
||||
var transaction = event.currentTarget.transaction;
|
||||
|
||||
this.hasUpgraded = true;
|
||||
this.oldVersion = curVersion;
|
||||
this.upgradedVersion = newVersion;
|
||||
|
||||
for (; curVersion < newVersion; curVersion++) {
|
||||
// if version is < 7 then it was from pre-production
|
||||
// db and we can safely discard its information.
|
||||
if (curVersion < 6) {
|
||||
// ensure clean state if this was an old db.
|
||||
var existingNames = db.objectStoreNames;
|
||||
for (var i = 0; i < existingNames.length; i++) {
|
||||
db.deleteObjectStore(existingNames[i]);
|
||||
}
|
||||
|
||||
// version 0-r are not maintained increment to 6
|
||||
curVersion = 6;
|
||||
|
||||
// busytimes has one event, has one calendar
|
||||
var busytimes = db.createObjectStore(
|
||||
store.busytimes,
|
||||
{ keyPath: '_id' }
|
||||
);
|
||||
|
||||
busytimes.createIndex(
|
||||
'end',
|
||||
'end.utc',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
|
||||
busytimes.createIndex(
|
||||
'eventId',
|
||||
'eventId',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
|
||||
// events -> belongs to calendar
|
||||
var events = db.createObjectStore(
|
||||
store.events,
|
||||
{ keyPath: '_id' }
|
||||
);
|
||||
|
||||
events.createIndex(
|
||||
'calendarId',
|
||||
'calendarId',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
|
||||
events.createIndex(
|
||||
'parentId',
|
||||
'parentId',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
|
||||
// accounts -> has many calendars
|
||||
db.createObjectStore(
|
||||
store.accounts, { keyPath: '_id', autoIncrement: true }
|
||||
);
|
||||
|
||||
// calendars -> has many events
|
||||
db.createObjectStore(
|
||||
store.calendars, { keyPath: '_id', autoIncrement: true }
|
||||
);
|
||||
|
||||
} else if (curVersion === 7) {
|
||||
db.createObjectStore(store.settings, { keyPath: '_id' });
|
||||
} else if (curVersion === 8) {
|
||||
var alarms = db.createObjectStore(
|
||||
store.alarms, { keyPath: '_id', autoIncrement: true }
|
||||
);
|
||||
|
||||
alarms.createIndex(
|
||||
'trigger',
|
||||
'trigger.utc',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
|
||||
alarms.createIndex(
|
||||
'busytimeId',
|
||||
'busytimeId',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
} else if (curVersion === 12) {
|
||||
var icalComponents = db.createObjectStore(
|
||||
store.icalComponents, { keyPath: 'eventId', autoIncrement: false }
|
||||
);
|
||||
|
||||
icalComponents.createIndex(
|
||||
'lastRecurrenceId',
|
||||
'lastRecurrenceId.utc',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
} else if (curVersion === 13) {
|
||||
var calendarStore = transaction.objectStore(store.calendars);
|
||||
calendarStore.createIndex(
|
||||
'accountId', 'accountId', { unique: false, multiEntry: false }
|
||||
);
|
||||
} else if (curVersion === 14) {
|
||||
// Bug 851003 - The database may have some busytimes and/or events
|
||||
// which have their calendarId field as a string rather than an int.
|
||||
// We need to fix the calendarIds and also remove any of the idb
|
||||
// objects that have deleted calendars.
|
||||
this.sanitizeEvents(transaction);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 1. Find all events with string calendar ids and index them.
|
||||
* 2. Check for each of the events whether the calendar
|
||||
* they reference still exists.
|
||||
* 3. Fix the events' calendarIds if the calendar still exists else
|
||||
* delete them.
|
||||
* @param {IDBTransaction} trans The active idb transaction during db
|
||||
* upgrade.
|
||||
*/
|
||||
sanitizeEvents: function(trans) {
|
||||
/**
|
||||
* Map from calendar ids to lists of event ids
|
||||
* which we've fixed with that id.
|
||||
* @type {Object.<number, Array.<number>>}
|
||||
*/
|
||||
var badCalendarIdToEventIds = {};
|
||||
|
||||
var objectStore = trans.objectStore(store.events);
|
||||
objectStore.openCursor().onsuccess = (function(evt) {
|
||||
var cursor = evt.target.result;
|
||||
if (!cursor) {
|
||||
return this._updateXorDeleteEvents(badCalendarIdToEventIds, trans);
|
||||
}
|
||||
|
||||
var calendarId = cursor.value.calendarId;
|
||||
if (typeof(calendarId) === 'number') {
|
||||
// Nothing to do here!
|
||||
return cursor.continue();
|
||||
}
|
||||
|
||||
// Record the bad reference.
|
||||
var eventIds = badCalendarIdToEventIds[calendarId] || [];
|
||||
eventIds.push(cursor.key);
|
||||
badCalendarIdToEventIds[calendarId] = eventIds;
|
||||
cursor.continue();
|
||||
}).bind(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* 1. Check for each of the events whether the calendar
|
||||
* they reference still exists.
|
||||
* 2. Fix the events' calendarIds if the calendar still exists else
|
||||
* delete them.
|
||||
*
|
||||
* @param {Object.<number, Array.<number>>} badCalendarIdToEventIds Map
|
||||
* from calendar ids to lists of event ids which we've fixed with
|
||||
* that id.
|
||||
* @param {IDBTransaction} trans The active idb transaction during db
|
||||
* upgrade.
|
||||
* @private
|
||||
*/
|
||||
_updateXorDeleteEvents: function(badCalendarIdToEventIds, trans) {
|
||||
var calendarIds = Object.keys(badCalendarIdToEventIds);
|
||||
calendarIds.forEach(function(calendarId) {
|
||||
//Bug 887698 cases for calendarIds of types strings or integers
|
||||
calendarId = probablyParseInt(calendarId);
|
||||
var eventIds = badCalendarIdToEventIds[calendarId];
|
||||
var calendars = trans.objectStore(store.calendars);
|
||||
calendars.get(calendarId).onsuccess = (function(evt) {
|
||||
var result = evt.target.result;
|
||||
if (result) {
|
||||
this._updateEvents(eventIds, calendarId, trans);
|
||||
} else {
|
||||
this._deleteEvents(eventIds, trans);
|
||||
}
|
||||
}).bind(this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a collection of events and the busytimes that depend on them.
|
||||
*
|
||||
* @param {Array.<number>} eventIds An array of event ids for the events.
|
||||
* @param {number} calendarId A numerical id to set as calendarId.
|
||||
* @param {IDBTransaction} trans The active idb transaction during db
|
||||
* upgrade.
|
||||
* @private
|
||||
*/
|
||||
_updateEvents: function(eventIds, calendarId, trans) {
|
||||
var eventStore = trans.objectStore(store.events);
|
||||
var busytimeStore = trans.objectStore(store.busytimes);
|
||||
var busytimeStoreIndexedByEventId = busytimeStore.index('eventId');
|
||||
|
||||
eventIds.forEach(function(eventId) {
|
||||
eventStore.get(eventId).onsuccess = function(evt) {
|
||||
var result = evt.target.result;
|
||||
result.calendarId = calendarId;
|
||||
eventStore.put(result);
|
||||
};
|
||||
|
||||
busytimeStoreIndexedByEventId.get(eventId).onsuccess = function(evt) {
|
||||
var result = evt.target.result;
|
||||
result.calendarId = calendarId;
|
||||
busytimeStore.put(result);
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a collection of events and the busytimes that depend on them.
|
||||
*
|
||||
* @param {Array.<number>} eventIds An array of event ids for the events.
|
||||
* @param {IDBTransaction} trans The active idb transaction during db
|
||||
* upgrade.
|
||||
* @private
|
||||
*/
|
||||
_deleteEvents: function(eventIds, trans) {
|
||||
var events = core.storeFactory.get('Event');
|
||||
eventIds.forEach(function(eventId) {
|
||||
events.remove(eventId, trans);
|
||||
});
|
||||
},
|
||||
|
||||
get store() {
|
||||
return store;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shortcut method for connection.close
|
||||
*/
|
||||
close: function() {
|
||||
if (this.connection) {
|
||||
this.isOpen = false;
|
||||
this.connection.close();
|
||||
this.connection = null;
|
||||
}
|
||||
},
|
||||
|
||||
clearNonCredentials: function(callback) {
|
||||
var stores = ['events', 'busytimes'];
|
||||
var trans = this.transaction(
|
||||
stores,
|
||||
'readwrite'
|
||||
);
|
||||
|
||||
trans.addEventListener('complete', callback);
|
||||
|
||||
stores.forEach(function(store) {
|
||||
store = trans.objectStore(store);
|
||||
store.clear();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup default values for initial calendar load.
|
||||
*/
|
||||
_setupDefaults: function(callback) {
|
||||
debug('Will setup defaults.');
|
||||
var storeFactory = core.storeFactory;
|
||||
var calendarStore = storeFactory.get('Calendar');
|
||||
var accountStore = storeFactory.get('Account');
|
||||
|
||||
var trans = this.transaction(
|
||||
['accounts', 'calendars'],
|
||||
'readwrite'
|
||||
);
|
||||
|
||||
if (callback) {
|
||||
trans.addEventListener('error', function(err) {
|
||||
callback(err);
|
||||
});
|
||||
|
||||
trans.addEventListener('complete', function() {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
var options = Presets.local.options;
|
||||
debug('Creating local calendar with options:', options);
|
||||
var account = new Account(options);
|
||||
account.preset = 'local';
|
||||
account._id = uuid();
|
||||
|
||||
var calendar = {
|
||||
_id: localCalendarId,
|
||||
accountId: account._id,
|
||||
remote: Local.defaultCalendar()
|
||||
};
|
||||
|
||||
accountStore.persist(account, trans);
|
||||
calendarStore.persist(calendar, trans);
|
||||
},
|
||||
|
||||
deleteDatabase: function(callback) {
|
||||
var req = idb.deleteDatabase(this.name);
|
||||
|
||||
req.onblocked = function() {
|
||||
// improve interface
|
||||
callback(new Error('blocked'));
|
||||
};
|
||||
|
||||
req.onsuccess = function(event) {
|
||||
callback(null, event);
|
||||
};
|
||||
|
||||
req.onerror = function(event) {
|
||||
callback(event, null);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
/**
|
||||
* EventMutations are a simple wrapper for a
|
||||
* set of idb transactions that span multiple
|
||||
* stores and calling out to the time controller
|
||||
* at appropriate times so it can cache the information.
|
||||
*
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
*
|
||||
* // create an event
|
||||
* var mutation = Calendar.EventMutations.create({
|
||||
* // this class does not handle/process events
|
||||
* // only persisting the records. Busytimes will
|
||||
* // automatically be recreated.
|
||||
* event: event
|
||||
* });
|
||||
*
|
||||
* // add an optional component
|
||||
* // mutation.icalComponent = component;
|
||||
*
|
||||
* mutation.commit(function(err) {
|
||||
* if (err) {
|
||||
* // handle it
|
||||
* }
|
||||
*
|
||||
* // success event/busytime/etc.. has been created
|
||||
* });
|
||||
*
|
||||
*
|
||||
* // update an event:
|
||||
* // update shares an identical api but will
|
||||
* // destroy/recreate associated busytimes with event.
|
||||
* var mutation = Calendar.EventMutations.update({
|
||||
* event: event
|
||||
* });
|
||||
*
|
||||
* mutation.commit(function() {
|
||||
*
|
||||
* });
|
||||
*
|
||||
*
|
||||
*/
|
||||
define(['require','exports','module','common/calc','core','ext/uuid'],function(require, exports) {
|
||||
'use strict';
|
||||
|
||||
var Calc = require('common/calc');
|
||||
var core = require('core');
|
||||
var uuid = require('ext/uuid');
|
||||
|
||||
/**
|
||||
* Create a single instance busytime for the given event object.
|
||||
*
|
||||
* @param {Object} event to create busytime for.
|
||||
*/
|
||||
function createBusytime(event) {
|
||||
return {
|
||||
_id: event._id + '-' + uuid.v4(),
|
||||
eventId: event._id,
|
||||
calendarId: event.calendarId,
|
||||
start: event.remote.start,
|
||||
end: event.remote.end
|
||||
};
|
||||
}
|
||||
|
||||
function Create(options) {
|
||||
if (options) {
|
||||
for (var key in options) {
|
||||
if (options.hasOwnProperty(key)) {
|
||||
this[key] = options[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Create.prototype = {
|
||||
commit: function(callback) {
|
||||
var storeFactory = core.storeFactory;
|
||||
var alarmStore = storeFactory.get('Alarm');
|
||||
var eventStore = storeFactory.get('Event');
|
||||
var busytimeStore = storeFactory.get('Busytime');
|
||||
var componentStore = storeFactory.get('IcalComponent');
|
||||
|
||||
var trans = core.db.transaction(
|
||||
eventStore._dependentStores,
|
||||
'readwrite'
|
||||
);
|
||||
|
||||
trans.oncomplete = function commitComplete() {
|
||||
callback(null);
|
||||
};
|
||||
|
||||
trans.onerror = function commitError(e) {
|
||||
callback(e.target.error);
|
||||
};
|
||||
|
||||
eventStore.persist(this.event, trans);
|
||||
|
||||
if (!this.busytime) {
|
||||
this.busytime = createBusytime(this.event);
|
||||
}
|
||||
|
||||
busytimeStore.persist(this.busytime, trans);
|
||||
|
||||
if (this.icalComponent) {
|
||||
componentStore.persist(this.icalComponent, trans);
|
||||
}
|
||||
|
||||
var alarms = this.event.remote.alarms;
|
||||
if (alarms && alarms.length) {
|
||||
var i = 0;
|
||||
var len = alarms.length;
|
||||
var now = Date.now();
|
||||
|
||||
var alarmTrans = core.db.transaction(
|
||||
['alarms'],
|
||||
'readwrite'
|
||||
);
|
||||
|
||||
for (; i < len; i++) {
|
||||
|
||||
var alarm = {
|
||||
startDate: {
|
||||
offset: this.busytime.start.offset,
|
||||
utc: this.busytime.start.utc + (alarms[i].trigger * 1000)
|
||||
},
|
||||
eventId: this.busytime.eventId,
|
||||
busytimeId: this.busytime._id
|
||||
};
|
||||
|
||||
var alarmDate = Calc.dateFromTransport(this.busytime.end).valueOf();
|
||||
if (alarmDate < now) {
|
||||
continue;
|
||||
}
|
||||
|
||||
alarmStore.persist(alarm, alarmTrans);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function Update() {
|
||||
Create.apply(this, arguments);
|
||||
}
|
||||
|
||||
Update.prototype = {
|
||||
commit: function(callback) {
|
||||
var storeFactory = core.storeFactory;
|
||||
var busytimeStore = storeFactory.get('Busytime');
|
||||
|
||||
var self = this;
|
||||
|
||||
// required so UI knows to refresh even in the
|
||||
// case where the start/end times are the same.
|
||||
busytimeStore.removeEvent(this.event._id, function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
Create.prototype.commit.call(self, callback);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.create = function createMutation(option) {
|
||||
return new Create(option);
|
||||
};
|
||||
|
||||
exports.update = function updateMutation(option) {
|
||||
return new Update(option);
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
define(['require','exports','module'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
function Account(options) {
|
||||
var key;
|
||||
|
||||
if (typeof(options) === 'undefined') {
|
||||
options = {};
|
||||
}
|
||||
|
||||
for (key in options) {
|
||||
if (options.hasOwnProperty(key)) {
|
||||
this[key] = options[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = Account;
|
||||
|
||||
Account.prototype = {
|
||||
|
||||
/**
|
||||
* Type of provider this
|
||||
* account requires.
|
||||
*/
|
||||
providerType: null,
|
||||
|
||||
/**
|
||||
* ID for this model always set
|
||||
* by the store when hydrating
|
||||
*/
|
||||
id: null,
|
||||
|
||||
/**
|
||||
* Which preset this model came from.
|
||||
*/
|
||||
preset: null,
|
||||
|
||||
/**
|
||||
* Domain for account
|
||||
*/
|
||||
domain: '',
|
||||
|
||||
/**
|
||||
* url/path for account
|
||||
*/
|
||||
entrypoint: '',
|
||||
|
||||
/**
|
||||
* Location where calendars can be found.
|
||||
* May be the same as entrypoint.
|
||||
*/
|
||||
calendarHome: '',
|
||||
|
||||
/**
|
||||
* username for authentication
|
||||
*/
|
||||
user: '',
|
||||
|
||||
/**
|
||||
* password for authentication
|
||||
*/
|
||||
password: '',
|
||||
|
||||
get fullUrl() {
|
||||
return this.domain + this.entrypoint;
|
||||
},
|
||||
|
||||
set fullUrl(value) {
|
||||
var protocolIdx = value.indexOf('://');
|
||||
|
||||
this.domain = value;
|
||||
this.entrypoint = '/';
|
||||
|
||||
if (protocolIdx !== -1) {
|
||||
protocolIdx += 3;
|
||||
// next chunk
|
||||
var domainChunk = value.substr(protocolIdx);
|
||||
var pathIdx = domainChunk.indexOf('/');
|
||||
|
||||
|
||||
if (pathIdx !== -1) {
|
||||
pathIdx = pathIdx + protocolIdx;
|
||||
|
||||
this.entrypoint = value.substr(pathIdx);
|
||||
this.domain = value.substr(0, pathIdx);
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Data only version of this object.
|
||||
* Used for both passing data between
|
||||
* threads (workers) and persisting data
|
||||
* in indexeddb.
|
||||
*/
|
||||
toJSON: function() {
|
||||
var output = {};
|
||||
var fields = [
|
||||
'entrypoint',
|
||||
'calendarHome',
|
||||
'domain',
|
||||
'password',
|
||||
'user',
|
||||
'providerType',
|
||||
'preset',
|
||||
'oauth',
|
||||
'error'
|
||||
];
|
||||
|
||||
fields.forEach(function(key) {
|
||||
output[key] = this[key];
|
||||
}, this);
|
||||
|
||||
if (this._id || this._id === 0) {
|
||||
output._id = this._id;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
define(['require','exports','module'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
function Cal(options) {
|
||||
if (typeof(options) === 'undefined') {
|
||||
options = {};
|
||||
}
|
||||
|
||||
this.remote = {};
|
||||
|
||||
for (var key in options) {
|
||||
if (options.hasOwnProperty(key)) {
|
||||
this[key] = options[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = Cal;
|
||||
|
||||
Cal.prototype = {
|
||||
|
||||
/**
|
||||
* Local copy of calendars remote state.
|
||||
* Taken from a calendar providers .toJSON method.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
remote: null,
|
||||
|
||||
/**
|
||||
* The date at which this calendar's events
|
||||
* where synchronized.
|
||||
*
|
||||
* @type {Date}
|
||||
*/
|
||||
firstEventSyncDate: null,
|
||||
|
||||
/**
|
||||
* Last sync token used in previous
|
||||
* event synchronization.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
lastEventSyncToken: '',
|
||||
|
||||
/**
|
||||
* Last date of event synchronization.
|
||||
* This is not going to be used
|
||||
* for any kind of serious operation
|
||||
* right now this is just for the UI.
|
||||
*
|
||||
* @type {Date}
|
||||
*/
|
||||
lastEventSyncDate: '',
|
||||
|
||||
/**
|
||||
* Indicates if calendar is displayed
|
||||
* locally in the ui.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
localDisplayed: true,
|
||||
|
||||
/**
|
||||
* Id of account this record
|
||||
* is associated with.
|
||||
*/
|
||||
accountId: '',
|
||||
|
||||
/**
|
||||
* Updates remote with data from a calendar provider.
|
||||
*
|
||||
* @param {Calendar.Provider.Calendar.Abstract} provider remote.
|
||||
*/
|
||||
updateRemote: function(provider) {
|
||||
var data = provider;
|
||||
if ('toJSON' in provider) {
|
||||
data = provider.toJSON();
|
||||
}
|
||||
|
||||
this.remote = data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if local and remote state differ
|
||||
* via sync tokens. Returns true when
|
||||
* local sync token and remote do not match.
|
||||
* Does not account for local changes only
|
||||
* when the server state has changed
|
||||
* and we have not yet synchronized.
|
||||
*
|
||||
* @return {Boolean} true when sync needed.
|
||||
*/
|
||||
eventSyncNeeded: function() {
|
||||
var local = this.lastEventSyncToken;
|
||||
var remote = this.remote.syncToken;
|
||||
|
||||
return local != remote;
|
||||
},
|
||||
|
||||
set name(name) {
|
||||
this.remote.name = name;
|
||||
return this.remote.name;
|
||||
},
|
||||
|
||||
set color(color) {
|
||||
this.remote.color = color;
|
||||
return this.remote.color;
|
||||
},
|
||||
|
||||
set description(description) {
|
||||
this.remote.description = description;
|
||||
return this.remote.description;
|
||||
},
|
||||
|
||||
get name() {
|
||||
return this.remote.name;
|
||||
},
|
||||
|
||||
get color() {
|
||||
var color = this.remote.color;
|
||||
if (color) {
|
||||
if (color.substr(0, 1) === '#') {
|
||||
return color.substr(0, 7);
|
||||
}
|
||||
}
|
||||
return this.remote.color;
|
||||
},
|
||||
|
||||
get description() {
|
||||
return this.remote.description;
|
||||
},
|
||||
|
||||
toJSON: function() {
|
||||
var result = {
|
||||
error: this.error,
|
||||
remote: this.remote,
|
||||
accountId: this.accountId,
|
||||
localDisplayed: this.localDisplayed,
|
||||
lastEventSyncDate: this.lastEventSyncDate,
|
||||
lastEventSyncToken: this.lastEventSyncToken,
|
||||
firstEventSyncDate: this.firstEventSyncDate
|
||||
};
|
||||
|
||||
if (this._id || this._id === 0) {
|
||||
result._id = this._id;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
define(['require','exports','module','common/calc','common/probably_parse_int'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Calc = require('common/calc');
|
||||
var probablyParseInt = require('common/probably_parse_int');
|
||||
|
||||
/**
|
||||
* Creates a wrapper around a event instance from the db
|
||||
*/
|
||||
function Event(data) {
|
||||
var isNew = false;
|
||||
|
||||
if (typeof(data) === 'undefined') {
|
||||
isNew = true;
|
||||
data = Object.create(null);
|
||||
data.remote = {};
|
||||
}
|
||||
|
||||
this.data = data;
|
||||
/** shortcut */
|
||||
var remote = this.remote = this.data.remote;
|
||||
|
||||
if ('start' in remote && !('startDate' in remote)) {
|
||||
remote.startDate = Calc.dateFromTransport(
|
||||
remote.start
|
||||
);
|
||||
}
|
||||
|
||||
if ('end' in remote && !('endDate' in remote)) {
|
||||
remote.endDate = Calc.dateFromTransport(
|
||||
remote.end
|
||||
);
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
this.resetToDefaults();
|
||||
}
|
||||
|
||||
var start = this.remote.startDate;
|
||||
var end = this.remote.endDate;
|
||||
|
||||
// the typeof check is to see if we have already
|
||||
// set the value in resetToDefaults (or prior)
|
||||
if (
|
||||
typeof(this._isAllDay) === 'undefined' &&
|
||||
Calc.isOnlyDate(start) &&
|
||||
Calc.isOnlyDate(end)
|
||||
) {
|
||||
// mostly to handle the case before the time
|
||||
// where we actually managed isAllDay as a setter.
|
||||
this.isAllDay = true;
|
||||
} else {
|
||||
// not on prototype intentionally because
|
||||
// we need to either need to resetToDefaults
|
||||
// or check startDate/endDate in the constructor.
|
||||
this.isAllDay = false;
|
||||
}
|
||||
}
|
||||
module.exports = Event;
|
||||
|
||||
Event.prototype = {
|
||||
|
||||
/**
|
||||
* Sets default values of an event.
|
||||
*/
|
||||
resetToDefaults: function() {
|
||||
var now = new Date();
|
||||
|
||||
this.isAllDay = false;
|
||||
|
||||
this.startDate = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate()
|
||||
);
|
||||
|
||||
this.endDate = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate() + 1
|
||||
);
|
||||
},
|
||||
|
||||
get _id() {
|
||||
return this.data._id;
|
||||
},
|
||||
|
||||
_setDate: function(date, field) {
|
||||
if (!(date instanceof Date)) {
|
||||
throw new TypeError('must pass instance of Date');
|
||||
}
|
||||
|
||||
var allDay = this.isAllDay;
|
||||
|
||||
if (allDay) {
|
||||
// clone the existing date
|
||||
date = new Date(date.valueOf());
|
||||
|
||||
// filter out the stuff we don't care about
|
||||
date.setHours(0);
|
||||
date.setMinutes(0);
|
||||
date.setSeconds(0);
|
||||
date.setMilliseconds(0);
|
||||
}
|
||||
|
||||
this.remote[field] = Calc.dateToTransport(
|
||||
date,
|
||||
null, // TODO allow setting tzid
|
||||
allDay
|
||||
);
|
||||
|
||||
this.remote[field + 'Date'] = date;
|
||||
},
|
||||
|
||||
/* start date */
|
||||
|
||||
get startDate() {
|
||||
return this.remote.startDate;
|
||||
},
|
||||
|
||||
set startDate(value) {
|
||||
this._setDate(value, 'start');
|
||||
},
|
||||
|
||||
/* end date */
|
||||
|
||||
get endDate() {
|
||||
return this.remote.endDate;
|
||||
},
|
||||
|
||||
set endDate(value) {
|
||||
this._setDate(value, 'end');
|
||||
},
|
||||
|
||||
set isAllDay(value) {
|
||||
this._isAllDay = value;
|
||||
|
||||
// send values through the their setter.
|
||||
if (this.endDate) {
|
||||
this.endDate = this.endDate;
|
||||
}
|
||||
|
||||
if (this.startDate) {
|
||||
this.startDate = this.startDate;
|
||||
}
|
||||
},
|
||||
|
||||
get isAllDay() {
|
||||
return this._isAllDay;
|
||||
},
|
||||
|
||||
/* associated records */
|
||||
|
||||
get calendarId() {
|
||||
return this.data.calendarId;
|
||||
},
|
||||
|
||||
set calendarId(value) {
|
||||
if (value && typeof(value) !== 'number') {
|
||||
value = probablyParseInt(value);
|
||||
}
|
||||
|
||||
this.data.calendarId = value;
|
||||
},
|
||||
|
||||
/* simple setters */
|
||||
|
||||
get syncToken() {
|
||||
return this.remote.syncToken;
|
||||
},
|
||||
|
||||
set syncToken(value) {
|
||||
this.remote.syncToken = value;
|
||||
},
|
||||
|
||||
get title() {
|
||||
return this.remote.title || '';
|
||||
},
|
||||
|
||||
set title(value) {
|
||||
this.remote.title = value;
|
||||
},
|
||||
|
||||
get description() {
|
||||
return this.remote.description || '';
|
||||
},
|
||||
|
||||
set description(value) {
|
||||
this.remote.description = value;
|
||||
},
|
||||
|
||||
get location() {
|
||||
return this.remote.location || '';
|
||||
},
|
||||
|
||||
set location(value) {
|
||||
this.remote.location = value;
|
||||
return this.remote.location;
|
||||
},
|
||||
|
||||
get alarms() {
|
||||
return this.remote.alarms || [];
|
||||
},
|
||||
|
||||
set alarms(value) {
|
||||
this.remote.alarms = value;
|
||||
return this.remote.alarms;
|
||||
},
|
||||
|
||||
/**
|
||||
* If data doesn't have any errors, the event
|
||||
* takes on the attributes of data.
|
||||
*
|
||||
* @param {Object} data, object that contains
|
||||
* at least some attributes of the event object.
|
||||
*
|
||||
* @return {Object} errors if validationErrors returns erros,
|
||||
* true otherwise.
|
||||
*/
|
||||
updateAttributes: function(data) {
|
||||
var errors = this.validationErrors(data);
|
||||
if (errors) {
|
||||
return errors;
|
||||
}
|
||||
for (var field in data) {
|
||||
this[field] = data[field];
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Validates the contents of the model.
|
||||
*
|
||||
* Output example:
|
||||
*
|
||||
* [
|
||||
* {
|
||||
* name: 'invalidDate',
|
||||
* properties: ['startDate', 'endDate']
|
||||
* }
|
||||
* //...
|
||||
* ]
|
||||
*
|
||||
* @param {Object} data, optional object that contains
|
||||
* at least some attributes of the event object.
|
||||
* @return {Array|False} see above.
|
||||
*/
|
||||
validationErrors: function(data) {
|
||||
var obj = data || this;
|
||||
var end = obj.endDate.valueOf();
|
||||
var start = obj.startDate.valueOf();
|
||||
var errors = [];
|
||||
|
||||
if (start >= end) {
|
||||
errors.push({
|
||||
name: 'start-after-end'
|
||||
});
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
define(['require','exports','module','common/promise','common/next_tick'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var denodeifyAll = require('common/promise').denodeifyAll;
|
||||
var nextTick = require('common/next_tick');
|
||||
|
||||
function Abstract(options) {
|
||||
var key;
|
||||
for (key in options) {
|
||||
if (options.hasOwnProperty(key)) {
|
||||
this[key] = options[key];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
denodeifyAll(this, [
|
||||
'eventCapabilities',
|
||||
'getAccount',
|
||||
'findCalendars',
|
||||
'syncEvents',
|
||||
'ensureRecurrencesExpanded',
|
||||
'createEvent',
|
||||
'updateEvent',
|
||||
'deleteEvent'
|
||||
]);
|
||||
}
|
||||
module.exports = Abstract;
|
||||
|
||||
Abstract.prototype = {
|
||||
// orange (used by local calendar)
|
||||
defaultColor: '#F97C17',
|
||||
|
||||
/**
|
||||
* Does this provider require credentials.
|
||||
*/
|
||||
useCredentials: false,
|
||||
|
||||
/**
|
||||
* Does this provider require a url.
|
||||
*/
|
||||
useUrl: false,
|
||||
|
||||
/**
|
||||
* Can provider sync with remote server?
|
||||
*/
|
||||
canSync: false,
|
||||
|
||||
/**
|
||||
* Can expand recurring events?
|
||||
*/
|
||||
canExpandRecurringEvents: false,
|
||||
|
||||
/**
|
||||
* - domain: (String)
|
||||
* - password: (String)
|
||||
* - user: (String)
|
||||
*
|
||||
* @param {Object} account user credentials.
|
||||
* @param {Function} callback node style (err, result).
|
||||
*/
|
||||
getAccount: function(account, callback) {},
|
||||
|
||||
/**
|
||||
* Attempts to find all calendars
|
||||
* for a given account.
|
||||
*
|
||||
*
|
||||
* account: (same as getAccount)
|
||||
*
|
||||
* @param {Object} account user credentials.
|
||||
* @param {Function} callback node style (err, result).
|
||||
*/
|
||||
findCalendars: function() {},
|
||||
|
||||
/**
|
||||
* Sync remote and local events.
|
||||
*
|
||||
*/
|
||||
syncEvents: function(account, calendar, callback) {},
|
||||
|
||||
/**
|
||||
* Ensures recurring events are expanded up to the given date.
|
||||
*
|
||||
* Its very important to correctly return the second callback arg
|
||||
* in the subclasses callback. When requiredExpansion is returned as true a
|
||||
* second call will likely be made to ensureRecurrencesExpanded to verify
|
||||
* there are no more pending events for the date (controller handles this).
|
||||
*
|
||||
* @param {Date} date to expand recurring events to.
|
||||
* @param {Function} callback [err, requiredExpansion].
|
||||
* first argument is error, second indicates if any expansion was done.
|
||||
*/
|
||||
ensureRecurrencesExpanded: function(date, callback) {},
|
||||
|
||||
/**
|
||||
* Update an event
|
||||
*
|
||||
* @param {Object} event record from event store.
|
||||
*
|
||||
* @param {Object} [busytime] optional busytime instance
|
||||
* when a busytime is passed the edit is treated
|
||||
* as an "exception" and will only edit the one recurrence
|
||||
* related to the busytime. This may result in the creation
|
||||
* of a new "event" related to the busytime.
|
||||
*/
|
||||
updateEvent: function(event, busytime, callback) {},
|
||||
|
||||
/**
|
||||
* Delete event
|
||||
*
|
||||
* @param {Object} event record from the event store.
|
||||
* @param {Object} [busytime] optional busytime instance
|
||||
* when given it will only remove this occurence/exception
|
||||
* of the event rather then the entire sequence of events.
|
||||
*/
|
||||
deleteEvent: function(event, busytime, callback) {},
|
||||
|
||||
/**
|
||||
* Create an event
|
||||
*/
|
||||
createEvent: function(event, callback) {},
|
||||
|
||||
/**
|
||||
* Returns an object with three keys used to
|
||||
* determine the capabilities of a given calendar.
|
||||
*
|
||||
* - canCreate (Boolean)
|
||||
* - canUpdate (Boolean)
|
||||
* - canDelete (Boolean)
|
||||
*
|
||||
* @param {Object} calendar full calendar details.
|
||||
*/
|
||||
calendarCapabilities: function(calendar) {
|
||||
return {
|
||||
canCreateEvent: true,
|
||||
canUpdateEvent: true,
|
||||
canDeleteEvent: true
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the capabilities of a single event.
|
||||
*
|
||||
* @param {Object} event object.
|
||||
* @param {Function} callback [err, caps].
|
||||
*/
|
||||
eventCapabilities: function(event, callback) {
|
||||
var caps = this.calendarCapabilities();
|
||||
|
||||
nextTick(function() {
|
||||
callback(null, {
|
||||
canCreate: caps.canCreateEvent,
|
||||
canUpdate: caps.canUpdateEvent,
|
||||
canDelete: caps.canDeleteEvent
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,712 @@
|
|||
define(['require','exports','module','./abstract','common/error','common/calc','./caldav_pull_events','common/error','common/error','./local','common/error','core','common/is_offline','event_mutations','common/next_tick'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Abstract = require('./abstract');
|
||||
var Authentication = require('common/error').Authentication;
|
||||
var Calc = require('common/calc');
|
||||
var CaldavPullEvents = require('./caldav_pull_events');
|
||||
var CalendarError = require('common/error');
|
||||
var InvalidServer = require('common/error').InvalidServer;
|
||||
var Local = require('./local');
|
||||
var ServerFailure = require('common/error').ServerFailure;
|
||||
var core = require('core');
|
||||
var isOffline = require('common/is_offline');
|
||||
var mutations = require('event_mutations');
|
||||
var nextTick = require('common/next_tick');
|
||||
|
||||
var CALDAV_ERROR_MAP = {
|
||||
'caldav-authentication': Authentication,
|
||||
'caldav-invalid-entrypoint': InvalidServer,
|
||||
'caldav-server-failure': ServerFailure
|
||||
};
|
||||
|
||||
function mapError(error, detail) {
|
||||
console.error('Error with name:', error.name);
|
||||
var calError = CALDAV_ERROR_MAP[error.name];
|
||||
if (!calError) {
|
||||
calError = new CalendarError(error.name, detail);
|
||||
} else {
|
||||
calError = new calError(detail);
|
||||
}
|
||||
|
||||
return calError;
|
||||
}
|
||||
|
||||
function CaldavProvider() {
|
||||
Abstract.apply(this, arguments);
|
||||
|
||||
var storeFactory = core.storeFactory;
|
||||
this.service = core.serviceController;
|
||||
this.accounts = storeFactory.get('Account');
|
||||
this.busytimes = storeFactory.get('Busytime');
|
||||
this.events = storeFactory.get('Event');
|
||||
this.icalComponents = storeFactory.get('IcalComponent');
|
||||
}
|
||||
module.exports = CaldavProvider;
|
||||
|
||||
CaldavProvider.prototype = {
|
||||
__proto__: Abstract.prototype,
|
||||
role: 'caldav',
|
||||
useUrl: true,
|
||||
useCredentials: true,
|
||||
canSync: true,
|
||||
canExpandRecurringEvents: true,
|
||||
|
||||
// allow us to test the offline behavior
|
||||
isOffline: isOffline,
|
||||
|
||||
/**
|
||||
* Number of dates in the past to sync.
|
||||
* This is usually from the first sync date.
|
||||
*/
|
||||
daysToSyncInPast: 30,
|
||||
|
||||
canCreateEvent: true,
|
||||
canUpdateEvent: true,
|
||||
canDeleteEvent: true,
|
||||
|
||||
hasAccountSettings: true,
|
||||
|
||||
/**
|
||||
* Error handling can be complex- this is the centralized location where
|
||||
* methods can send their error state and some context (an account).
|
||||
*
|
||||
* this._handleServiceError(
|
||||
* err,
|
||||
* { account: account, calendar: calendar }
|
||||
* );
|
||||
*
|
||||
* @param {Object} rawErr from service.
|
||||
* @param {Object} detail for the error.
|
||||
*/
|
||||
_handleServiceError: function(rawErr, detail) {
|
||||
var calendarErr = mapError(rawErr, detail);
|
||||
|
||||
// when we receive a permanent error we should mark the account with an
|
||||
// error.
|
||||
if (
|
||||
calendarErr instanceof Authentication ||
|
||||
calendarErr instanceof InvalidServer
|
||||
) {
|
||||
// there must always be an account
|
||||
if (detail.account) {
|
||||
// but we only mark it with a permanent error if its persisted.
|
||||
if (detail.account._id) {
|
||||
this.accounts.markWithError(detail.account, calendarErr);
|
||||
}
|
||||
} else {
|
||||
console.error('Permanent server error without an account!');
|
||||
}
|
||||
}
|
||||
|
||||
return calendarErr;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines the capabilities of a specific calendar.
|
||||
*
|
||||
* The .remote property should contain a .privilegeSet array
|
||||
* with the caldav specific names of privileges.
|
||||
* In the case where .privilegeSet is missing all privileges are granted.
|
||||
*
|
||||
* (see http://tools.ietf.org/html/rfc3744#section-5.4).
|
||||
*
|
||||
* - write-content: (PUT) can edit/add events
|
||||
* - unbind: (DELETE) remove events
|
||||
*
|
||||
*
|
||||
* There are aggregate values (write for example) but
|
||||
* the spec states the specific permissions must also be expanded
|
||||
* so even if they have full write permissions we only check
|
||||
* for write-content.
|
||||
*
|
||||
* @param {Object} calendar object with caldav remote details.
|
||||
* @return {Object} object with three properties
|
||||
* (canUpdate, canDelete, canCreate).
|
||||
*/
|
||||
calendarCapabilities: function(calendar) {
|
||||
var remote = calendar.remote;
|
||||
|
||||
if (!remote.privilegeSet) {
|
||||
return {
|
||||
canUpdateEvent: true,
|
||||
canDeleteEvent: true,
|
||||
canCreateEvent: true
|
||||
};
|
||||
}
|
||||
|
||||
var privilegeSet = remote.privilegeSet;
|
||||
var canWriteConent = privilegeSet.indexOf('write-content') !== -1;
|
||||
|
||||
return {
|
||||
canUpdateEvent: canWriteConent,
|
||||
canCreateEvent: canWriteConent,
|
||||
canDeleteEvent: privilegeSet.indexOf('unbind') !== -1
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the capabilities of a single event.
|
||||
*
|
||||
* @param {Object} event local object.
|
||||
* @param {Function} callback [err, caps].
|
||||
*/
|
||||
eventCapabilities: function(event, callback) {
|
||||
if (event.remote.isRecurring) {
|
||||
// XXX: for now recurring events cannot be edited
|
||||
nextTick(function() {
|
||||
callback(null, {
|
||||
canUpdate: false,
|
||||
canDelete: false,
|
||||
canCreate: false
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
var calendarStore = core.storeFactory.get('Calendar');
|
||||
|
||||
calendarStore.get(event.calendarId, function(err, calendar) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var caps = this.calendarCapabilities(
|
||||
calendar
|
||||
);
|
||||
|
||||
callback(null, {
|
||||
canCreate: caps.canCreateEvent,
|
||||
canUpdate: caps.canUpdateEvent,
|
||||
canDelete: caps.canDeleteEvent
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
getAccount: function(account, callback) {
|
||||
if (this.bailWhenOffline(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.service.request(
|
||||
'caldav',
|
||||
'getAccount',
|
||||
account,
|
||||
function(err, data) {
|
||||
if (err) {
|
||||
return callback(
|
||||
self._handleServiceError(err, { account: account })
|
||||
);
|
||||
}
|
||||
callback(null, data);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hook to format remote data if needed.
|
||||
*/
|
||||
formatRemoteCalendar: function(calendar) {
|
||||
if (!calendar.color) {
|
||||
calendar.color = this.defaultColor;
|
||||
}
|
||||
|
||||
return calendar;
|
||||
},
|
||||
|
||||
findCalendars: function(account, callback) {
|
||||
if (this.bailWhenOffline(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
function formatCalendars(err, data) {
|
||||
if (err) {
|
||||
return callback(self._handleServiceError(err, {
|
||||
account: account
|
||||
}));
|
||||
}
|
||||
|
||||
// format calendars if needed
|
||||
if (data) {
|
||||
for (var key in data) {
|
||||
data[key] = self.formatRemoteCalendar(data[key]);
|
||||
}
|
||||
}
|
||||
|
||||
callback(err, data);
|
||||
}
|
||||
|
||||
this.service.request(
|
||||
'caldav',
|
||||
'findCalendars',
|
||||
account.toJSON(),
|
||||
formatCalendars
|
||||
);
|
||||
},
|
||||
|
||||
_syncEvents: function(account, calendar, cached, callback) {
|
||||
|
||||
var startDate;
|
||||
// calculate the first date we want to sync
|
||||
if (!calendar.firstEventSyncDate) {
|
||||
startDate = Calc.createDay(new Date());
|
||||
|
||||
// will be persisted if sync is successful (clone is required)
|
||||
calendar.firstEventSyncDate = new Date(
|
||||
startDate.valueOf()
|
||||
);
|
||||
} else {
|
||||
startDate = new Date(calendar.firstEventSyncDate.valueOf());
|
||||
}
|
||||
|
||||
// start date - the amount of days is the sync range
|
||||
startDate.setDate(startDate.getDate() - this.daysToSyncInPast);
|
||||
|
||||
var options = {
|
||||
startDate: startDate,
|
||||
cached: cached
|
||||
};
|
||||
|
||||
var stream = this.service.stream(
|
||||
'caldav',
|
||||
'streamEvents',
|
||||
account.toJSON(),
|
||||
calendar.remote,
|
||||
options
|
||||
);
|
||||
|
||||
var pull = new CaldavPullEvents(stream, {
|
||||
account: account,
|
||||
calendar: calendar
|
||||
});
|
||||
|
||||
var calendarStore = core.storeFactory.get('Calendar');
|
||||
var syncStart = new Date();
|
||||
|
||||
var self = this;
|
||||
stream.request(function(err) {
|
||||
if (err) {
|
||||
return callback(
|
||||
self._handleServiceError(err, {
|
||||
account: account,
|
||||
calendar: calendar
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
var trans = pull.commit(function(commitErr) {
|
||||
if (commitErr) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
|
||||
/**
|
||||
* Successfully synchronizing a calendar indicates we can remove this
|
||||
* error.
|
||||
*/
|
||||
calendar.error = undefined;
|
||||
|
||||
calendar.lastEventSyncToken = calendar.remote.syncToken;
|
||||
calendar.lastEventSyncDate = syncStart;
|
||||
|
||||
calendarStore.persist(calendar, trans);
|
||||
|
||||
});
|
||||
|
||||
return pull;
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds list of event urls & sync tokens.
|
||||
*
|
||||
* @param {Calendar.Model.Calendar} calender model instance.
|
||||
* @param {Function} callback node style [err, results].
|
||||
*/
|
||||
_cachedEventsFor: function(calendar, callback) {
|
||||
var store = core.storeFactory.get('Event');
|
||||
|
||||
store.eventsForCalendar(calendar._id, function(err, results) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var list = Object.create(null);
|
||||
|
||||
var i = 0;
|
||||
var len = results.length;
|
||||
var item;
|
||||
|
||||
for (; i < len; i++) {
|
||||
item = results[i];
|
||||
list[item.remote.url] = {
|
||||
syncToken: item.remote.syncToken,
|
||||
id: item._id
|
||||
};
|
||||
}
|
||||
|
||||
callback(null, list);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sync remote and local events for a calendar.
|
||||
*/
|
||||
syncEvents: function(account, calendar, callback) {
|
||||
var self = this;
|
||||
|
||||
if (this.bailWhenOffline(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!calendar._id) {
|
||||
throw new Error('calendar must be assigned an _id');
|
||||
}
|
||||
|
||||
// Don't attempt to sync when provider cannot
|
||||
// or we have matching tokens
|
||||
if ((calendar.lastEventSyncToken &&
|
||||
calendar.lastEventSyncToken === calendar.remote.syncToken)) {
|
||||
return nextTick(callback);
|
||||
}
|
||||
|
||||
this._cachedEventsFor(calendar, function(err, results) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
self._syncEvents(
|
||||
account,
|
||||
calendar,
|
||||
results,
|
||||
callback
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* See abstract for contract details...
|
||||
*
|
||||
* Finds all ical components that have not been expanded
|
||||
* beyond the given point and expands / persists them.
|
||||
*
|
||||
* @param {Date} maxDate maximum date to expand to.
|
||||
* @param {Function} callback [err, didExpand].
|
||||
*/
|
||||
ensureRecurrencesExpanded: function(maxDate, callback) {
|
||||
var self = this;
|
||||
this.icalComponents.findRecurrencesBefore(maxDate,
|
||||
function(err, results) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!results.length) {
|
||||
callback(null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// CaldavPullRequest is based on a calendar/account combination
|
||||
// so we must group all of the outstanding components into
|
||||
// their calendars before we can begin expanding them.
|
||||
var groups = Object.create(null);
|
||||
results.forEach(function(comp) {
|
||||
var calendarId = comp.calendarId;
|
||||
if (!(calendarId in groups)) {
|
||||
groups[calendarId] = [];
|
||||
}
|
||||
|
||||
groups[calendarId].push(comp);
|
||||
});
|
||||
|
||||
var pullGroups = [];
|
||||
var pending = 0;
|
||||
var options = {
|
||||
maxDate: Calc.dateToTransport(maxDate)
|
||||
};
|
||||
|
||||
function next(err, pull) {
|
||||
pullGroups.push(pull);
|
||||
if (!(--pending)) {
|
||||
var trans = core.db.transaction(
|
||||
['icalComponents', 'alarms', 'busytimes'],
|
||||
'readwrite'
|
||||
);
|
||||
|
||||
trans.oncomplete = function() {
|
||||
callback(null, true);
|
||||
};
|
||||
|
||||
trans.onerror = function(event) {
|
||||
callback(event.result.error.name);
|
||||
};
|
||||
|
||||
pullGroups.forEach(function(pull) {
|
||||
pull.commit(trans);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (var calendarId in groups) {
|
||||
pending++;
|
||||
self._expandComponents(
|
||||
calendarId,
|
||||
groups[calendarId],
|
||||
options,
|
||||
next
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
_expandComponents: function(calendarId, comps, options, callback) {
|
||||
var calStore = core.storeFactory.get('Calendar');
|
||||
|
||||
calStore.ownersOf(calendarId, function(err, owners) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var calendar = owners.calendar;
|
||||
var account = owners.account;
|
||||
|
||||
var stream = this.service.stream(
|
||||
'caldav',
|
||||
'expandComponents',
|
||||
comps,
|
||||
options
|
||||
);
|
||||
|
||||
var pull = new CaldavPullEvents(
|
||||
stream,
|
||||
{
|
||||
account: account,
|
||||
calendar: calendar,
|
||||
stores: [
|
||||
'busytimes', 'alarms', 'icalComponents'
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
stream.request(function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
callback(null, pull);
|
||||
});
|
||||
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
createEvent: function(event, busytime, callback) {
|
||||
if (typeof(busytime) === 'function') {
|
||||
callback = busytime;
|
||||
busytime = null;
|
||||
}
|
||||
|
||||
|
||||
if (this.bailWhenOffline(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.events.ownersOf(event, fetchOwners);
|
||||
|
||||
var self = this;
|
||||
var calendar;
|
||||
var account;
|
||||
function fetchOwners(err, owners) {
|
||||
calendar = owners.calendar;
|
||||
account = owners.account;
|
||||
|
||||
self.service.request(
|
||||
'caldav',
|
||||
'createEvent',
|
||||
account,
|
||||
calendar.remote,
|
||||
event.remote,
|
||||
handleRequest
|
||||
);
|
||||
}
|
||||
|
||||
function handleRequest(err, remote) {
|
||||
if (err) {
|
||||
return callback(self._handleServiceError(err, {
|
||||
account: account,
|
||||
calendar: calendar
|
||||
}));
|
||||
}
|
||||
|
||||
var event = {
|
||||
_id: calendar._id + '-' + remote.id,
|
||||
calendarId: calendar._id
|
||||
};
|
||||
|
||||
var component = {
|
||||
eventId: event._id,
|
||||
ical: remote.icalComponent
|
||||
};
|
||||
|
||||
delete remote.icalComponent;
|
||||
event.remote = remote;
|
||||
|
||||
var create = mutations.create({
|
||||
event: event,
|
||||
icalComponent: component
|
||||
});
|
||||
|
||||
create.commit(function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, create.busytime, create.event);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
updateEvent: function(event, busytime, callback) {
|
||||
if (typeof(busytime) === 'function') {
|
||||
callback = busytime;
|
||||
busytime = null;
|
||||
}
|
||||
|
||||
if (this.bailWhenOffline(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.events.ownersOf(event, fetchOwners);
|
||||
|
||||
var self = this;
|
||||
var calendar;
|
||||
var account;
|
||||
|
||||
function fetchOwners(err, owners) {
|
||||
calendar = owners.calendar;
|
||||
account = owners.account;
|
||||
|
||||
self.icalComponents.get(
|
||||
event._id, fetchComponent
|
||||
);
|
||||
}
|
||||
|
||||
function fetchComponent(err, ical) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var details = {
|
||||
event: event.remote,
|
||||
icalComponent: ical.ical
|
||||
};
|
||||
|
||||
self.service.request(
|
||||
'caldav',
|
||||
'updateEvent',
|
||||
account,
|
||||
calendar.remote,
|
||||
details,
|
||||
handleUpdate
|
||||
);
|
||||
}
|
||||
|
||||
function handleUpdate(err, remote) {
|
||||
if (err) {
|
||||
callback(self._handleServiceError(err, {
|
||||
account: account,
|
||||
calendar: calendar
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
var component = {
|
||||
eventId: event._id,
|
||||
ical: remote.icalComponent
|
||||
};
|
||||
|
||||
delete remote.icalComponent;
|
||||
event.remote = remote;
|
||||
|
||||
var update = mutations.update({
|
||||
event: event,
|
||||
icalComponent: component
|
||||
});
|
||||
|
||||
update.commit(function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
callback(null, update.busytime, update.event);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
deleteEvent: function(event, busytime, callback) {
|
||||
if (typeof(busytime) === 'function') {
|
||||
callback = busytime;
|
||||
busytime = null;
|
||||
}
|
||||
|
||||
if (this.bailWhenOffline(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.events.ownersOf(event, fetchOwners);
|
||||
|
||||
var calendar;
|
||||
var account;
|
||||
var self = this;
|
||||
function fetchOwners(err, owners) {
|
||||
calendar = owners.calendar;
|
||||
account = owners.account;
|
||||
|
||||
self.service.request(
|
||||
'caldav',
|
||||
'deleteEvent',
|
||||
account,
|
||||
calendar.remote,
|
||||
event.remote,
|
||||
handleRequest
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function handleRequest(err) {
|
||||
if (err) {
|
||||
callback(self._handleServiceError(err, {
|
||||
account: account,
|
||||
calendar: calendar
|
||||
}));
|
||||
return;
|
||||
}
|
||||
Local.prototype.deleteEvent.call(self, event, busytime, callback);
|
||||
}
|
||||
},
|
||||
|
||||
bailWhenOffline: function(callback) {
|
||||
if (!this.offlineMessage && 'mozL10n' in window.navigator) {
|
||||
this.offlineMessage = window.navigator.mozL10n.get('error-offline');
|
||||
}
|
||||
|
||||
var ret = this.isOffline() && callback;
|
||||
if (ret) {
|
||||
var error = new Error();
|
||||
error.name = 'offline';
|
||||
error.message = this.offlineMessage;
|
||||
callback(error);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,391 @@
|
|||
define(['require','exports','module','common/calc','core','common/debug','ext/uuid'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Calc = require('common/calc');
|
||||
var core = require('core');
|
||||
var debug = require('common/debug')('pull events');
|
||||
var uuid = require('ext/uuid');
|
||||
|
||||
/**
|
||||
* Event synchronization class for caldav provider.
|
||||
*
|
||||
* Options:
|
||||
* - account: (Calendar.Models.Account) required
|
||||
* - calendar: (Calendar.Models.Calendar) required
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* // instance of a service stream
|
||||
* var stream;
|
||||
*
|
||||
* var pull = new Calendar.Provider.CaldavPullEvents(stream, {
|
||||
* calendar: calendarModel,
|
||||
* account: accountModel,
|
||||
* });
|
||||
*
|
||||
* stream.request(function() {
|
||||
* // stream is complete here the audit of
|
||||
* // events can be made. They are flushed
|
||||
* // to the cache where possible but not actually
|
||||
* // persisted in the database.
|
||||
*
|
||||
* // assuming we are ready commit the changes
|
||||
* pull.commit(function(err) {
|
||||
* // all changes have been committed at this point.
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* @param {Calendar.Responder} stream event emitter usually
|
||||
* a service stream.
|
||||
* @param {Object} options options for instance (see above).
|
||||
*/
|
||||
function PullEvents(stream, options) {
|
||||
if (options.calendar) {
|
||||
this.calendar = options.calendar;
|
||||
} else {
|
||||
throw new Error('.calendar option must be given');
|
||||
}
|
||||
|
||||
if (options.account) {
|
||||
this.account = options.account;
|
||||
} else {
|
||||
throw new Error('.account option must be given');
|
||||
}
|
||||
|
||||
stream.on('event', this);
|
||||
stream.on('component', this);
|
||||
stream.on('occurrence', this);
|
||||
stream.on('missingEvents', this);
|
||||
|
||||
this.icalQueue = [];
|
||||
this.eventQueue = [];
|
||||
this.busytimeQueue = [];
|
||||
this.alarmQueue = [];
|
||||
|
||||
var storeFactory = core.storeFactory;
|
||||
this._busytimeStore = storeFactory.get('Busytime');
|
||||
|
||||
// Catch account events to watch for mid-sync removal
|
||||
this._accountStore = storeFactory.get('Account');
|
||||
this._accountStore.on('remove', this._onRemoveAccount.bind(this));
|
||||
|
||||
this._aborted = false;
|
||||
this._trans = null;
|
||||
}
|
||||
module.exports = PullEvents;
|
||||
|
||||
PullEvents.prototype = {
|
||||
eventQueue: null,
|
||||
busytimeQueue: null,
|
||||
|
||||
/**
|
||||
* Get db id for busytime.
|
||||
*
|
||||
* @param {Object} busytime service sent busytime.
|
||||
*/
|
||||
busytimeIdFromRemote: function(busytime) {
|
||||
var eventId = this.eventIdFromRemote(busytime, !busytime.isException);
|
||||
|
||||
return busytime.start.utc + '-' +
|
||||
busytime.end.utc + '-' +
|
||||
eventId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get db id for event.
|
||||
*
|
||||
* @param {Object} event service sent event or '.remote'
|
||||
* property in db event.
|
||||
*
|
||||
* @param {Boolean} ignoreException when true will ignore
|
||||
* recurrence exceptions.
|
||||
*
|
||||
* @return {String} database object id.
|
||||
*/
|
||||
eventIdFromRemote: function(event, ignoreException) {
|
||||
var id = event.eventId || event.id;
|
||||
|
||||
if (!ignoreException && event.recurrenceId) {
|
||||
id += '-' + event.recurrenceId.utc;
|
||||
}
|
||||
|
||||
return this.calendar._id + '-' + id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Format an incoming event.
|
||||
*
|
||||
* @param {Object} event service sent event.
|
||||
*/
|
||||
formatEvent: function(event) {
|
||||
// get id or parent id we ignore the exception
|
||||
// rules here so children (exceptions) can lookup
|
||||
// their parents id.
|
||||
var id = this.eventIdFromRemote(event, true);
|
||||
|
||||
var result = Object.create(null);
|
||||
result.calendarId = this.calendar._id;
|
||||
result.remote = event;
|
||||
|
||||
if (event.recurrenceId) {
|
||||
result.parentId = id;
|
||||
// don't ignore the exceptions
|
||||
result._id = this.eventIdFromRemote(event);
|
||||
} else {
|
||||
result._id = id;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Formats and tags busytime sent from service.
|
||||
*
|
||||
* @param {Object} time service sent busytime.
|
||||
*/
|
||||
formatBusytime: function(time) {
|
||||
var eventId = this.eventIdFromRemote(time, !time.isException);
|
||||
var id = eventId + '-' + uuid.v4();
|
||||
var calendarId = this.calendar._id;
|
||||
|
||||
time._id = id;
|
||||
time.calendarId = calendarId;
|
||||
time.eventId = eventId;
|
||||
|
||||
if (time.alarms) {
|
||||
var i = 0;
|
||||
var len = time.alarms.length;
|
||||
var alarm;
|
||||
|
||||
for (; i < len; i++) {
|
||||
alarm = time.alarms[i];
|
||||
alarm.eventId = eventId;
|
||||
alarm.busytimeId = id;
|
||||
}
|
||||
}
|
||||
|
||||
return this._busytimeStore.initRecord(time);
|
||||
},
|
||||
|
||||
handleOccurrenceSync: function(item) {
|
||||
var alarms;
|
||||
|
||||
if ('alarms' in item) {
|
||||
alarms = item.alarms;
|
||||
delete item.alarms;
|
||||
|
||||
if (alarms.length) {
|
||||
var i = 0;
|
||||
var len = alarms.length;
|
||||
var now = Date.now();
|
||||
|
||||
for (; i < len; i++) {
|
||||
var alarm = {
|
||||
startDate: {},
|
||||
eventId: item.eventId,
|
||||
busytimeId: item._id
|
||||
};
|
||||
|
||||
// Copy the start object
|
||||
for (var j in item.start) {
|
||||
alarm.startDate[j] = item.start[j];
|
||||
}
|
||||
alarm.startDate.utc += (alarms[i].trigger * 1000);
|
||||
|
||||
var alarmDate = Calc.dateFromTransport(item.end);
|
||||
if (alarmDate.valueOf() < now) {
|
||||
continue;
|
||||
}
|
||||
this.alarmQueue.push(alarm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.busytimeQueue.push(item);
|
||||
},
|
||||
|
||||
handleComponentSync: function(component) {
|
||||
component.eventId = this.eventIdFromRemote(component);
|
||||
component.calendarId = this.calendar._id;
|
||||
|
||||
if (!component.lastRecurrenceId) {
|
||||
delete component.lastRecurrenceId;
|
||||
}
|
||||
|
||||
this.icalQueue.push(component);
|
||||
},
|
||||
|
||||
handleEventSync: function(event) {
|
||||
var exceptions = event.remote.exceptions;
|
||||
delete event.remote.exceptions;
|
||||
|
||||
var id = event._id;
|
||||
|
||||
// clear any busytimes that could possibly be
|
||||
// related to this event as we will be adding new
|
||||
// ones as part of the sync.
|
||||
this._busytimeStore.removeEvent(id);
|
||||
|
||||
this.eventQueue.push(event);
|
||||
|
||||
var component = event.remote.icalComponent;
|
||||
delete event.remote.icalComponent;
|
||||
|
||||
// don't save components for exceptions.
|
||||
// the parent has the ical data.
|
||||
if (!event.remote.recurrenceId) {
|
||||
this.icalQueue.push({
|
||||
data: component,
|
||||
eventId: event._id
|
||||
});
|
||||
}
|
||||
|
||||
if (exceptions) {
|
||||
for (var i = 0; i < exceptions.length; i++) {
|
||||
this.handleEventSync(this.formatEvent(exceptions[i]));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Account removal event handler. Aborts the rest of sync processing, if
|
||||
* the account deleted is the subject of the current sync.
|
||||
*
|
||||
* @param {String} database object id.
|
||||
*/
|
||||
_onRemoveAccount: function(id) {
|
||||
if (id === this.account._id) {
|
||||
// This is our account, so abort the sync.
|
||||
this.abort();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Abort the sync. After this, further events will be ignored and commit()
|
||||
* will do nothing.
|
||||
*/
|
||||
abort: function() {
|
||||
if (this._aborted) {
|
||||
// Bail, if already aborted.
|
||||
return;
|
||||
}
|
||||
// Flag that the sync should be aborted.
|
||||
this._aborted = true;
|
||||
if (this._trans) {
|
||||
// Attempt to abort the in-progress commit transaction
|
||||
this._trans.abort();
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
if (this._aborted) {
|
||||
// Ignore all events, if the sync has been aborted.
|
||||
return;
|
||||
}
|
||||
|
||||
var data = event.data;
|
||||
|
||||
switch (event.type) {
|
||||
case 'missingEvents':
|
||||
this.removeList = data[0];
|
||||
break;
|
||||
case 'occurrence':
|
||||
var occur = this.formatBusytime(data[0]);
|
||||
this.handleOccurrenceSync(occur);
|
||||
break;
|
||||
case 'component':
|
||||
this.handleComponentSync(data[0]);
|
||||
break;
|
||||
case 'event':
|
||||
var e = this.formatEvent(data[0]);
|
||||
this.handleEventSync(e);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Commit all pending records.
|
||||
*
|
||||
*
|
||||
* @param {IDBTransaction} [trans] optional transaction.
|
||||
* @param {Function} callback fired when transaction completes.
|
||||
*/
|
||||
commit: function(trans, callback) {
|
||||
var storeFactory = core.storeFactory;
|
||||
var eventStore = storeFactory.get('Event');
|
||||
var icalComponentStore = storeFactory.get('IcalComponent');
|
||||
var busytimeStore = storeFactory.get('Busytime');
|
||||
var alarmStore = storeFactory.get('Alarm');
|
||||
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = core.db.transaction(
|
||||
['calendars', 'events', 'busytimes', 'alarms', 'icalComponents'],
|
||||
'readwrite'
|
||||
);
|
||||
}
|
||||
|
||||
if (this._aborted) {
|
||||
// Commit nothing, if sync was aborted.
|
||||
return callback && callback(null);
|
||||
}
|
||||
|
||||
// Stash a reference to the transaction, in case we still need to abort.
|
||||
this._trans = trans;
|
||||
|
||||
var self = this;
|
||||
|
||||
this.eventQueue.forEach(function(event) {
|
||||
debug('add event', event);
|
||||
eventStore.persist(event, trans);
|
||||
});
|
||||
|
||||
this.icalQueue.forEach(function(ical) {
|
||||
debug('add component', ical);
|
||||
icalComponentStore.persist(ical, trans);
|
||||
});
|
||||
|
||||
this.busytimeQueue.forEach(function(busy) {
|
||||
debug('add busytime', busy);
|
||||
busytimeStore.persist(busy, trans);
|
||||
});
|
||||
|
||||
this.alarmQueue.forEach(function(alarm) {
|
||||
debug('add alarm', alarm);
|
||||
alarmStore.persist(alarm, trans);
|
||||
});
|
||||
|
||||
if (this.removeList) {
|
||||
this.removeList.forEach(function(id) {
|
||||
eventStore.remove(id, trans);
|
||||
});
|
||||
}
|
||||
|
||||
function handleError(e) {
|
||||
if (e && e.type !== 'abort') {
|
||||
console.error('Error persisting sync results', e);
|
||||
}
|
||||
|
||||
// if we have an event preventDefault so we don't trigger window.onerror
|
||||
if (e && e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
self._trans = null;
|
||||
callback && callback(e);
|
||||
}
|
||||
|
||||
trans.addEventListener('error', handleError);
|
||||
trans.addEventListener('abort', handleError);
|
||||
|
||||
|
||||
trans.addEventListener('complete', function() {
|
||||
self._trans = null;
|
||||
callback && callback(null);
|
||||
});
|
||||
|
||||
return trans;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
define(['require','exports','module','common/calc'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Calc = require('common/calc');
|
||||
|
||||
/**
|
||||
* This is a more crude version of what asuth does in email.
|
||||
* Right now this class is here only for debugging sync issues.
|
||||
* We need to add the settings so we can optionally turn this on.
|
||||
*/
|
||||
function EventLogger() {
|
||||
this.events = Object.create(null);
|
||||
this.occurs = Object.create(null);
|
||||
|
||||
this.richLog = {};
|
||||
}
|
||||
module.exports = EventLogger;
|
||||
|
||||
EventLogger.prototype = {
|
||||
displayLog: function(id, string) {
|
||||
if (!(id in this.richLog)) {
|
||||
this.richLog[id] = [];
|
||||
}
|
||||
this.richLog[id].push(string);
|
||||
},
|
||||
|
||||
addEvent: function(event) {
|
||||
var id = event.id;
|
||||
this.events[id] = event;
|
||||
var log = this.formatEvent(event);
|
||||
this.displayLog(id, log);
|
||||
},
|
||||
|
||||
addBusytime: function(busy) {
|
||||
var id = busy.eventId;
|
||||
this.occurs[id] = busy;
|
||||
this.displayLog(id, this.formatBusytime(busy));
|
||||
},
|
||||
|
||||
formatEvent: function(event) {
|
||||
var format = [
|
||||
'add event: (' + event.id + ')',
|
||||
'title:' + event.title,
|
||||
'start:' + (Calc.dateFromTransport(event.start)).toString(),
|
||||
'end:' + (Calc.dateFromTransport(event.end)).toString(),
|
||||
'isException:' + event.isException
|
||||
];
|
||||
|
||||
return format.join(' || ');
|
||||
},
|
||||
|
||||
formatBusytime: function(busy) {
|
||||
var event = this.events[busy.eventId];
|
||||
var title = 'busytime for event: ' + busy.eventId;
|
||||
|
||||
if (event) {
|
||||
title = 'busytime for event: ' + event.title;
|
||||
}
|
||||
|
||||
var format = [
|
||||
title,
|
||||
'start:' + (Calc.dateFromTransport(busy.start)).toString(),
|
||||
'end:' + (Calc.dateFromTransport(busy.end)).toString(),
|
||||
'isException:' + busy.isException
|
||||
];
|
||||
|
||||
return format.join(' || ');
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
define(['require','exports','module','common/debug','./caldav','./local'],function(require, exports) {
|
||||
'use strict';
|
||||
|
||||
var debug = require('common/debug')('provider/factory');
|
||||
|
||||
var ctors = {
|
||||
Caldav: require('./caldav'),
|
||||
Local: require('./local')
|
||||
};
|
||||
|
||||
var providers = exports.providers = Object.create(null);
|
||||
|
||||
exports.get = function(name) {
|
||||
if (!providers[name]) {
|
||||
try {
|
||||
debug(`Initializing provider ${name}`);
|
||||
providers[name] = new ctors[name]();
|
||||
} catch (e) {
|
||||
console.error('Error', e.name, e.message);
|
||||
console.error('Failed to initialize provider', name, e.stack);
|
||||
}
|
||||
}
|
||||
|
||||
return providers[name];
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
define(['require','exports','module','./abstract','core','common/constants','event_mutations','ext/uuid'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Abstract = require('./abstract');
|
||||
var core = require('core');
|
||||
var localCalendarId = require('common/constants').localCalendarId;
|
||||
var mutations = require('event_mutations');
|
||||
var uuid = require('ext/uuid');
|
||||
|
||||
function Local() {
|
||||
Abstract.apply(this, arguments);
|
||||
|
||||
var storeFactory = core.storeFactory;
|
||||
this.events = storeFactory.get('Event');
|
||||
this.busytimes = storeFactory.get('Busytime');
|
||||
this.alarms = storeFactory.get('Alarm');
|
||||
}
|
||||
module.exports = Local;
|
||||
|
||||
/**
|
||||
* Returns the details for the default calendars.
|
||||
*/
|
||||
Local.defaultCalendar = function() {
|
||||
// XXX: Make async
|
||||
//var l10nId = 'calendar-local';
|
||||
var name;
|
||||
|
||||
/* TODO(gareth): Make l10n available in backend.
|
||||
if ('mozL10n' in window.navigator) {
|
||||
name = window.navigator.mozL10n.get(l10nId);
|
||||
if (name === l10nId) {
|
||||
name = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
name = 'Offline calendar';
|
||||
}
|
||||
*/
|
||||
name = 'Offline calendar';
|
||||
|
||||
return {
|
||||
// XXX localize this name somewhere
|
||||
name: name,
|
||||
id: localCalendarId,
|
||||
color: Local.prototype.defaultColor
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
Local.prototype = {
|
||||
__proto__: Abstract.prototype,
|
||||
|
||||
canExpandRecurringEvents: false,
|
||||
|
||||
getAccount: function(account, callback) {
|
||||
callback(null, {});
|
||||
},
|
||||
|
||||
findCalendars: function(account, callback) {
|
||||
var list = {};
|
||||
list[localCalendarId] = Local.defaultCalendar();
|
||||
callback(null, list);
|
||||
},
|
||||
|
||||
syncEvents: function(account, calendar, cb) {
|
||||
cb(null);
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {Calendar.EventMutations.Create} mutation object.
|
||||
*/
|
||||
createEvent: function(event, callback) {
|
||||
// most providers come with their own built in
|
||||
// id system when creating a local event we need to generate
|
||||
// our own UUID.
|
||||
if (!event.remote.id) {
|
||||
// TOOD: uuid is provided by ext/uuid.js
|
||||
// if/when platform supports a safe
|
||||
// random number generator (values never conflict)
|
||||
// we can use that instead of uuid.js
|
||||
event.remote.id = uuid();
|
||||
}
|
||||
|
||||
var create = mutations.create({ event: event });
|
||||
create.commit(function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, create.busytime, create.event);
|
||||
});
|
||||
|
||||
return create;
|
||||
},
|
||||
|
||||
deleteEvent: function(event, busytime, callback) {
|
||||
if (typeof(busytime) === 'function') {
|
||||
callback = busytime;
|
||||
busytime = null;
|
||||
}
|
||||
|
||||
var storeFactory = core.storeFactory;
|
||||
storeFactory.get('Event').remove(event._id, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {Calendar.EventMutations.Update} mutation object.
|
||||
*/
|
||||
updateEvent: function(event, busytime, callback) {
|
||||
if (typeof(busytime) === 'function') {
|
||||
callback = busytime;
|
||||
busytime = null;
|
||||
}
|
||||
|
||||
var update = mutations.update({ event: event });
|
||||
update.commit(function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, update.busytime, update.event);
|
||||
});
|
||||
|
||||
return update;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,386 @@
|
|||
define(['require','exports','module','common/responder','core','common/promise','common/next_tick'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Responder = require('common/responder');
|
||||
var core = require('core');
|
||||
var denodeifyAll = require('common/promise').denodeifyAll;
|
||||
var nextTick = require('common/next_tick');
|
||||
|
||||
/**
|
||||
* Creates an abstract store instance.
|
||||
* Every store must contain a reference
|
||||
* to the database.
|
||||
*/
|
||||
function Abstract() {
|
||||
this._cached = Object.create(null);
|
||||
Responder.call(this);
|
||||
|
||||
denodeifyAll(this, [
|
||||
'persist',
|
||||
'all',
|
||||
'_allCached',
|
||||
'removeByIndex',
|
||||
'get',
|
||||
'remove',
|
||||
'count'
|
||||
]);
|
||||
}
|
||||
module.exports = Abstract;
|
||||
|
||||
Abstract.prototype = {
|
||||
__proto__: Responder.prototype,
|
||||
|
||||
_store: null,
|
||||
|
||||
/**
|
||||
* Stores that will need to be removed
|
||||
* when a record is removed from this store.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
_dependentStores: null,
|
||||
|
||||
_createModel: function(object, id) {
|
||||
if (typeof(id) !== 'undefined') {
|
||||
object._id = id;
|
||||
}
|
||||
|
||||
return object;
|
||||
},
|
||||
|
||||
_addToCache: function(object) {
|
||||
this._cached[object._id] = object;
|
||||
},
|
||||
|
||||
_clearCache: function() {
|
||||
this._cached = Object.create(null);
|
||||
},
|
||||
|
||||
_removeFromCache: function(id) {
|
||||
if (id in this._cached) {
|
||||
delete this._cached[id];
|
||||
}
|
||||
},
|
||||
|
||||
_transactionCallback: function(trans, callback) {
|
||||
if (callback) {
|
||||
trans.addEventListener('error', function(e) {
|
||||
callback(e);
|
||||
});
|
||||
|
||||
trans.addEventListener('complete', function() {
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an account to the database.
|
||||
*
|
||||
* @param {Object} object reference to account object to store.
|
||||
* @param {IDBTransaction} trans transaction.
|
||||
* @param {Function} callback node style callback.
|
||||
*/
|
||||
persist: function(object, trans, callback) {
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = undefined;
|
||||
}
|
||||
|
||||
if (!trans) {
|
||||
trans = core.db.transaction(
|
||||
this._dependentStores || this._store,
|
||||
'readwrite'
|
||||
);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var store = trans.objectStore(this._store);
|
||||
var data = this._objectData(object);
|
||||
var id;
|
||||
var model;
|
||||
|
||||
var putReq;
|
||||
var reqType = this._detectPersistType(object);
|
||||
|
||||
trans.addEventListener('error', function(event) {
|
||||
if (callback) {
|
||||
callback(event);
|
||||
}
|
||||
});
|
||||
|
||||
// determine type of event
|
||||
if (reqType === 'update') {
|
||||
putReq = store.put(data);
|
||||
} else {
|
||||
this._assignId(data);
|
||||
putReq = store.add(data);
|
||||
this._addDependents(object, trans);
|
||||
}
|
||||
|
||||
// when we have the id we can add the model to the cache.
|
||||
if (data._id) {
|
||||
id = data._id;
|
||||
model = self._createModel(object, id);
|
||||
self._addToCache(model);
|
||||
}
|
||||
|
||||
trans.addEventListener('complete', function(data) {
|
||||
if (!model) {
|
||||
id = putReq.result;
|
||||
model = self._createModel(object, id);
|
||||
self._addToCache(model);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(null, id, model);
|
||||
}
|
||||
|
||||
self.emit(reqType, id, model);
|
||||
self.emit('persist', id, model);
|
||||
});
|
||||
},
|
||||
|
||||
_allCached: function(callback) {
|
||||
var list = this._cached;
|
||||
nextTick(function() {
|
||||
callback(null, list);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads all records in the database
|
||||
* for this store.
|
||||
*
|
||||
* Using this function will fill
|
||||
* the cache with all records in the store.
|
||||
* As such this should only be used once
|
||||
* during the app life-cycle.
|
||||
*/
|
||||
all: function(callback) {
|
||||
if (this._allCallbacks) {
|
||||
this._allCallbacks.push(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// list of pending callbacks
|
||||
this._allCallbacks = [callback];
|
||||
|
||||
var self = this;
|
||||
var trans = core.db.transaction(this._store);
|
||||
var store = trans.objectStore(this._store);
|
||||
|
||||
function process(data) {
|
||||
return self._addToCache(self._createModel(data));
|
||||
}
|
||||
|
||||
function fireQueue(err, value) {
|
||||
var callback;
|
||||
while ((callback = self._allCallbacks.shift())) {
|
||||
callback(err, value);
|
||||
}
|
||||
}
|
||||
|
||||
store.mozGetAll().onsuccess = function(event) {
|
||||
event.target.result.forEach(process);
|
||||
};
|
||||
|
||||
trans.onerror = function(event) {
|
||||
fireQueue(event.target.error.name);
|
||||
};
|
||||
|
||||
trans.oncomplete = function() {
|
||||
fireQueue(null, self._cached);
|
||||
self.all = self._allCached;
|
||||
};
|
||||
},
|
||||
|
||||
_addDependents: function() {},
|
||||
_removeDependents: function(trans) {},
|
||||
|
||||
_detectPersistType: function(object) {
|
||||
return ('_id' in object) ? 'update' : 'add';
|
||||
},
|
||||
|
||||
_parseId: function(id) {
|
||||
return id;
|
||||
},
|
||||
|
||||
_assignId: function(obj) {
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all records a index value and removes
|
||||
* them from the cache. 'remove' events are *not* emitted
|
||||
* when removing in this manner for performance reasons.
|
||||
*
|
||||
* TODO: the test for this method still lives in the event store tests
|
||||
* where this code began should refactor those tests to be general
|
||||
* and live in the abstract tests.
|
||||
*
|
||||
* @param {String} indexName name of store index.
|
||||
* @param {Numeric} indexValue value in index.
|
||||
* @param {IDBTransation} [trans] optional transaction to reuse.
|
||||
* @param {Function} [callback] optional callback to use.
|
||||
* When called without a transaction chances
|
||||
* are you should pass a callback.
|
||||
*/
|
||||
removeByIndex: function(indexName, indexValue, trans, callback) {
|
||||
var self = this;
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = undefined;
|
||||
}
|
||||
|
||||
if (!trans) {
|
||||
trans = core.db.transaction(
|
||||
this._dependentStores || this._store,
|
||||
'readwrite'
|
||||
);
|
||||
}
|
||||
if (callback) {
|
||||
|
||||
trans.addEventListener('complete', function() {
|
||||
callback(null);
|
||||
});
|
||||
|
||||
trans.addEventListener('error', function(event) {
|
||||
callback(event);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var index = trans.objectStore(this._store).index(indexName);
|
||||
var req = index.openCursor(
|
||||
IDBKeyRange.only(indexValue)
|
||||
);
|
||||
|
||||
req.onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (cursor) {
|
||||
// remove deps first intentionally to mimic, removes normal behaviour
|
||||
self._removeDependents(cursor.primaryKey, trans);
|
||||
self._removeFromCache(cursor.primaryKey);
|
||||
cursor.delete();
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds a single record.
|
||||
*
|
||||
* Does not go through any cache or emit any events.
|
||||
*
|
||||
* @param {String} id id of record.
|
||||
* @param {IDBTransaction} [trans] optional transaction.
|
||||
* @param {Function} callback node style [err, record].
|
||||
*/
|
||||
get: function(id, trans, callback) {
|
||||
var self = this;
|
||||
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = null;
|
||||
}
|
||||
|
||||
if (!trans) {
|
||||
trans = core.db.transaction(this._store);
|
||||
}
|
||||
|
||||
var store = trans.objectStore(this._store);
|
||||
var req = store.get(this._parseId(id));
|
||||
|
||||
req.onsuccess = function() {
|
||||
var model;
|
||||
|
||||
if (req.result) {
|
||||
model = self._createModel(req.result);
|
||||
}
|
||||
|
||||
callback(null, model);
|
||||
};
|
||||
|
||||
req.onerror = function(event) {
|
||||
callback(event);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a object from the store.
|
||||
*
|
||||
* @param {String} id record reference.
|
||||
* @param {IDBTransaction} trans transaction.
|
||||
* @param {Function} callback node style callback.
|
||||
*/
|
||||
remove: function(id, trans, callback) {
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = undefined;
|
||||
}
|
||||
|
||||
if (!trans) {
|
||||
trans = core.db.transaction(
|
||||
this._dependentStores || this._store,
|
||||
'readwrite'
|
||||
);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var store = trans.objectStore(this._store);
|
||||
id = this._parseId(id);
|
||||
|
||||
store.delete(id);
|
||||
|
||||
this._removeDependents(id, trans);
|
||||
self.emit('preRemove', id);
|
||||
|
||||
trans.addEventListener('error', function(event) {
|
||||
if (callback) {
|
||||
callback(event);
|
||||
}
|
||||
});
|
||||
|
||||
trans.addEventListener('complete', function() {
|
||||
if (callback) {
|
||||
callback(null, id);
|
||||
}
|
||||
|
||||
self.emit('remove', id);
|
||||
|
||||
// intentionally after the callbacks...
|
||||
self._removeFromCache(id);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Find number of records in store.
|
||||
*
|
||||
* @param {Function} callback node style err/count.
|
||||
*/
|
||||
count: function(callback) {
|
||||
var trans = core.db.transaction(this._store);
|
||||
var store = trans.objectStore(this._store);
|
||||
|
||||
var req = store.count();
|
||||
|
||||
req.onsuccess = function() {
|
||||
callback(null, req.result);
|
||||
};
|
||||
|
||||
req.onerror = function(e) {
|
||||
callback(e);
|
||||
};
|
||||
},
|
||||
|
||||
_objectData: function(data) {
|
||||
if ('toJSON' in data) {
|
||||
return data.toJSON();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,400 @@
|
|||
define(['require','exports','module','models/account','./abstract','core','common/debug','common/promise','common/extend','common/next_tick','common/probably_parse_int'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var AccountModel = require('models/account');
|
||||
var Abstract = require('./abstract');
|
||||
var core = require('core');
|
||||
var debug = require('common/debug')('store/account');
|
||||
var denodeifyAll = require('common/promise').denodeifyAll;
|
||||
var extend = require('common/extend');
|
||||
var nextTick = require('common/next_tick');
|
||||
var probablyParseInt = require('common/probably_parse_int');
|
||||
|
||||
function Account() {
|
||||
Abstract.apply(this, arguments);
|
||||
|
||||
denodeifyAll(this, [
|
||||
'verifyAndPersist',
|
||||
'sync',
|
||||
'markWithError',
|
||||
'syncableAccounts',
|
||||
'availablePresets'
|
||||
]);
|
||||
}
|
||||
module.exports = Account;
|
||||
|
||||
Account.prototype = {
|
||||
__proto__: Abstract.prototype,
|
||||
|
||||
_store: 'accounts',
|
||||
|
||||
_parseId: probablyParseInt,
|
||||
|
||||
/**
|
||||
* Checks if a given account is a duplicate of another.
|
||||
*
|
||||
* @param {Calendar.Model.Account} model to check.
|
||||
* @param {Function} callback [err].
|
||||
*/
|
||||
_validateModel: function(model, callback) {
|
||||
this.all(function(err, allAccounts) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if this account is already registered
|
||||
for (var index in allAccounts) {
|
||||
if (
|
||||
allAccounts[index].user === model.user &&
|
||||
allAccounts[index].fullUrl === model.fullUrl &&
|
||||
allAccounts[index]._id !== model._id
|
||||
) {
|
||||
|
||||
var dupErr = new Error(
|
||||
'Cannot add two accounts with the same url / entry point'
|
||||
);
|
||||
|
||||
dupErr.name = 'account-exist';
|
||||
callback(dupErr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
|
||||
verifyAndPersist: function(model, callback) {
|
||||
var self = this;
|
||||
var provider = core.providerFactory.get(
|
||||
model.providerType
|
||||
);
|
||||
|
||||
provider.getAccount(model.toJSON(), function(err, data) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
model.error = undefined;
|
||||
|
||||
// if this works we always will get a calendar home.
|
||||
// This is used to find calendars.
|
||||
model.calendarHome = data.calendarHome;
|
||||
|
||||
// server may override properties on demand.
|
||||
extend(model, data);
|
||||
|
||||
self._validateModel(model, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.persist(model, callback);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Because this is a top-level store
|
||||
* when we remove an account all records
|
||||
* related to it must be removed.
|
||||
*/
|
||||
_dependentStores: [
|
||||
'accounts', 'calendars', 'events',
|
||||
'busytimes', 'alarms', 'icalComponents'
|
||||
],
|
||||
|
||||
_removeDependents: function(id, trans) {
|
||||
var store = core.storeFactory.get('Calendar');
|
||||
store.remotesByAccount(id, trans, function(err, related) {
|
||||
if (err) {
|
||||
return console.error('Error removing deps for account: ', id);
|
||||
}
|
||||
|
||||
var key;
|
||||
for (key in related) {
|
||||
store.remove(related[key]._id, trans);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Syncs all calendars for account.
|
||||
*
|
||||
* TODO: Deprecate this method in favor of new provider API's.
|
||||
*
|
||||
* @param {Calendar.Models.Account} account sync target.
|
||||
* @param {Function} callback node style.
|
||||
*/
|
||||
sync: function(account, callback) {
|
||||
//TODO: We need to block removal when syncing
|
||||
//OR after removal ensure everything created here
|
||||
//is purged.
|
||||
|
||||
var self = this;
|
||||
var provider = core.providerFactory.get(account.providerType);
|
||||
var calendarStore = core.storeFactory.get('Calendar');
|
||||
|
||||
var persist = [];
|
||||
|
||||
// remotesByAccount return an object indexed by remote ids
|
||||
var calendars;
|
||||
|
||||
// these are remote ids not local ones
|
||||
var originalIds;
|
||||
|
||||
function fetchExistingCalendars(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
calendars = results;
|
||||
originalIds = Object.keys(calendars);
|
||||
|
||||
provider.findCalendars(account, persistCalendars);
|
||||
}
|
||||
|
||||
function persistCalendars(err, remoteCals) {
|
||||
var key;
|
||||
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
for (key in remoteCals) {
|
||||
if (remoteCals.hasOwnProperty(key)) {
|
||||
var cal = remoteCals[key];
|
||||
var idx = originalIds.indexOf(key);
|
||||
|
||||
if (idx !== -1) {
|
||||
// update an existing calendar
|
||||
originalIds.splice(idx, 1);
|
||||
|
||||
var original = calendars[key];
|
||||
original.remote = cal;
|
||||
original.error = undefined;
|
||||
persist.push(original);
|
||||
} else {
|
||||
// create a new calendar
|
||||
persist.push(
|
||||
calendarStore._createModel({
|
||||
remote: new Object(cal),
|
||||
accountId: account._id
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// at this point whatever is left in originalIds
|
||||
// is considered a removed calendar.
|
||||
|
||||
// update / remove
|
||||
if (persist.length || originalIds.length) {
|
||||
var trans = core.db.transaction(
|
||||
self._dependentStores,
|
||||
'readwrite'
|
||||
);
|
||||
|
||||
originalIds.forEach(function(id) {
|
||||
calendarStore.remove(calendars[id]._id, trans);
|
||||
});
|
||||
|
||||
persist.forEach(function(object) {
|
||||
calendarStore.persist(object, trans);
|
||||
});
|
||||
|
||||
// event listeners must come at the end
|
||||
// because persist/remove also listen to
|
||||
// transaction complete events.
|
||||
trans.addEventListener('error', function(err) {
|
||||
callback(err);
|
||||
});
|
||||
|
||||
trans.addEventListener('complete', function() {
|
||||
callback(null);
|
||||
});
|
||||
} else {
|
||||
// invoke callback nothing to sync
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
calendarStore.remotesByAccount(
|
||||
account._id,
|
||||
fetchExistingCalendars
|
||||
);
|
||||
},
|
||||
|
||||
_createModel: function(obj, id) {
|
||||
if (!(obj instanceof AccountModel)) {
|
||||
obj = new AccountModel(obj);
|
||||
}
|
||||
|
||||
if (typeof(id) !== 'undefined') {
|
||||
obj._id = id;
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks given model with an error and sends an error event with the given
|
||||
* model
|
||||
*
|
||||
* This will trigger an 'error' event immediately with the given model.
|
||||
* The callback fires _after_ the event. Its entirely possible (under rare
|
||||
* conditions) that this operation will fail but the event will fire.
|
||||
*
|
||||
*
|
||||
* @param {Object} account model.
|
||||
* @param {Calendar.Error} error to mark model with.
|
||||
* @param {IDBTransaction} [trans] optional transaction.
|
||||
* @param {Function} [callback] optional called with [err, model].
|
||||
*/
|
||||
markWithError: function(account, error, trans, callback) {
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = null;
|
||||
}
|
||||
|
||||
if (!account._id) {
|
||||
throw new Error('given account must be persisted');
|
||||
}
|
||||
|
||||
if (!account.error) {
|
||||
account.error = {
|
||||
name: error.name,
|
||||
date: new Date(),
|
||||
count: 0
|
||||
};
|
||||
}
|
||||
|
||||
// increment the error count
|
||||
account.error.count++;
|
||||
|
||||
var calendarStore = core.storeFactory.get('Calendar');
|
||||
var self = this;
|
||||
function fetchedCalendars(err, calendars) {
|
||||
if (!trans) {
|
||||
trans = core.db.transaction(
|
||||
self._dependentStores,
|
||||
'readwrite'
|
||||
);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
console.error('Cannot fetch all calendars', err);
|
||||
return self.persist(account, trans, callback);
|
||||
}
|
||||
|
||||
for (var id in calendars) {
|
||||
calendarStore.markWithError(calendars[id], error, trans);
|
||||
}
|
||||
|
||||
self.persist(account, trans);
|
||||
self._transactionCallback(trans, callback);
|
||||
|
||||
}
|
||||
|
||||
// find related calendars and mark those too
|
||||
calendarStore.remotesByAccount(
|
||||
account._id,
|
||||
fetchedCalendars
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds and returns all accounts that can sync (based on their provider).
|
||||
*
|
||||
* accountStore.syncableAccounts(function(err, list) {
|
||||
* if (list.length === 0)
|
||||
* // hide sync options
|
||||
* });
|
||||
*
|
||||
* @param {Function} callback [Error err, Array accountList].
|
||||
*/
|
||||
syncableAccounts: function(callback) {
|
||||
debug('Will find syncable accounts...');
|
||||
this.all((err, list) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var results = [];
|
||||
for (var key in list) {
|
||||
var account = list[key];
|
||||
var provider = core.providerFactory.get(account.providerType);
|
||||
if (provider.canSync) {
|
||||
results.push(account);
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of available presets filtered by
|
||||
* the currently used presets in the database.
|
||||
*
|
||||
* Expected structure of the presetList is as follows:
|
||||
*
|
||||
* {
|
||||
* 'presetType': {
|
||||
* // most important field when true if the preset
|
||||
* // is available in the database that preset type
|
||||
* // will be excluded.
|
||||
* singleUse: true
|
||||
* providerType: 'X',
|
||||
* options: {}
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* @param {Object} presetList see example ^^^.
|
||||
* @param {Function} callback [err, ['presetKey', ...]].
|
||||
*/
|
||||
availablePresets: function(presetList, callback) {
|
||||
var results = [];
|
||||
var singleUse = {};
|
||||
var hasSingleUses = false;
|
||||
|
||||
for (var preset in presetList) {
|
||||
if (presetList[preset].singleUse) {
|
||||
hasSingleUses = true;
|
||||
singleUse[preset] = true;
|
||||
} else {
|
||||
results.push(preset);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasSingleUses) {
|
||||
return nextTick(function() {
|
||||
callback(null, results);
|
||||
});
|
||||
}
|
||||
|
||||
this.all(function(err, list) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var id in list) {
|
||||
var preset = list[id].preset;
|
||||
if (singleUse[preset]) {
|
||||
delete singleUse[preset];
|
||||
}
|
||||
}
|
||||
|
||||
// add un-used presets to the list.
|
||||
callback(null, results.concat(Object.keys(singleUse)));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
285
assets/webapps/calendar.gaiamobile.org/js/backend/store/alarm.js
Normal file
285
assets/webapps/calendar.gaiamobile.org/js/backend/store/alarm.js
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
define(['require','exports','module','./abstract','common/calc','common/create_dom_promise','core','common/debug','common/promise'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Abstract = require('./abstract');
|
||||
var Calc = require('common/calc');
|
||||
var createDOMPromise = require('common/create_dom_promise');
|
||||
var core = require('core');
|
||||
var debug = require('common/debug')('store/alarm');
|
||||
var denodeifyAll = require('common/promise').denodeifyAll;
|
||||
//var object = require('common/object');
|
||||
|
||||
/**
|
||||
* The alarm store can be thought of as a big queue.
|
||||
* Over time we add and remove alarm times related to
|
||||
* a specific busytime/event instance.
|
||||
* (and there could be multiple alarms per busytime/event).
|
||||
*
|
||||
* When `workQueue` is called records will be removed
|
||||
* from the queue (this object store) and added (via mozAlarms).
|
||||
*/
|
||||
function Alarm() {
|
||||
Abstract.apply(this, arguments);
|
||||
this._processQueue = this._processQueue.bind(this);
|
||||
|
||||
denodeifyAll(this, [
|
||||
'findAllByBusytimeId',
|
||||
'workQueue'
|
||||
]);
|
||||
}
|
||||
module.exports = Alarm;
|
||||
|
||||
Alarm.prototype = {
|
||||
__proto__: Abstract.prototype,
|
||||
|
||||
_store: 'alarms',
|
||||
|
||||
_dependentStores: ['alarms'],
|
||||
|
||||
/**
|
||||
* Number of hours ahead of current time to add new alarms.
|
||||
*
|
||||
* @type Numeric
|
||||
*/
|
||||
_alarmAddThresholdHours: 48,
|
||||
|
||||
/** disable caching */
|
||||
_addToCache: function() {},
|
||||
_removeFromCache: function() {},
|
||||
|
||||
/**
|
||||
* When false will not process queue automatically
|
||||
* (that is after each alarm transaction is complete).
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
autoQueue: false,
|
||||
|
||||
_processQueue: function() {
|
||||
this.workQueue();
|
||||
},
|
||||
|
||||
_objectData: function(object) {
|
||||
var data = Abstract.prototype._objectData.call(this, object);
|
||||
if (data.startDate) {
|
||||
// ensure the pending trigger is always in sync
|
||||
// with the current trigger whenever we update
|
||||
// the model.
|
||||
data.trigger = data.startDate;
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Manage the queue when alarms are added.
|
||||
*/
|
||||
_addDependents: function(obj, trans) {
|
||||
if (!this.autoQueue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// by using processQueue even if we added
|
||||
// 6000 alarms during a single transaction we only
|
||||
// receive the event once as addEventListener discards
|
||||
// duplicates.
|
||||
trans.addEventListener('complete', this._processQueue);
|
||||
},
|
||||
|
||||
/**
|
||||
* Move alarms over to the alarm api's database.
|
||||
*
|
||||
*
|
||||
* @param {Date} now date to use as current time.
|
||||
*
|
||||
* @param {Boolean} requiresAlarm attempts to ensure at
|
||||
* lest one alarm is added.
|
||||
*
|
||||
* @param {Function} callback node style callback.
|
||||
*/
|
||||
_moveAlarms: function(now, requiresAlarm, callback) {
|
||||
// use transport dates so we can handle timezones & floating time.
|
||||
var time = Calc.dateToTransport(now);
|
||||
var utc = time.utc;
|
||||
// keep adding events until we are beyond this time.
|
||||
var minimum = utc + (this._alarmAddThresholdHours * Calc.HOUR);
|
||||
|
||||
var request = core.db
|
||||
.transaction('alarms', 'readwrite')
|
||||
.objectStore('alarms')
|
||||
.index('trigger')
|
||||
.openCursor();
|
||||
|
||||
request.onerror = function() {
|
||||
callback(new Error('Alarm cursor failed to open.'));
|
||||
};
|
||||
|
||||
var past = []; // Alarms that should be fired immediately.
|
||||
var future = []; // Alarms that should fire in the future.
|
||||
request.onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (!cursor ||
|
||||
(cursor.key >= minimum && (!requiresAlarm || future.length))) {
|
||||
// We've pulled all (or at least enough) alarms into memory.
|
||||
// Now we can send them to the notifications controller
|
||||
// or the alarms api.
|
||||
return dispatchAlarms(past, future)
|
||||
.then(callback)
|
||||
.catch(error => debug('Error dispatching alarms:', error));
|
||||
}
|
||||
|
||||
var record = cursor.value;
|
||||
var date = Calc.dateFromTransport(record.trigger);
|
||||
var bucket = date < Date.now() ? past : future;
|
||||
bucket.push(record);
|
||||
// We need to save the trigger time so that we can send the
|
||||
// appropriate time to the alarms api. However, we want to mark
|
||||
// that we've handled this alarm so delete the trigger prop.
|
||||
record.triggered = record.trigger;
|
||||
delete record.trigger;
|
||||
cursor.update(record);
|
||||
cursor.continue();
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds single alarm by busytime id.
|
||||
*
|
||||
* @param {Object} related busytime object.
|
||||
* @param {IDBTransaction} [trans] optional transaction.
|
||||
* @param {Function} callback node style [err, records].
|
||||
*/
|
||||
findAllByBusytimeId: function(busytimeId, trans, callback) {
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = null;
|
||||
}
|
||||
|
||||
if (!trans) {
|
||||
trans = core.db.transaction(this._dependentStores);
|
||||
}
|
||||
|
||||
var store = trans.objectStore(this._store);
|
||||
var index = store.index('busytimeId');
|
||||
var key = IDBKeyRange.only(busytimeId);
|
||||
|
||||
index.mozGetAll(key).onsuccess = function(e) {
|
||||
callback(null, e.target.result);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Works queue putting alarms into the alarm api database where needed.
|
||||
*
|
||||
*/
|
||||
workQueue: function(now, callback) {
|
||||
if (typeof(now) === 'function') {
|
||||
callback = now;
|
||||
now = null;
|
||||
}
|
||||
|
||||
now = now || new Date();
|
||||
var alarms = navigator.mozAlarms;
|
||||
|
||||
if (!alarms) {
|
||||
if (callback) {
|
||||
callback(null);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var requiresAlarm = false;
|
||||
|
||||
/**
|
||||
* Why are we getting all alarms here?
|
||||
*
|
||||
* The alarms are designed to keep the total number
|
||||
* of entires (in mozAlarms) down but we should keep at
|
||||
* minimum one active at all times. For example if the user
|
||||
* has sync turned off and wants notifications we need
|
||||
* to have an alarm go off to trigger adding more alarms.
|
||||
*/
|
||||
var req = alarms.getAll();
|
||||
|
||||
//XXX: even with the good reasons above we need
|
||||
// to justify the perf cost here later.
|
||||
req.onsuccess = function(e) {
|
||||
var data = e.target.result;
|
||||
var len = data.length;
|
||||
var mozAlarm;
|
||||
|
||||
requiresAlarm = true;
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
mozAlarm = data[i].data;
|
||||
if (
|
||||
mozAlarm &&
|
||||
'eventId' in mozAlarm &&
|
||||
'trigger' in mozAlarm
|
||||
) {
|
||||
requiresAlarm = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
callback = callback || function() {};
|
||||
self._moveAlarms(
|
||||
now,
|
||||
requiresAlarm,
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
var msg = 'failed to get alarms';
|
||||
console.error('CALENDAR:', msg);
|
||||
|
||||
if (callback) {
|
||||
callback(new Error(msg));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function dispatchAlarms(past, future) {
|
||||
// If the alarm was meant to be triggered in the past,
|
||||
// we want to immediately issue a notification.
|
||||
// However, in bug 857284 we add the stipulation that
|
||||
// we shouldn't issue duplicates, so handle that here also.
|
||||
var eventToAlarm = {};
|
||||
past.forEach(alarm => {
|
||||
var event = alarm.eventId;
|
||||
if (!event || event in eventToAlarm) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventToAlarm[event] = alarm;
|
||||
});
|
||||
|
||||
/* TODO(gareth): Should not talk to notifications controller from backend.
|
||||
object.forEach(eventToAlarm, (event, alarm) => {
|
||||
core.notificationsController.onAlarm(alarm);
|
||||
});
|
||||
*/
|
||||
|
||||
// If the alarm should be triggered in the future, then we can create an
|
||||
// entry in the alarms api to wake us up to issue a notification for it
|
||||
// at the appropriate time.
|
||||
var alarms = navigator.mozAlarms;
|
||||
return Promise.all(future.map(alarm => {
|
||||
var timezone = alarm.triggered.tzid === Calc.FLOATING ?
|
||||
'ignoreTimezone' :
|
||||
'honorTimezone';
|
||||
return createDOMPromise(
|
||||
alarms.add(
|
||||
Calc.dateFromTransport(alarm.triggered),
|
||||
timezone,
|
||||
alarm
|
||||
)
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
define(['require','exports','module','./abstract','common/calc','common/binsearch','core','common/compare','common/promise'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Abstract = require('./abstract');
|
||||
var Calc = require('common/calc');
|
||||
var binsearch = require('common/binsearch');
|
||||
var core = require('core');
|
||||
var compare = require('common/compare');
|
||||
var denodeifyAll = require('common/promise').denodeifyAll;
|
||||
|
||||
/**
|
||||
* Objects saved in the busytime store:
|
||||
*
|
||||
* {
|
||||
* _id: (uuid),
|
||||
* start: Calendar.Calc.dateToTransport(x),
|
||||
* end: Calendar.Calc.dateToTransport(x),
|
||||
* eventId: eventId,
|
||||
* calendarId: calendarId
|
||||
* }
|
||||
*
|
||||
*/
|
||||
function Busytime() {
|
||||
Abstract.apply(this, arguments);
|
||||
this._setupCache();
|
||||
|
||||
denodeifyAll(this, [
|
||||
'removeEvent',
|
||||
'loadSpan'
|
||||
]);
|
||||
}
|
||||
module.exports = Busytime;
|
||||
|
||||
Busytime.prototype = {
|
||||
__proto__: Abstract.prototype,
|
||||
|
||||
_store: 'busytimes',
|
||||
|
||||
_dependentStores: ['alarms', 'busytimes'],
|
||||
|
||||
_setupCache: function() {
|
||||
this._byEventId = Object.create(null);
|
||||
},
|
||||
|
||||
_createModel: function(input, id) {
|
||||
return this.initRecord(input, id);
|
||||
},
|
||||
|
||||
initRecord: function(input, id) {
|
||||
var _super = Abstract.prototype._createModel;
|
||||
var model = _super.apply(this, arguments);
|
||||
model.startDate = Calc.dateFromTransport(model.start);
|
||||
model.endDate = Calc.dateFromTransport(model.end);
|
||||
return model;
|
||||
},
|
||||
|
||||
_removeDependents: function(id, trans) {
|
||||
core.storeFactory.get('Alarm').removeByIndex('busytimeId', id, trans);
|
||||
},
|
||||
|
||||
removeEvent: function(id, trans, callback) {
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = undefined;
|
||||
}
|
||||
|
||||
if (typeof(trans) === 'undefined') {
|
||||
trans = core.db.transaction(
|
||||
this._dependentStores,
|
||||
'readwrite'
|
||||
);
|
||||
}
|
||||
|
||||
// build the request using the inherited method
|
||||
var req = this.removeByIndex('eventId', id, trans);
|
||||
|
||||
// get the original method which handles the generic bit
|
||||
var success = req.onsuccess;
|
||||
|
||||
// override the default .onsuccess to get the ids
|
||||
// so we can emit remove events.
|
||||
var self = this;
|
||||
req.onsuccess = function(e) {
|
||||
var cursor = e.target.result;
|
||||
|
||||
if (cursor) {
|
||||
var id = cursor.primaryKey;
|
||||
self.emit('remove', id);
|
||||
}
|
||||
|
||||
success(e);
|
||||
};
|
||||
|
||||
this._transactionCallback(trans, callback);
|
||||
},
|
||||
|
||||
_startCompare: function(aObj, bObj) {
|
||||
var a = aObj.start.utc;
|
||||
var b = bObj.start.utc;
|
||||
return compare(a, b);
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads all busytimes in given timespan.
|
||||
*
|
||||
* @param {Calendar.Timespan} span timespan.
|
||||
* @param {Function} callback node style callback
|
||||
* where first argument is
|
||||
* an error (or null)
|
||||
* and the second argument
|
||||
* is a list of all loaded
|
||||
* busytimes in the timespan.
|
||||
*/
|
||||
loadSpan: function(span, callback) {
|
||||
var trans = core.db.transaction(this._store);
|
||||
var store = trans.objectStore(this._store);
|
||||
|
||||
var startPoint = Calc.dateToTransport(new Date(span.start));
|
||||
var endPoint = Calc.dateToTransport(new Date(span.end));
|
||||
|
||||
// XXX: we need to implement busytime chunking
|
||||
// to make this efficient.
|
||||
var keyRange = IDBKeyRange.lowerBound(startPoint.utc);
|
||||
|
||||
var index = store.index('end');
|
||||
var self = this;
|
||||
|
||||
index.mozGetAll(keyRange).onsuccess = function(e) {
|
||||
var data = e.target.result;
|
||||
|
||||
// sort data
|
||||
data = data.sort(self._startCompare);
|
||||
|
||||
// attempt to find a start time that occurs
|
||||
// after the end time of the span
|
||||
var idx = binsearch.insert(
|
||||
data,
|
||||
{ start: { utc: endPoint.utc + 1 } },
|
||||
self._startCompare
|
||||
);
|
||||
|
||||
// remove unrelated timespan...
|
||||
data = data.slice(0, idx);
|
||||
|
||||
// fire callback
|
||||
if (callback) {
|
||||
callback(null, data.map(function(item) {
|
||||
return self.initRecord(item);
|
||||
}));
|
||||
}
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
/* we don't use id based caching for busytimes */
|
||||
|
||||
_addToCache: function() {},
|
||||
_removeFromCache: function() {}
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,329 @@
|
|||
define(['require','exports','module','./abstract','models/calendar','core','common/promise','common/constants','common/probably_parse_int'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Abstract = require('./abstract');
|
||||
var CalendarModel = require('models/calendar');
|
||||
var core = require('core');
|
||||
var denodeifyAll = require('common/promise').denodeifyAll;
|
||||
var localCalendarId = require('common/constants').localCalendarId;
|
||||
var probablyParseInt = require('common/probably_parse_int');
|
||||
|
||||
function Store() {
|
||||
Abstract.apply(this, arguments);
|
||||
this._usedColors = [];
|
||||
|
||||
denodeifyAll(this, [
|
||||
'markWithError',
|
||||
'remotesByAccount',
|
||||
'sync',
|
||||
'providerFor',
|
||||
'ownersOf'
|
||||
]);
|
||||
}
|
||||
module.exports = Store;
|
||||
|
||||
/**
|
||||
* Remote calendar colors
|
||||
*/
|
||||
Store.REMOTE_COLORS = [
|
||||
'#00aacc', // light blue
|
||||
'#bad600', // light green
|
||||
'#df4784', // pink
|
||||
'#f9bc17', // yellow
|
||||
'#0766b7', // dark blue
|
||||
'#76a408', // dark green
|
||||
'#33a185' // teal
|
||||
];
|
||||
|
||||
/**
|
||||
* Local calendar color (orange)
|
||||
*/
|
||||
Store.LOCAL_COLOR = '#f97c17',
|
||||
|
||||
/**
|
||||
* List of possible calendar capabilities.
|
||||
*/
|
||||
Store.capabilities = {
|
||||
createEvent: 'canCreateEvent',
|
||||
updateEvent: 'canUpdateEvent',
|
||||
deleteEvent: 'canDeleteEvent'
|
||||
};
|
||||
|
||||
Store.prototype = {
|
||||
__proto__: Abstract.prototype,
|
||||
|
||||
_store: 'calendars',
|
||||
|
||||
_dependentStores: [
|
||||
'calendars', 'events', 'busytimes',
|
||||
'alarms', 'icalComponents'
|
||||
],
|
||||
|
||||
_parseId: probablyParseInt,
|
||||
|
||||
_createModel: function(obj, id) {
|
||||
if (!(obj instanceof CalendarModel)) {
|
||||
obj = new CalendarModel(obj);
|
||||
}
|
||||
|
||||
if (typeof(id) !== 'undefined') {
|
||||
obj._id = id;
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
|
||||
_removeDependents: function(id, trans) {
|
||||
var store = core.storeFactory.get('Event');
|
||||
store.removeByIndex('calendarId', id, trans);
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks a given calendar with an error.
|
||||
*
|
||||
* Emits a 'error' event immediately.. This method is typically
|
||||
* triggered by an account wide error.
|
||||
*
|
||||
*
|
||||
* @param {Object} calendar model.
|
||||
* @param {Calendar.Error} error for given calendar.
|
||||
* @param {IDBTransaction} transaction optional.
|
||||
* @param {Function} callback fired when model is saved [err, id, model].
|
||||
*/
|
||||
markWithError: function(calendar, error, trans, callback) {
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = null;
|
||||
}
|
||||
|
||||
if (!calendar._id) {
|
||||
throw new Error('given calendar must be persisted.');
|
||||
}
|
||||
|
||||
calendar.error = {
|
||||
name: error.name,
|
||||
date: new Date()
|
||||
};
|
||||
|
||||
this.persist(calendar, trans, callback);
|
||||
},
|
||||
|
||||
persist: function(calendar, trans, callback) {
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = undefined;
|
||||
}
|
||||
|
||||
this._updateCalendarColor(calendar);
|
||||
|
||||
var cb = callback;
|
||||
var cached = this._cached[calendar._id];
|
||||
|
||||
if (cached && cached.localDisplayed !== calendar.localDisplayed) {
|
||||
cb = function(err, id, model) {
|
||||
this.emit('calendarVisibilityChange', id, model);
|
||||
callback(err, id, model);
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
Abstract.prototype.persist.call(this, calendar, trans, cb);
|
||||
},
|
||||
|
||||
remove: function(id, trans, callback) {
|
||||
this._removeCalendarColorFromCache(id);
|
||||
Abstract.prototype.remove.apply(this, arguments);
|
||||
},
|
||||
|
||||
_updateCalendarColor: function(calendar) {
|
||||
// we avoid storing multiple colors for same calendar in case of an
|
||||
// "update" operation
|
||||
this._removeCalendarColorFromCache(calendar._id);
|
||||
this._setCalendarColor(calendar);
|
||||
// cache is built asynchronously, we need to store the color as soon as
|
||||
// possible to avoid adding same color multiple times in a row (eg.
|
||||
// account with multiple calendars will call persist multiple times)
|
||||
this._usedColors.push(calendar.color);
|
||||
},
|
||||
|
||||
_removeCalendarColorFromCache: function(id) {
|
||||
// we need to remove the color from index as soon as possible to avoid
|
||||
// race conditions (remove is async)
|
||||
var color = this.getColorByCalendarId(id);
|
||||
var index = this._usedColors.indexOf(color);
|
||||
if (index !== -1) {
|
||||
this._usedColors.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
getColorByCalendarId: function(id) {
|
||||
return this._cached[id] && this._cached[id].color;
|
||||
},
|
||||
|
||||
_setCalendarColor: function(calendar) {
|
||||
// local calendar should always use the same color
|
||||
if (calendar._id === localCalendarId) {
|
||||
calendar.color = Store.LOCAL_COLOR;
|
||||
return;
|
||||
}
|
||||
|
||||
// restore previous color only if it is part of the palette, otherwise we
|
||||
// get the next available color (or least used)
|
||||
var prevColor = this.getColorByCalendarId(calendar._id);
|
||||
if (prevColor && Store.REMOTE_COLORS.indexOf(prevColor) !== -1) {
|
||||
calendar.color = prevColor;
|
||||
} else {
|
||||
calendar.color = this._getNextColor();
|
||||
}
|
||||
},
|
||||
|
||||
_getNextColor: function() {
|
||||
var available = Store.REMOTE_COLORS.filter(function(color) {
|
||||
return this._usedColors.indexOf(color) === -1;
|
||||
}, this);
|
||||
|
||||
return available.length ? available[0] : this._getLeastUsedColor();
|
||||
},
|
||||
|
||||
_getLeastUsedColor: function() {
|
||||
var counter = {};
|
||||
this._usedColors.forEach(function(color) {
|
||||
counter[color] = (counter[color] || 0) + 1;
|
||||
});
|
||||
|
||||
var leastUsedColor;
|
||||
var leastUsedCount = Infinity;
|
||||
for (var color in counter) {
|
||||
if (counter[color] < leastUsedCount) {
|
||||
leastUsedCount = counter[color];
|
||||
leastUsedColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
return leastUsedColor;
|
||||
},
|
||||
|
||||
shouldDisplayCalendar: function(calendarId) {
|
||||
var calendar = this._cached[calendarId];
|
||||
return calendar && calendar.localDisplayed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Find calendars in a specific account.
|
||||
* Results will be returned in an object where
|
||||
* the key is the remote.id and the value is the calendar.
|
||||
*
|
||||
* @param {String|Numeric} accountId id of account.
|
||||
* @param {Function} callback [err, object] see above.
|
||||
*/
|
||||
remotesByAccount: function(accountId, trans, callback) {
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = null;
|
||||
}
|
||||
|
||||
if (!trans) {
|
||||
trans = core.db.transaction(this._store);
|
||||
}
|
||||
|
||||
var store = trans.objectStore(this._store);
|
||||
|
||||
var reqKey = IDBKeyRange.only(accountId);
|
||||
var req = store.index('accountId').mozGetAll(reqKey);
|
||||
|
||||
req.onerror = function remotesError(e) {
|
||||
callback(e.target.error);
|
||||
};
|
||||
|
||||
var self = this;
|
||||
req.onsuccess = function remotesSuccess(e) {
|
||||
var result = Object.create(null);
|
||||
e.target.result.forEach(function(calendar) {
|
||||
result[calendar.remote.id] = self._createModel(
|
||||
calendar,
|
||||
calendar._id
|
||||
);
|
||||
});
|
||||
|
||||
callback(null, result);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Sync remote and local events for a calendar.
|
||||
*
|
||||
* TODO: Deprecate use of this function in favor of a sync methods
|
||||
* inside of providers.
|
||||
*/
|
||||
sync: function(account, calendar, callback) {
|
||||
var provider = core.providerFactory.get(account.providerType);
|
||||
provider.syncEvents(account, calendar, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shortcut to find provider for calendar.
|
||||
*
|
||||
* @param {Calendar.Models.Calendar} calendar input calendar.
|
||||
* @param {Function} callback [err, provider].
|
||||
*/
|
||||
providerFor: function(calendar, callback) {
|
||||
this.ownersOf(calendar, function(err, owners) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, core.providerFactory.get(owners.account.providerType));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds calendar/account for a given event.
|
||||
*
|
||||
* TODO: think about moving this function into its
|
||||
* own file as a mixin.
|
||||
*
|
||||
* @param {Object|String|Numeric} objectOrId must contain .calendarId.
|
||||
* @param {Function} callback [err, { ... }].
|
||||
*/
|
||||
ownersOf: function(objectOrId, callback) {
|
||||
var result = {};
|
||||
|
||||
var accountStore = core.storeFactory.get('Account');
|
||||
|
||||
// case 1. given a calendar
|
||||
if (objectOrId instanceof CalendarModel) {
|
||||
result.calendar = objectOrId;
|
||||
accountStore.get(objectOrId.accountId, fetchAccount);
|
||||
return;
|
||||
}
|
||||
|
||||
// case 2 given a calendar id or object
|
||||
|
||||
if (typeof(objectOrId) === 'object') {
|
||||
objectOrId = objectOrId.calendarId;
|
||||
}
|
||||
|
||||
// why??? because we use this method in event store too..
|
||||
var calendarStore = core.storeFactory.get('Calendar');
|
||||
calendarStore.get(objectOrId, fetchCalendar);
|
||||
|
||||
function fetchCalendar(err, calendar) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
result.calendar = calendar;
|
||||
accountStore.get(calendar.accountId, fetchAccount);
|
||||
}
|
||||
|
||||
function fetchAccount(err, account) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
result.account = account;
|
||||
callback(null, result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
156
assets/webapps/calendar.gaiamobile.org/js/backend/store/event.js
Normal file
156
assets/webapps/calendar.gaiamobile.org/js/backend/store/event.js
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
define(['require','exports','module','./abstract','common/calc','./calendar','core','common/promise'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Abstract = require('./abstract');
|
||||
var Calc = require('common/calc');
|
||||
var Calendar = require('./calendar');
|
||||
var core = require('core');
|
||||
var denodeifyAll = require('common/promise').denodeifyAll;
|
||||
|
||||
function Events() {
|
||||
Abstract.apply(this, arguments);
|
||||
|
||||
denodeifyAll(this, [
|
||||
'providerFor',
|
||||
'findByIds',
|
||||
'ownersOf',
|
||||
'eventsForCalendar'
|
||||
]);
|
||||
}
|
||||
module.exports = Events;
|
||||
|
||||
Events.prototype = {
|
||||
__proto__: Abstract.prototype,
|
||||
_store: 'events',
|
||||
_dependentStores: ['events', 'busytimes', 'alarms', 'icalComponents'],
|
||||
|
||||
/** disable caching */
|
||||
_addToCache: function() {},
|
||||
_removeFromCache: function() {},
|
||||
|
||||
_createModel: function(input, id) {
|
||||
var _super = Abstract.prototype._createModel;
|
||||
var model = _super.apply(this, arguments);
|
||||
model.remote.startDate = Calc.dateFromTransport(model.remote.start);
|
||||
model.remote.endDate = Calc.dateFromTransport(model.remote.end);
|
||||
return model;
|
||||
},
|
||||
|
||||
/**
|
||||
* Link busytime dependants see _addDependents.
|
||||
*/
|
||||
_removeDependents: function(id, trans) {
|
||||
this.removeByIndex('parentId', id, trans);
|
||||
|
||||
var busy = core.storeFactory.get('Busytime');
|
||||
busy.removeEvent(id, trans);
|
||||
|
||||
var component = core.storeFactory.get('IcalComponent');
|
||||
component.remove(id, trans);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate an id for a newly created record.
|
||||
* Based off of remote id (uuid) and calendar id.
|
||||
*/
|
||||
_assignId: function(obj) {
|
||||
var id = obj.calendarId + '-' + obj.remote.id;
|
||||
obj._id = id;
|
||||
return id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shortcut finds provider for given event.
|
||||
*
|
||||
* @param {Object} event full event record from db.
|
||||
*/
|
||||
providerFor: function(event, callback) {
|
||||
this.ownersOf(event, function(err, owners) {
|
||||
callback(null, core.providerFactory.get(owners.account.providerType));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds a list of events by id.
|
||||
*
|
||||
* @param {Array} ids list of ids.
|
||||
* @param {Function} callback node style second argument
|
||||
* is an object of _id/event.
|
||||
*/
|
||||
findByIds: function(ids, callback) {
|
||||
var results = {};
|
||||
var pending = ids.length;
|
||||
var self = this;
|
||||
|
||||
if (!pending) {
|
||||
callback(null, results);
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (!(--pending)) {
|
||||
// fatal errors should break
|
||||
// and so we are not handling them
|
||||
// here...
|
||||
callback(null, results);
|
||||
}
|
||||
}
|
||||
|
||||
function success(e) {
|
||||
var item = e.target.result;
|
||||
|
||||
if (item) {
|
||||
results[item._id] = self._createModel(item);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
function error() {
|
||||
// can't find it or something
|
||||
// skip!
|
||||
next();
|
||||
}
|
||||
|
||||
ids.forEach(function(id) {
|
||||
var trans = core.db.transaction('events');
|
||||
var store = trans.objectStore('events');
|
||||
var req = store.get(id);
|
||||
|
||||
req.onsuccess = success;
|
||||
req.onerror = error;
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds calendar/account for a given event.
|
||||
*
|
||||
* @param {Object} event must contain .calendarId.
|
||||
* @param {Function} callback [err, { ... }].
|
||||
*/
|
||||
ownersOf: Calendar.prototype.ownersOf,
|
||||
/**
|
||||
* Loads all events for given calendarId
|
||||
* and returns results. Does not cache.
|
||||
*
|
||||
* @param {String} calendarId calendar to find.
|
||||
* @param {Function} callback node style [err, array of events].
|
||||
*/
|
||||
eventsForCalendar: function(calendarId, callback) {
|
||||
var trans = core.db.transaction('events');
|
||||
var store = trans.objectStore('events');
|
||||
var index = store.index('calendarId');
|
||||
var key = IDBKeyRange.only(calendarId);
|
||||
|
||||
var req = index.mozGetAll(key);
|
||||
|
||||
req.onsuccess = function(e) {
|
||||
callback(null, e.target.result);
|
||||
};
|
||||
|
||||
req.onerror = function(e) {
|
||||
callback(e);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
define(['require','exports','module','common/debug','./account','./alarm','./busytime','./calendar','./event','./ical_component','./setting'],function(require, exports) {
|
||||
'use strict';
|
||||
|
||||
var debug = require('common/debug')('store/factory');
|
||||
|
||||
var ctors = {
|
||||
Account: require('./account'),
|
||||
Alarm: require('./alarm'),
|
||||
Busytime: require('./busytime'),
|
||||
Calendar: require('./calendar'),
|
||||
Event: require('./event'),
|
||||
IcalComponent: require('./ical_component'),
|
||||
Setting: require('./setting')
|
||||
};
|
||||
|
||||
exports._instances = Object.create(null);
|
||||
|
||||
exports.get = function(name) {
|
||||
if (!exports._instances[name]) {
|
||||
try {
|
||||
debug(`Initializing store ${name}`);
|
||||
exports._instances[name] = new ctors[name]();
|
||||
} catch (e) {
|
||||
console.error('Error', e.name, e.message);
|
||||
console.error('Failed to initialize store', name, e.stack);
|
||||
}
|
||||
}
|
||||
return exports._instances[name];
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
define(['require','exports','module','./abstract','common/calc','core','common/promise'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Abstract = require('./abstract');
|
||||
var Calc = require('common/calc');
|
||||
var core = require('core');
|
||||
var denodeifyAll = require('common/promise').denodeifyAll;
|
||||
|
||||
function IcalComponent() {
|
||||
Abstract.apply(this, arguments);
|
||||
|
||||
denodeifyAll(this, [
|
||||
'findRecurrencesBefore'
|
||||
]);
|
||||
}
|
||||
module.exports = IcalComponent;
|
||||
|
||||
IcalComponent.prototype = {
|
||||
__proto__: Abstract.prototype,
|
||||
|
||||
_store: 'icalComponents',
|
||||
|
||||
/** disable caching */
|
||||
_addToCache: function() {},
|
||||
_removeFromCache: function() {},
|
||||
|
||||
_createModel: function(object) {
|
||||
return object;
|
||||
},
|
||||
|
||||
_detectPersistType: function(object) {
|
||||
// always fire update.
|
||||
return 'update';
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds all components which have recurrences
|
||||
* that are not expanded beyond the given date.
|
||||
*
|
||||
* @param {Date} maxDate max date to find.
|
||||
* @param {Function} callback results of search [err, [icalComp, ...]].
|
||||
*/
|
||||
findRecurrencesBefore: function(maxDate, callback) {
|
||||
var trans = core.db.transaction(this._store, 'readwrite');
|
||||
|
||||
trans.onerror = function(event) {
|
||||
callback(event.target.error.name);
|
||||
};
|
||||
|
||||
var time = Calc.dateToTransport(maxDate);
|
||||
var utc = time.utc;
|
||||
var range = IDBKeyRange.bound(0, utc);
|
||||
var store = trans.objectStore(this._store);
|
||||
var idx = store.index('lastRecurrenceId');
|
||||
|
||||
var req = idx.mozGetAll(range);
|
||||
|
||||
req.onsuccess = function(event) {
|
||||
callback(null, event.target.result);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
define(['require','exports','module','./abstract','common/promise','common/next_tick'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Abstract = require('./abstract');
|
||||
var denodeifyAll = require('common/promise').denodeifyAll;
|
||||
var nextTick = require('common/next_tick');
|
||||
|
||||
function Setting() {
|
||||
Abstract.apply(this, arguments);
|
||||
denodeifyAll(this, [
|
||||
'getValue',
|
||||
'set'
|
||||
]);
|
||||
}
|
||||
module.exports = Setting;
|
||||
|
||||
Setting.prototype = {
|
||||
__proto__: Abstract.prototype,
|
||||
|
||||
_store: 'settings',
|
||||
|
||||
/**
|
||||
* Default option values.
|
||||
*/
|
||||
defaults: {
|
||||
standardAlarmDefault: -300,
|
||||
alldayAlarmDefault: 32400,
|
||||
syncFrequency: 15,
|
||||
syncAlarm: {
|
||||
alarmId: null,
|
||||
start: null,
|
||||
end: null
|
||||
}
|
||||
},
|
||||
|
||||
/** disable caching */
|
||||
_addToCache: function() {},
|
||||
_removeFromCache: function() {},
|
||||
|
||||
/**
|
||||
* This method also will use the internal cache to ensure
|
||||
* callers are in a consistent state and don't require round
|
||||
* trips to the database. When the value does not exist defaults
|
||||
* are used where possible...
|
||||
*
|
||||
*
|
||||
* settings.getValue('syncFrequency', function(err, value) {
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
*
|
||||
* @param {String} key name of setting.
|
||||
* @param {Function} callback usual [err, value] does not include metadata.
|
||||
*/
|
||||
getValue: function(key, callback) {
|
||||
var self = this;
|
||||
|
||||
if (key in this._cached) {
|
||||
nextTick(function handleCached() {
|
||||
callback(null, self._cached[key].value);
|
||||
});
|
||||
|
||||
// we have cached value exit...
|
||||
return;
|
||||
}
|
||||
|
||||
this.get(key, function handleStored(err, value) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (value === undefined && self.defaults[key] !== undefined) {
|
||||
value = { value: self.defaults[key] };
|
||||
}
|
||||
|
||||
self._cached[key] = value;
|
||||
callback(null, value.value);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Persist a setting change.
|
||||
*
|
||||
* In addition to updating the value of the setting
|
||||
* it will also update the updatedAt & createdAt properties
|
||||
* of the record.
|
||||
*
|
||||
* Calling this function will also emit a 'change' event
|
||||
* prior to fully persisting the record to the database.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var settingStore;
|
||||
*
|
||||
* settingStore.set('syncFrequency', 15, function() {
|
||||
* // done
|
||||
* });
|
||||
*
|
||||
* // somewhere else in the app:
|
||||
*
|
||||
* settingStore.on('syncFrequencyChange', function(value) {
|
||||
* // value === 15
|
||||
* });
|
||||
*
|
||||
* @param {String} key name of setting.
|
||||
* @param {Object} value any object that IndexedDb can store.
|
||||
* @param {IDBTransaction} [trans] idb transaction optional.
|
||||
* @param {Function} [callback] optional callback.
|
||||
*/
|
||||
set: function(key, value, trans, callback) {
|
||||
if (typeof(trans) === 'function') {
|
||||
callback = trans;
|
||||
trans = null;
|
||||
}
|
||||
|
||||
var cached = this._cached[key];
|
||||
var record;
|
||||
|
||||
if (cached && cached._id) {
|
||||
cached.value = value;
|
||||
cached.updatedAt = new Date();
|
||||
record = cached;
|
||||
} else {
|
||||
var created = new Date();
|
||||
this._cached[key] = record = {
|
||||
_id: key,
|
||||
createdAt: created,
|
||||
updatedAt: created,
|
||||
value: value
|
||||
};
|
||||
}
|
||||
|
||||
this.emit(key + 'Change', value, record);
|
||||
this.persist(record, trans, callback);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
785
assets/webapps/calendar.gaiamobile.org/js/bundle.js
Normal file
785
assets/webapps/calendar.gaiamobile.org/js/bundle.js
Normal file
|
|
@ -0,0 +1,785 @@
|
|||
;(function(global){var
|
||||
version='0.8.11',curlName='curl',defineName='define',bootScriptAttr='data-curl-run',bootScript,userCfg,prevCurl,prevDefine,doc=global.document,head=doc&&(doc['head']||doc.getElementsByTagName('head')[0]),insertBeforeEl=head&&head.getElementsByTagName('base')[0]||null,msgUsingExports={},msgFactoryExecuted={},activeScripts={},readyStates='addEventListener'in global?{}:{'loaded':1,'complete':1},cleanPrototype={},toString=cleanPrototype.toString,undef,cache={},urlCache={},preload=false,argsNet,dontAddExtRx=/\?|\.js\b/,absUrlRx=/^\/|^[^:]+:\/\/|^[A-Za-z]:[\\/]/,findDotsRx=/(\.)(\.?)(?:$|\/([^\.\/]+.*)?)/g,removeCommentsRx=/\/\*[\s\S]*?\*\/|\/\/.*?[\n\r]/g,findRValueRequiresRx=/require\s*\(\s*(["'])(.*?[^\\])\1\s*\)|[^\\]?(["'])/g,splitCommaSepRx=/\s*,\s*/,cjsGetters,core;function noop(){}
|
||||
function isType(obj,type){return toString.call(obj).indexOf('[object '+type)==0;}
|
||||
function normalizePkgDescriptor(descriptor,isPkg){var main;descriptor.path=removeEndSlash(descriptor['path']||descriptor['location']||'');if(isPkg){main=descriptor['main']||'./main';if(!isRelUrl(main))main='./'+main;descriptor.main=reduceLeadingDots(main,descriptor.name+'/');}
|
||||
descriptor.config=descriptor['config'];return descriptor;}
|
||||
function isRelUrl(it){return it.charAt(0)=='.';}
|
||||
function isAbsUrl(it){return absUrlRx.test(it);}
|
||||
function joinPath(path,file){return removeEndSlash(path)+'/'+file;}
|
||||
function removeEndSlash(path){return path&&path.charAt(path.length-1)=='/'?path.substr(0,path.length-1):path;}
|
||||
function reduceLeadingDots(childId,baseId){var removeLevels,normId,levels,isRelative,diff;removeLevels=1;normId=childId;if(isRelUrl(normId)){isRelative=true;normId=normId.replace(findDotsRx,function(m,dot,doubleDot,remainder){if(doubleDot)removeLevels++;return remainder||'';});}
|
||||
if(isRelative){levels=baseId.split('/');diff=levels.length-removeLevels;if(diff<0){return childId;}
|
||||
levels.splice(diff,removeLevels);return levels.concat(normId||[]).join('/');}
|
||||
else{return normId;}}
|
||||
function pluginParts(id){var delPos=id.indexOf('!');return{resourceId:id.substr(delPos+1),pluginId:delPos>=0&&id.substr(0,delPos)};}
|
||||
function Begetter(){}
|
||||
function beget(parent,mixin){Begetter.prototype=parent||cleanPrototype;var child=new Begetter();Begetter.prototype=cleanPrototype;for(var p in mixin)child[p]=mixin[p];return child;}
|
||||
function Promise(){var self,thens,complete;self=this;thens=[];function then(resolved,rejected,progressed){thens.push([resolved,rejected,progressed]);}
|
||||
function notify(which,arg){var aThen,cb,i=0;while((aThen=thens[i++])){cb=aThen[which];if(cb)cb(arg);}}
|
||||
complete=function promiseComplete(success,arg){then=success?function(resolved,rejected){resolved&&resolved(arg);}:function(resolved,rejected){rejected&&rejected(arg);};complete=noop;notify(success?0:1,arg);notify=noop;thens=undef;};this.then=function(resolved,rejected,progressed){then(resolved,rejected,progressed);return self;};this.resolve=function(val){self.resolved=val;complete(true,val);};this.reject=function(ex){self.rejected=ex;complete(false,ex);};this.progress=function(msg){notify(2,msg);}}
|
||||
function isPromise(o){return o instanceof Promise||o instanceof CurlApi;}
|
||||
function when(promiseOrValue,callback,errback,progback){if(isPromise(promiseOrValue)){return promiseOrValue.then(callback,errback,progback);}
|
||||
else{return callback(promiseOrValue);}}
|
||||
function countdown(howMany,lambda,completed){var result;return function(){if(--howMany>=0&&lambda)result=lambda.apply(undef,arguments);if(howMany==0&&completed)completed(result);return result;}}
|
||||
core={toAbsId:function(id,parentId,cfg){var absId,pluginId,parts;absId=reduceLeadingDots(id,parentId);if(isRelUrl(absId))return absId;parts=pluginParts(absId);pluginId=parts.pluginId;absId=pluginId||parts.resourceId;if(absId in cfg.pathMap){absId=cfg.pathMap[absId].main||absId;}
|
||||
if(pluginId){if(pluginId.indexOf('/')<0&&!(pluginId in cfg.pathMap)){absId=joinPath(cfg.pluginPath,pluginId);}
|
||||
absId=absId+'!'+parts.resourceId;}
|
||||
return absId;},createContext:function(cfg,baseId,depNames,isPreload){var def;def=new Promise();def.id=baseId||'';def.isPreload=isPreload;def.depNames=depNames;def.config=cfg;function toAbsId(childId,checkPlugins){var absId,parts,plugin;absId=core.toAbsId(childId,def.id,cfg);if(!checkPlugins)return absId;parts=pluginParts(absId);if(!parts.pluginId)return absId;plugin=cache[parts.pluginId];if('normalize'in plugin){parts.resourceId=plugin['normalize'](parts.resourceId,toAbsId,def.config)||'';}
|
||||
else{parts.resourceId=toAbsId(parts.resourceId);}
|
||||
return parts.pluginId+'!'+parts.resourceId;}
|
||||
function toUrl(n){return core.resolvePathInfo(toAbsId(n,true),cfg).url;}
|
||||
function localRequire(ids,callback,errback){var cb,rvid,childDef,earlyExport;cb=callback&&function(){callback.apply(undef,arguments[0]);};if(isType(ids,'String')){if(cb){throw new Error('require(id, callback) not allowed');}
|
||||
rvid=toAbsId(ids,true);childDef=cache[rvid];if(!(rvid in cache)){throw new Error('Module not resolved: '+rvid);}
|
||||
earlyExport=isPromise(childDef)&&childDef.exports;return earlyExport||childDef;}
|
||||
else{when(core.getDeps(core.createContext(cfg,def.id,ids,isPreload)),cb,errback);}}
|
||||
def.require=localRequire;localRequire['toUrl']=toUrl;def.toAbsId=toAbsId;return def;},createResourceDef:function(cfg,id,isPreload){var def,origResolve,execute;def=core.createContext(cfg,id,undef,isPreload);origResolve=def.resolve;execute=countdown(1,function(deps){def.deps=deps;try{return core.executeDefFunc(def);}
|
||||
catch(ex){def.reject(ex);}});def.resolve=function resolve(deps){when(isPreload||preload,function(){origResolve((cache[def.id]=urlCache[def.url]=execute(deps)));});};def.exportsReady=function executeFactory(deps){when(isPreload||preload,function(){if(def.exports){execute(deps);def.progress(msgFactoryExecuted);}});};return def;},createPluginDef:function(cfg,id,resId,isPreload){var def;def=core.createContext(cfg,resId,undef,isPreload);return def;},getCjsRequire:function(def){return def.require;},getCjsExports:function(def){return def.exports||(def.exports={});},getCjsModule:function(def){var module=def.module;if(!module){module=def.module={'id':def.id,'uri':core.getDefUrl(def),'exports':core.getCjsExports(def),'config':function(){return def.config;}};module.exports=module['exports'];}
|
||||
return module;},getDefUrl:function(def){return def.url||(def.url=core.checkToAddJsExt(def.require['toUrl'](def.id),def.config));},setApi:function(cfg){var apiName,defName,apiObj,defObj,failMsg,okToOverwrite;apiName=curlName;defName=defineName;apiObj=defObj=global;failMsg=' already exists';if(cfg){okToOverwrite=cfg['overwriteApi']||cfg.overwriteApi;apiName=cfg['apiName']||cfg.apiName||apiName;apiObj=cfg['apiContext']||cfg.apiContext||apiObj;defName=cfg['defineName']||cfg.defineName||defName;defObj=cfg['defineContext']||cfg.defineContext||defObj;if(prevCurl&&isType(prevCurl,'Function')){global[curlName]=prevCurl;}
|
||||
prevCurl=null;if(prevDefine&&isType(prevDefine,'Function')){global[defineName]=prevDefine;}
|
||||
prevDefine=null;if(!okToOverwrite){if(apiObj[apiName]&&apiObj[apiName]!=_curl){throw new Error(apiName+failMsg);}
|
||||
if(defObj[defName]&&defObj[defName]!=define){throw new Error(defName+failMsg);}}}
|
||||
apiObj[apiName]=_curl;defObj[defName]=define;},config:function(cfg){var prevCfg,newCfg,pluginCfgs,p;if('baseUrl'in cfg)cfg.baseUrl=cfg['baseUrl'];if('main'in cfg)cfg.main=cfg['main'];if('preloads'in cfg)cfg.preloads=cfg['preloads'];if('pluginPath'in cfg)cfg.pluginPath=cfg['pluginPath'];if('dontAddFileExt'in cfg||cfg.dontAddFileExt){cfg.dontAddFileExt=new RegExp(cfg['dontAddFileExt']||cfg.dontAddFileExt);}
|
||||
prevCfg=userCfg;newCfg=beget(prevCfg,cfg);newCfg.pathMap=beget(prevCfg.pathMap);pluginCfgs=cfg['plugins']||{};newCfg.plugins=beget(prevCfg.plugins);newCfg.paths=beget(prevCfg.paths,cfg.paths);newCfg.packages=beget(prevCfg.packages,cfg.packages);newCfg.pathList=[];function fixAndPushPaths(coll,isPkg){var id,pluginId,data,parts,currCfg,info;for(var name in coll){data=coll[name];if(isType(data,'String'))data={path:coll[name]};data.name=data.name||name;currCfg=newCfg;parts=pluginParts(removeEndSlash(data.name));id=parts.resourceId;pluginId=parts.pluginId;if(pluginId){currCfg=pluginCfgs[pluginId];if(!currCfg){currCfg=pluginCfgs[pluginId]=beget(newCfg);currCfg.pathMap=beget(newCfg.pathMap);currCfg.pathList=[];}
|
||||
delete coll[name];}
|
||||
info=normalizePkgDescriptor(data,isPkg);if(info.config)info.config=beget(newCfg,info.config);info.specificity=id.split('/').length;if(id){currCfg.pathMap[id]=info;currCfg.pathList.push(id);}
|
||||
else{currCfg.baseUrl=core.resolveUrl(data.path,newCfg);}}}
|
||||
function convertPathMatcher(cfg){var pathMap=cfg.pathMap;cfg.pathRx=new RegExp('^('+
|
||||
cfg.pathList.sort(function(a,b){return pathMap[b].specificity-pathMap[a].specificity;}).join('|').replace(/\/|\./g,'\\$&')+')(?=\\/|$)');delete cfg.pathList;}
|
||||
fixAndPushPaths(cfg['packages'],true);fixAndPushPaths(cfg['paths'],false);for(p in pluginCfgs){var absId=core.toAbsId(p+'!','',newCfg);newCfg.plugins[absId.substr(0,absId.length-1)]=pluginCfgs[p];}
|
||||
pluginCfgs=newCfg.plugins;for(p in pluginCfgs){pluginCfgs[p]=beget(newCfg,pluginCfgs[p]);var pathList=pluginCfgs[p].pathList;if(pathList){pluginCfgs[p].pathList=pathList.concat(newCfg.pathList);convertPathMatcher(pluginCfgs[p]);}}
|
||||
for(p in prevCfg.pathMap){if(!newCfg.pathMap.hasOwnProperty(p))newCfg.pathList.push(p);}
|
||||
convertPathMatcher(newCfg);return newCfg;},resolvePathInfo:function(absId,cfg){var pathMap,pathInfo,path,pkgCfg;pathMap=cfg.pathMap;if(!isAbsUrl(absId)){path=absId.replace(cfg.pathRx,function(match){pathInfo=pathMap[match]||{};pkgCfg=pathInfo.config;return pathInfo.path||'';});}
|
||||
else{path=absId;}
|
||||
return{config:pkgCfg||userCfg,url:core.resolveUrl(path,cfg)};},resolveUrl:function(path,cfg){var baseUrl=cfg.baseUrl;return baseUrl&&!isAbsUrl(path)?joinPath(baseUrl,path):path;},checkToAddJsExt:function(url,cfg){return url+((cfg||userCfg).dontAddFileExt.test(url)?'':'.js');},loadScript:function(def,success,failure){var el=doc.createElement('script');function process(ev){ev=ev||global.event;if(ev.type=='load'||readyStates[el.readyState]){delete activeScripts[def.id];el.onload=el.onreadystatechange=el.onerror='';success();}}
|
||||
function fail(e){failure(new Error('Syntax or http error: '+def.url));}
|
||||
el.onload=el.onreadystatechange=process;el.onerror=fail;el.type=def.mimetype||'text/javascript';el.charset='utf-8';el.async=!def.order;el.src=def.url;activeScripts[def.id]=el;head.insertBefore(el,insertBeforeEl);return el;},extractCjsDeps:function(defFunc){var source,ids=[],currQuote;source=typeof defFunc=='string'?defFunc:defFunc.toSource?defFunc.toSource():defFunc.toString();source.replace(removeCommentsRx,'').replace(findRValueRequiresRx,function(m,rq,id,qq){if(qq){currQuote=currQuote==qq?undef:currQuote;}
|
||||
else if(!currQuote){ids.push(id);}
|
||||
return'';});return ids;},fixArgs:function(args){var id,deps,defFunc,defFuncArity,len,cjs;len=args.length;defFunc=args[len-1];defFuncArity=isType(defFunc,'Function')?defFunc.length:-1;if(len==2){if(isType(args[0],'Array')){deps=args[0];}
|
||||
else{id=args[0];}}
|
||||
else if(len==3){id=args[0];deps=args[1];}
|
||||
if(!deps&&defFuncArity>0){cjs=true;deps=['require','exports','module'].slice(0,defFuncArity).concat(core.extractCjsDeps(defFunc));}
|
||||
return{id:id,deps:deps||[],res:defFuncArity>=0?defFunc:function(){return defFunc;},cjs:cjs};},executeDefFunc:function(def){var resource,moduleThis;moduleThis=def.cjs?def.exports:undef;resource=def.res.apply(moduleThis,def.deps);if(resource===undef&&def.exports){resource=def.module?(def.exports=def.module['exports']):def.exports;}
|
||||
return resource;},defineResource:function(def,args){def.res=args.res;def.cjs=args.cjs;def.depNames=args.deps;core.getDeps(def);},getDeps:function(parentDef){var i,names,deps,len,dep,completed,name,exportCollector,resolveCollector;deps=[];names=parentDef.depNames;len=names.length;if(names.length==0)allResolved();function collect(dep,index,alsoExport){deps[index]=dep;if(alsoExport)exportCollector(dep,index);}
|
||||
exportCollector=countdown(len,collect,allExportsReady);resolveCollector=countdown(len,collect,allResolved);for(i=0;i<len;i++){name=names[i];if(name in cjsGetters){resolveCollector(cjsGetters[name](parentDef),i,true);if(parentDef.exports){parentDef.progress(msgUsingExports);}}
|
||||
else if(!name){resolveCollector(undef,i,true);}
|
||||
else{getDep(name,i);}}
|
||||
return parentDef;function getDep(name,index){var resolveOnce,exportOnce,childDef,earlyExport;resolveOnce=countdown(1,function(dep){exportOnce(dep);resolveCollector(dep,index);});exportOnce=countdown(1,function(dep){exportCollector(dep,index);});childDef=core.fetchDep(name,parentDef);earlyExport=isPromise(childDef)&&childDef.exports;if(earlyExport){exportOnce(earlyExport);}
|
||||
when(childDef,resolveOnce,parentDef.reject,parentDef.exports&&function(msg){if(childDef.exports){if(msg==msgUsingExports){exportOnce(childDef.exports);}
|
||||
else if(msg==msgFactoryExecuted){resolveOnce(childDef.exports);}}});}
|
||||
function allResolved(){parentDef.resolve(deps);}
|
||||
function allExportsReady(){parentDef.exportsReady&&parentDef.exportsReady(deps);}},fetchResDef:function(def){core.getDefUrl(def);core.loadScript(def,function(){var args=argsNet;argsNet=undef;if(def.useNet!==false){if(!args||args.ex){def.reject(new Error(((args&&args.ex)||'define() missing or duplicated: '+def.url)));}
|
||||
else{core.defineResource(def,args);}}},def.reject);return def;},fetchDep:function(depName,parentDef){var toAbsId,isPreload,parentCfg,parts,absId,mainId,loaderId,pluginId,resId,pathInfo,def,tempDef,resCfg;toAbsId=parentDef.toAbsId;isPreload=parentDef.isPreload;parentCfg=parentDef.config||userCfg;absId=toAbsId(depName);if(absId in cache){mainId=absId;}
|
||||
else{parts=pluginParts(absId);resId=parts.resourceId;mainId=parts.pluginId||resId;pathInfo=core.resolvePathInfo(mainId,parentCfg);}
|
||||
if(!(absId in cache)){resCfg=core.resolvePathInfo(resId,parentCfg).config;if(parts.pluginId){loaderId=mainId;}
|
||||
else{loaderId=resCfg['moduleLoader']||resCfg.moduleLoader||resCfg['loader']||resCfg.loader;if(loaderId){resId=mainId;mainId=loaderId;pathInfo=core.resolvePathInfo(loaderId,parentCfg);}}}
|
||||
if(mainId in cache){def=cache[mainId];}
|
||||
else if(pathInfo.url in urlCache){def=cache[mainId]=urlCache[pathInfo.url];}
|
||||
else{def=core.createResourceDef(resCfg,mainId,isPreload);def.url=core.checkToAddJsExt(pathInfo.url,pathInfo.config);cache[mainId]=urlCache[pathInfo.url]=def;core.fetchResDef(def);}
|
||||
if(mainId==loaderId){if(parts.pluginId&&parentCfg.plugins[parts.pluginId]){resCfg=parentCfg.plugins[parts.pluginId];}
|
||||
tempDef=new Promise();when(def,function(plugin){var normalizedDef,fullId,dynamic;dynamic=plugin['dynamic'];if('normalize'in plugin){resId=plugin['normalize'](resId,toAbsId,def.config)||'';}
|
||||
else{resId=toAbsId(resId);}
|
||||
fullId=loaderId+'!'+resId;normalizedDef=cache[fullId];if(!(fullId in cache)){normalizedDef=core.createPluginDef(resCfg,fullId,resId,isPreload);if(!dynamic){cache[fullId]=normalizedDef;}
|
||||
var loaded=function(res){if(!dynamic)cache[fullId]=res;normalizedDef.resolve(res);};loaded['resolve']=loaded;loaded['reject']=loaded['error']=normalizedDef.reject;plugin.load(resId,normalizedDef.require,loaded,resCfg);}
|
||||
if(tempDef!=normalizedDef){when(normalizedDef,tempDef.resolve,tempDef.reject,tempDef.progress);}},tempDef.reject);}
|
||||
return tempDef||def;},getCurrentDefName:function(){var def;if(!isType(global.opera,'Opera')){for(var d in activeScripts){if(activeScripts[d].readyState=='interactive'){def=d;break;}}}
|
||||
return def;},findScript:function(predicate){var i=0,scripts,script;scripts=doc&&(doc.scripts||doc.getElementsByTagName('script'));while(scripts&&(script=scripts[i++])){if(predicate(script))return script;}},extractDataAttrConfig:function(){var script,attr='';script=core.findScript(function(script){var run;run=script.getAttribute(bootScriptAttr);if(run)attr=run;return run;});if(script){script.setAttribute(bootScriptAttr,'');}
|
||||
return attr;},bootScript:function(){var urls=bootScript.split(splitCommaSepRx);if(urls.length){load();}
|
||||
function load(){core.loadScript({url:urls.shift()},check,check);}
|
||||
function check(){if(bootScript){if(urls.length){core.nextTurn(fail);load();}
|
||||
else fail('run.js script did not run.');}}
|
||||
function fail(msg){throw new Error(msg||'Primary run.js failed. Trying fallback.');}},nextTurn:function(task){setTimeout(task,0);}};cjsGetters={'require':core.getCjsRequire,'exports':core.getCjsExports,'module':core.getCjsModule};function _curl(){var args,promise,cfg;bootScript='';args=[].slice.call(arguments);if(isType(args[0],'Object')){cfg=args.shift();promise=_config(cfg);}
|
||||
return new CurlApi(args[0],args[1],args[2],promise);}
|
||||
function _config(cfg,callback,errback){var pPromise,main,fallback;bootScript='';if(cfg){core.setApi(cfg);userCfg=core.config(cfg);if('preloads'in cfg){pPromise=new CurlApi(cfg['preloads'],undef,errback,preload,true);core.nextTurn(function(){preload=pPromise;});}
|
||||
main=cfg['main'];if(main){return new CurlApi(main,callback,errback);}}}
|
||||
function CurlApi(ids,callback,errback,waitFor,isPreload){var then,ctx;ctx=core.createContext(userCfg,undef,[].concat(ids),isPreload);this['then']=this.then=then=function(resolved,rejected){when(ctx,function(deps){if(resolved)resolved.apply(undef,deps);},function(ex){if(rejected)rejected(ex);else throw ex;});return this;};this['next']=function(ids,cb,eb){return new CurlApi(ids,cb,eb,ctx);};this['config']=_config;if(callback||errback)then(callback,errback);core.nextTurn(function(){when(isPreload||preload,function(){when(waitFor,function(){core.getDeps(ctx);},errback);});});}
|
||||
_curl['version']=version;_curl['config']=_config;function _define(args){var id,def,pathInfo;id=args.id;if(id==undef){if(argsNet!==undef){argsNet={ex:'Multiple anonymous defines encountered'};}
|
||||
else if(!(id=core.getCurrentDefName())){argsNet=args;}}
|
||||
if(id!=undef){def=cache[id];if(!(id in cache)){pathInfo=core.resolvePathInfo(id,userCfg);def=core.createResourceDef(pathInfo.config,id);cache[id]=def;}
|
||||
if(!isPromise(def))throw new Error('duplicate define: '+id);def.useNet=false;core.defineResource(def,args);}}
|
||||
function define(){var args=core.fixArgs(arguments);_define(args);}
|
||||
define['amd']={'plugins':true,'jQuery':true,'curl':version};userCfg={baseUrl:'',pluginPath:'curl/plugin',dontAddFileExt:dontAddExtRx,paths:{},packages:{},plugins:{},pathMap:{},pathRx:/$^/};prevCurl=global[curlName];prevDefine=global[defineName];if(prevCurl&&isType(prevCurl,'Object')){global[curlName]=undef;_config(prevCurl);}
|
||||
else{core.setApi();}
|
||||
bootScript=core.extractDataAttrConfig();if(bootScript)core.nextTurn(core.bootScript);cache[curlName]=_curl;cache['curl/_privileged']={'core':core,'cache':cache,'config':function(){return userCfg;},'_define':_define,'_curl':_curl,'Promise':Promise};}(this.window||(typeof global!='undefined'&&global)||this));define("ext/curl",function(){});define('models/account',['require','exports','module'],function(require,exports,module){'use strict';function Account(options){var key;if(typeof(options)==='undefined'){options={};}
|
||||
for(key in options){if(options.hasOwnProperty(key)){this[key]=options[key];}}}
|
||||
module.exports=Account;Account.prototype={providerType:null,id:null,preset:null,domain:'',entrypoint:'',calendarHome:'',user:'',password:'',get fullUrl(){return this.domain+this.entrypoint;},set fullUrl(value){var protocolIdx=value.indexOf('://');this.domain=value;this.entrypoint='/';if(protocolIdx!==-1){protocolIdx+=3;var domainChunk=value.substr(protocolIdx);var pathIdx=domainChunk.indexOf('/');if(pathIdx!==-1){pathIdx=pathIdx+protocolIdx;this.entrypoint=value.substr(pathIdx);this.domain=value.substr(0,pathIdx);}}},toJSON:function(){var output={};var fields=['entrypoint','calendarHome','domain','password','user','providerType','preset','oauth','error'];fields.forEach(function(key){output[key]=this[key];},this);if(this._id||this._id===0){output._id=this._id;}
|
||||
return output;}};});define('common/presets',{"google":{"providerType":"Caldav","group":"remote","authenticationType":"oauth2","apiCredentials":{"tokenUrl":"https://accounts.google.com/o/oauth2/token","authorizationUrl":"https://accounts.google.com/o/oauth2/auth","user_info":{"url":"https://www.googleapis.com/oauth2/v3/userinfo","field":"email"},"client_secret":"jQTKlOhF-RclGaGJot3HIcVf","client_id":"605300196874-1ki833poa7uqabmh3hq6u1onlqlsi54h.apps.googleusercontent.com","scope":"https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/userinfo.email","redirect_uri":"https://oauth.gaiamobile.org/authenticated"},"options":{"domain":"https://apidata.googleusercontent.com","entrypoint":"/caldav/v2/","providerType":"Caldav"}},"yahoo":{"providerType":"Caldav","group":"remote","options":{"domain":"https://caldav.calendar.yahoo.com","entrypoint":"/","providerType":"Caldav","user":"@yahoo.com","usernameType":"email"}},"caldav":{"providerType":"Caldav","group":"remote","options":{"domain":"","entrypoint":"","providerType":"Caldav"}},"local":{"singleUse":true,"providerType":"Local","group":"local","options":{"providerType":"Local"}}});define('common/promise',['require','exports','module'],function(require,exports){'use strict';function denodeify(fn){return function(){var args=Array.slice(arguments);if(args.length===fn.length){return fn.apply(this,args);}
|
||||
var deferred=defer();args.push(function(err,result){if(err){return deferred.reject(err);}
|
||||
if(arguments.length>2){result=Array.prototype.slice.call(arguments,1);}
|
||||
deferred.resolve(result);});var returnValue=fn.apply(this,args);return typeof returnValue==='object'?returnValue:deferred.promise;};}
|
||||
exports.denodeify=denodeify;function denodeifyAll(object,methods){methods.forEach((method)=>{object[method]=exports.denodeify(object[method]);});}
|
||||
exports.denodeifyAll=denodeifyAll;function defer(){var deferred={};var promise=new Promise((resolve,reject)=>{deferred.resolve=resolve;deferred.reject=reject;});deferred.promise=promise;return deferred;}});define('common/next_tick',['require','exports','module'],function(require,exports,module){'use strict';var resolved=Promise.resolve();module.exports=function(callback){resolved.then(callback);};});define('provider/abstract',['require','exports','module','common/promise','common/next_tick'],function(require,exports,module){'use strict';var denodeifyAll=require('common/promise').denodeifyAll;var nextTick=require('common/next_tick');function Abstract(options){var key;for(key in options){if(options.hasOwnProperty(key)){this[key]=options[key];}}
|
||||
denodeifyAll(this,['eventCapabilities','getAccount','findCalendars','syncEvents','ensureRecurrencesExpanded','createEvent','updateEvent','deleteEvent']);}
|
||||
module.exports=Abstract;Abstract.prototype={defaultColor:'#F97C17',useCredentials:false,useUrl:false,canSync:false,canExpandRecurringEvents:false,getAccount:function(account,callback){},findCalendars:function(){},syncEvents:function(account,calendar,callback){},ensureRecurrencesExpanded:function(date,callback){},updateEvent:function(event,busytime,callback){},deleteEvent:function(event,busytime,callback){},createEvent:function(event,callback){},calendarCapabilities:function(calendar){return{canCreateEvent:true,canUpdateEvent:true,canDeleteEvent:true};},eventCapabilities:function(event,callback){var caps=this.calendarCapabilities();nextTick(function(){callback(null,{canCreate:caps.canCreateEvent,canUpdate:caps.canUpdateEvent,canDelete:caps.canDeleteEvent});});}};});define('core',['require','exports','module'],function(require,exports,module){'use strict';exports.db=null;exports.errorController=null;exports.notificationsController=null;exports.periodicSyncController=null;exports.providerFactory=null;exports.serviceController=null;exports.storeFactory=null;exports.syncController=null;exports.timeController=null;exports.viewFactory=null;});define('common/constants',['require','exports','module'],function(require,exports){'use strict';exports.localCalendarId='local-first';});define('common/timespan',['require','exports','module'],function(require,exports,module){'use strict';function Timespan(startDate,endDate){this.start=startDate.valueOf();this.end=endDate.valueOf();}
|
||||
module.exports=Timespan;Timespan.prototype={isEqual:function(inputSpan){return(this.start===inputSpan.start&&this.end===inputSpan.end);},trimOverlap:function(span){if(this.contains(span)||span.contains(this)){return null;}
|
||||
var start=span.start;var end=span.end;var ourEnd=this.end;var ourStart=this.start;var overlapsBefore=start>=ourStart&&start<ourEnd;var overlapsAfter=ourStart>=start&&ourStart<end;var newStart=span.start;var newEnd=span.end;if(overlapsBefore){newStart=ourEnd+1;}
|
||||
if(overlapsAfter){newEnd=ourStart-1;}
|
||||
return new Timespan(newStart,newEnd);},overlaps:function(start,end){var ourStart=this.start;var ourEnd=this.end;if(start instanceof Timespan){end=start.end;start=start.start;}else{start=(start instanceof Date)?start.valueOf():start;end=(end instanceof Date)?end.valueOf():end;}
|
||||
return(start>=ourStart&&start<ourEnd||ourStart>=start&&ourStart<end);},contains:function(date){var start=this.start;var end=this.end;if(date instanceof Date){return start<=date&&end>=date;}else if(date instanceof Timespan){return start<=date.start&&end>=date.end;}else{return this.containsNumeric(date);}},containsNumeric:function(timestamp){var start=this.start;var end=this.end;return start<=timestamp&&end>=timestamp;}};});!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define('ext/co',[],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.co=e()}}(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){var slice=Array.prototype.slice;module.exports=co['default']=co.co=co;co.wrap=function(fn){createPromise.__generatorFunction__=fn;return createPromise;function createPromise(){return co.call(this,fn.apply(this,arguments));}};function co(gen){var ctx=this;return new Promise(function(resolve,reject){if(typeof gen==='function')gen=gen.call(ctx);if(!gen||typeof gen.next!=='function')return resolve(gen);onFulfilled();function onFulfilled(res){var ret;try{ret=gen.next(res);}catch(e){return reject(e);}
|
||||
next(ret);}
|
||||
function onRejected(err){var ret;try{ret=gen.throw(err);}catch(e){return reject(e);}
|
||||
next(ret);}
|
||||
function next(ret){if(ret.done)return resolve(ret.value);var value=toPromise.call(ctx,ret.value);if(value&&isPromise(value))return value.then(onFulfilled,onRejected);return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
|
||||
+'but the following object was passed: "'+String(ret.value)+'"'));}});}
|
||||
function toPromise(obj){if(!obj)return obj;if(isPromise(obj))return obj;if(isGeneratorFunction(obj)||isGenerator(obj))return co.call(this,obj);if('function'==typeof obj)return thunkToPromise.call(this,obj);if(Array.isArray(obj))return arrayToPromise.call(this,obj);if(isObject(obj))return objectToPromise.call(this,obj);return obj;}
|
||||
function thunkToPromise(fn){var ctx=this;return new Promise(function(resolve,reject){fn.call(ctx,function(err,res){if(err)return reject(err);if(arguments.length>2)res=slice.call(arguments,1);resolve(res);});});}
|
||||
function arrayToPromise(obj){return Promise.all(obj.map(toPromise,this));}
|
||||
function objectToPromise(obj){var results=new obj.constructor();var keys=Object.keys(obj);var promises=[];for(var i=0;i<keys.length;i++){var key=keys[i];var promise=toPromise.call(this,obj[key]);if(promise&&isPromise(promise))defer(promise,key);else results[key]=obj[key];}
|
||||
return Promise.all(promises).then(function(){return results;});function defer(promise,key){results[key]=undefined;promises.push(promise.then(function(res){results[key]=res;}));}}
|
||||
function isPromise(obj){return'function'==typeof obj.then;}
|
||||
function isGenerator(obj){return'function'==typeof obj.next&&'function'==typeof obj.throw;}
|
||||
function isGeneratorFunction(obj){var constructor=obj.constructor;if(!constructor)return false;if('GeneratorFunction'===constructor.name||'GeneratorFunction'===constructor.displayName)return true;return isGenerator(constructor.prototype);}
|
||||
function isObject(val){return Object==val.constructor;}},{}]},{},[1])(1)});define('common/calc',['require','exports','module','./timespan','ext/co'],function(require,exports){'use strict';var Timespan=require('./timespan');var co=require('ext/co');const SECOND=1000;const MINUTE=(SECOND*60);const HOUR=MINUTE*60;exports._hourDate=new Date();exports.startDay=0;exports.FLOATING='floating';exports.ALLDAY='allday';exports.SECOND=SECOND;exports.MINUTE=MINUTE;exports.HOUR=HOUR;exports.PAST='past';exports.NEXT_MONTH='next-month';exports.OTHER_MONTH='other-month';exports.PRESENT='present';exports.FUTURE='future';Object.defineProperty(exports,'today',{get:function(){return new Date();}});exports.daysInWeek=function(){return 7;};exports.dayOfWeekFromMonday=function(numeric){var day=numeric-1;if(day<0){return 6;}
|
||||
return day;};exports.dayOfWeekFromStartDay=function(numeric){var day=numeric-exports.startDay;if(day<0){return 7+day;}
|
||||
return day;};exports.isToday=function(date){return exports.isSameDate(date,exports.today);};exports.isOnlyDate=function(date){if(date.getHours()===0&&date.getMinutes()===0&&date.getSeconds()===0){return true;}
|
||||
return false;};exports.hourDiff=function(start,end){start=(start instanceof Date)?start.valueOf():start;end=(end instanceof Date)?end.valueOf():end;start=start/HOUR;end=end/HOUR;return end-start;};exports.spanOfDay=function(date,includeTime){if(typeof(includeTime)==='undefined'){date=exports.createDay(date);}
|
||||
var end=exports.createDay(date);end.setDate(end.getDate()+1);return new Timespan(date,end);};exports.spanOfMonth=function(month){month=exports.monthStart(month);var startDay=exports.getWeekStartDate(month);var endDay=exports.monthEnd(month);endDay=exports.getWeekEndDate(endDay);return new Timespan(startDay,endDay);};exports.monthEnd=function(date,diff=0){var endDay=new Date(date.getFullYear(),date.getMonth()+diff+1,1);endDay.setMilliseconds(-1);return endDay;};exports.getUTC=function(date){return new Date(date.getUTCFullYear(),date.getUTCMonth(),date.getUTCDate(),date.getUTCHours(),date.getUTCMinutes(),date.getUTCSeconds(),date.getUTCMilliseconds());};exports.dateFromTransport=function(transport){var utc=transport.utc;var offset=transport.offset;var zone=transport.tzid;var date=new Date(parseInt(utc)-parseInt(offset));if(zone&&zone===exports.FLOATING){return exports.getUTC(date);}
|
||||
return date;};exports.dateToTransport=function(date,tzid,isDate){var result=Object.create(null);if(isDate){result.isDate=isDate;}
|
||||
if(tzid){result.tzid=tzid;}
|
||||
var utc=Date.UTC(date.getFullYear(),date.getMonth(),date.getDate(),date.getHours(),date.getMinutes(),date.getSeconds(),date.getMilliseconds());if(isDate||tzid&&tzid===exports.FLOATING){result.utc=utc;result.offset=0;result.tzid=exports.FLOATING;}else{var localUtc=date.valueOf();var offset=utc-localUtc;result.utc=utc;result.offset=offset;}
|
||||
return result;};exports.isSameDate=function(first,second){return first.getMonth()==second.getMonth()&&first.getDate()==second.getDate()&&first.getFullYear()==second.getFullYear();};exports.getDayId=function(date){return['d',date.getFullYear(),date.getMonth(),date.getDate()].join('-');};exports.dateFromId=function(id){var parts=id.split('-'),date,type;if(parts.length>1){type=parts.shift();switch(type){case'd':date=new Date(parts[0],parts[1],parts[2]);break;case'm':date=new Date(parts[0],parts[1]);break;}}
|
||||
return date;};exports.getMonthId=function(date){return['m',date.getFullYear(),date.getMonth()].join('-');};exports.createDay=function(date,day,month,year){return new Date(year!=null?year:date.getFullYear(),month!=null?month:date.getMonth(),day!=null?day:date.getDate());};exports.endOfDay=function(date){var day=exports.createDay(date,date.getDate()+1);day.setMilliseconds(-1);return day;};exports.monthStart=function(date,diff=0){return new Date(date.getFullYear(),date.getMonth()+diff,1);};exports.dayOfWeek=function(date){var number=date;if(typeof(date)!=='number'){number=date.getDay();}
|
||||
return exports.dayOfWeekFromStartDay(number);};exports.getWeekStartDate=function(date){var currentDay=exports.dayOfWeek(date);var startDay=(date.getDate()-currentDay);return exports.createDay(date,startDay);};exports.getWeekEndDate=function(date){var start=exports.getWeekStartDate(date);start.setDate(start.getDate()+7);start.setMilliseconds(-1);return start;};exports.daysBetween=function(start,end,includeTime){if(start instanceof Timespan){if(end){includeTime=end;}
|
||||
end=new Date(start.end);start=new Date(start.start);}
|
||||
if(start>end){var tmp=end;end=start;start=tmp;tmp=null;}
|
||||
var list=[];var last=start.getDate();if(exports.isSameDate(start,end)){if(includeTime){list.push(end);}else{list.push(exports.createDay(start));}
|
||||
return list;}
|
||||
while(true){var next=new Date(start.getFullYear(),start.getMonth(),++last);if(next>end){throw new Error('sanity fails next is greater then end');}
|
||||
if(!exports.isSameDate(next,end)){list.push(next);continue;}
|
||||
break;}
|
||||
if(includeTime){list.unshift(start);list.push(end);}else{list.unshift(exports.createDay(start));list.push(exports.createDay(end));}
|
||||
return list;},exports.getWeeksDays=function(startDate){var weeksDayStart=exports.getWeekStartDate(startDate);var result=[weeksDayStart];for(var i=1;i<7;i++){result.push(exports.createDay(weeksDayStart,weeksDayStart.getDate()+i));}
|
||||
return result;};exports.isPast=function(date){return(date.valueOf()<exports.today.valueOf());};exports.isFuture=function(date){return!exports.isPast(date);};exports.relativeState=function(day,month){var states;if(exports.isToday(day)){return exports.PRESENT;}
|
||||
if(exports.isPast(day)){states=exports.PAST;}else{states=exports.FUTURE;}
|
||||
if(day.getMonth()!==month.getMonth()){states+=' '+exports.OTHER_MONTH;}
|
||||
return states;};exports.relativeOffset=function(baseDate,date){if(exports.isSameDate(baseDate,date)){return date.getHours()+(date.getMinutes()/60);}
|
||||
return 0;};exports.relativeDuration=function(baseDate,startDate,endDate){if(!exports.isSameDate(startDate,endDate)){if(exports.isSameDate(baseDate,startDate)){endDate=exports.endOfDay(baseDate);}else if(exports.isSameDate(baseDate,endDate)){startDate=exports.createDay(endDate);}else{return 24;}}
|
||||
return exports.hourDiff(startDate,endDate);};exports.isAllDay=function(baseDate,startDate,endDate){var refStart=exports.createDay(baseDate);var refEnd=exports.endOfDay(baseDate);var startBefore=startDate<=refStart;var endsAfter=endDate>=refEnd;return(startBefore&&endsAfter)||Number(startDate)===Number(endDate);};if(typeof(window)!=='undefined'){window.addEventListener('localized',co.wrap(function*(){exports.startDay=yield mozIntl.calendarInfo('firstDayOfTheWeek');}));}});(function(){var _global=this;var _rng;if(typeof(_global.require)=='function'&&typeof(module)!='undefined'&&module.exports){try{var _rb=_global.require('crypto').randomBytes;_rng=_rb&&function(){return _rb(16);};}catch(e){}}
|
||||
if(!_rng&&_global.crypto&&crypto.getRandomValues){var _rnds8=new Uint8Array(16);_rng=function whatwgRNG(){crypto.getRandomValues(_rnds8);return _rnds8;};}
|
||||
if(!_rng){var _rnds=new Array(16);_rng=function(){for(var i=0,r;i<16;i++){if((i&0x03)===0)r=Math.random()*0x100000000;_rnds[i]=r>>>((i&0x03)<<3)&0xff;}
|
||||
return _rnds;};}
|
||||
var BufferClass=typeof(_global.Buffer)=='function'?_global.Buffer:Array;var _byteToHex=[];var _hexToByte={};for(var i=0;i<256;i++){_byteToHex[i]=(i+0x100).toString(16).substr(1);_hexToByte[_byteToHex[i]]=i;}
|
||||
function parse(s,buf,offset){var i=(buf&&offset)||0,ii=0;buf=buf||[];s.toLowerCase().replace(/[0-9a-f]{2}/g,function(oct){if(ii<16){buf[i+ii++]=_hexToByte[oct];}});while(ii<16){buf[i+ii++]=0;}
|
||||
return buf;}
|
||||
function unparse(buf,offset){var i=offset||0,bth=_byteToHex;return bth[buf[i++]]+bth[buf[i++]]+
|
||||
bth[buf[i++]]+bth[buf[i++]]+'-'+
|
||||
bth[buf[i++]]+bth[buf[i++]]+'-'+
|
||||
bth[buf[i++]]+bth[buf[i++]]+'-'+
|
||||
bth[buf[i++]]+bth[buf[i++]]+'-'+
|
||||
bth[buf[i++]]+bth[buf[i++]]+
|
||||
bth[buf[i++]]+bth[buf[i++]]+
|
||||
bth[buf[i++]]+bth[buf[i++]];}
|
||||
var _seedBytes=_rng();var _nodeId=[_seedBytes[0]|0x01,_seedBytes[1],_seedBytes[2],_seedBytes[3],_seedBytes[4],_seedBytes[5]];var _clockseq=(_seedBytes[6]<<8|_seedBytes[7])&0x3fff;var _lastMSecs=0,_lastNSecs=0;function v1(options,buf,offset){var i=buf&&offset||0;var b=buf||[];options=options||{};var clockseq=options.clockseq!=null?options.clockseq:_clockseq;var msecs=options.msecs!=null?options.msecs:new Date().getTime();var nsecs=options.nsecs!=null?options.nsecs:_lastNSecs+1;var dt=(msecs-_lastMSecs)+(nsecs-_lastNSecs)/10000;if(dt<0&&options.clockseq==null){clockseq=clockseq+1&0x3fff;}
|
||||
if((dt<0||msecs>_lastMSecs)&&options.nsecs==null){nsecs=0;}
|
||||
if(nsecs>=10000){throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec');}
|
||||
_lastMSecs=msecs;_lastNSecs=nsecs;_clockseq=clockseq;msecs+=12219292800000;var tl=((msecs&0xfffffff)*10000+nsecs)%0x100000000;b[i++]=tl>>>24&0xff;b[i++]=tl>>>16&0xff;b[i++]=tl>>>8&0xff;b[i++]=tl&0xff;var tmh=(msecs/0x100000000*10000)&0xfffffff;b[i++]=tmh>>>8&0xff;b[i++]=tmh&0xff;b[i++]=tmh>>>24&0xf|0x10;b[i++]=tmh>>>16&0xff;b[i++]=clockseq>>>8|0x80;b[i++]=clockseq&0xff;var node=options.node||_nodeId;for(var n=0;n<6;n++){b[i+n]=node[n];}
|
||||
return buf?buf:unparse(b);}
|
||||
function v4(options,buf,offset){var i=buf&&offset||0;if(typeof(options)=='string'){buf=options=='binary'?new BufferClass(16):null;options=null;}
|
||||
options=options||{};var rnds=options.random||(options.rng||_rng)();rnds[6]=(rnds[6]&0x0f)|0x40;rnds[8]=(rnds[8]&0x3f)|0x80;if(buf){for(var ii=0;ii<16;ii++){buf[i+ii]=rnds[ii];}}
|
||||
return buf||unparse(rnds);}
|
||||
var uuid=v4;uuid.v1=v1;uuid.v4=v4;uuid.parse=parse;uuid.unparse=unparse;uuid.BufferClass=BufferClass;if(typeof define==='function'&&define.amd){define('ext/uuid',[],function(){return uuid;});}else if(typeof(module)!='undefined'&&module.exports){module.exports=uuid;}else{var _previousRoot=_global.uuid;uuid.noConflict=function(){_global.uuid=_previousRoot;return uuid;};_global.uuid=uuid;}}).call(this);define('event_mutations',['require','exports','module','common/calc','core','ext/uuid'],function(require,exports){'use strict';var Calc=require('common/calc');var core=require('core');var uuid=require('ext/uuid');function createBusytime(event){return{_id:event._id+'-'+uuid.v4(),eventId:event._id,calendarId:event.calendarId,start:event.remote.start,end:event.remote.end};}
|
||||
function Create(options){if(options){for(var key in options){if(options.hasOwnProperty(key)){this[key]=options[key];}}}}
|
||||
Create.prototype={commit:function(callback){var storeFactory=core.storeFactory;var alarmStore=storeFactory.get('Alarm');var eventStore=storeFactory.get('Event');var busytimeStore=storeFactory.get('Busytime');var componentStore=storeFactory.get('IcalComponent');var trans=core.db.transaction(eventStore._dependentStores,'readwrite');trans.oncomplete=function commitComplete(){callback(null);};trans.onerror=function commitError(e){callback(e.target.error);};eventStore.persist(this.event,trans);if(!this.busytime){this.busytime=createBusytime(this.event);}
|
||||
busytimeStore.persist(this.busytime,trans);if(this.icalComponent){componentStore.persist(this.icalComponent,trans);}
|
||||
var alarms=this.event.remote.alarms;if(alarms&&alarms.length){var i=0;var len=alarms.length;var now=Date.now();var alarmTrans=core.db.transaction(['alarms'],'readwrite');for(;i<len;i++){var alarm={startDate:{offset:this.busytime.start.offset,utc:this.busytime.start.utc+(alarms[i].trigger*1000)},eventId:this.busytime.eventId,busytimeId:this.busytime._id};var alarmDate=Calc.dateFromTransport(this.busytime.end).valueOf();if(alarmDate<now){continue;}
|
||||
alarmStore.persist(alarm,alarmTrans);}}}};function Update(){Create.apply(this,arguments);}
|
||||
Update.prototype={commit:function(callback){var storeFactory=core.storeFactory;var busytimeStore=storeFactory.get('Busytime');var self=this;busytimeStore.removeEvent(this.event._id,function(err){if(err){callback(err);return;}
|
||||
Create.prototype.commit.call(self,callback);});}};exports.create=function createMutation(option){return new Create(option);};exports.update=function updateMutation(option){return new Update(option);};});define('provider/local',['require','exports','module','./abstract','core','common/constants','event_mutations','ext/uuid'],function(require,exports,module){'use strict';var Abstract=require('./abstract');var core=require('core');var localCalendarId=require('common/constants').localCalendarId;var mutations=require('event_mutations');var uuid=require('ext/uuid');function Local(){Abstract.apply(this,arguments);var storeFactory=core.storeFactory;this.events=storeFactory.get('Event');this.busytimes=storeFactory.get('Busytime');this.alarms=storeFactory.get('Alarm');}
|
||||
module.exports=Local;Local.defaultCalendar=function(){var l10nId='calendar-local';var name;if('mozL10n'in window.navigator){name=window.navigator.mozL10n.get(l10nId);if(name===l10nId){name=null;}}
|
||||
if(!name){name='Offline calendar';}
|
||||
return{name:name,id:localCalendarId,color:Local.prototype.defaultColor};};Local.prototype={__proto__:Abstract.prototype,canExpandRecurringEvents:false,getAccount:function(account,callback){callback(null,{});},findCalendars:function(account,callback){var list={};list[localCalendarId]=Local.defaultCalendar();callback(null,list);},syncEvents:function(account,calendar,cb){cb(null);},createEvent:function(event,callback){if(!event.remote.id){event.remote.id=uuid();}
|
||||
var create=mutations.create({event:event});create.commit(function(err){if(err){return callback(err);}
|
||||
callback(null,create.busytime,create.event);});return create;},deleteEvent:function(event,busytime,callback){if(typeof(busytime)==='function'){callback=busytime;busytime=null;}
|
||||
var storeFactory=core.storeFactory;storeFactory.get('Event').remove(event._id,callback);},updateEvent:function(event,busytime,callback){if(typeof(busytime)==='function'){callback=busytime;busytime=null;}
|
||||
var update=mutations.update({event:event});update.commit(function(err){if(err){return callback(err);}
|
||||
callback(null,update.busytime,update.event);});return update;}};});define('common/responder',['require','exports','module'],function(require,exports,module){'use strict';function Responder(events){this._$events=Object.create(null);this.buffer={};if(typeof(events)!=='undefined'){this.addEventListener(events);}}
|
||||
module.exports=Responder;Responder.stringify=function stringify(command,data){return JSON.stringify([command,data]);};Responder.parse=function parse(json){var data;try{data=(json.forEach)?json:JSON.parse(json);}catch(e){throw new Error('Could not parse json: "'+json+'"');}
|
||||
return data;};Responder.prototype={parse:Responder.parse,stringify:Responder.stringify,events:null,respond:function respond(json){var event=Responder.parse(json);var args=Array.prototype.slice.call(arguments).slice(1);this.emit.apply(this,event.concat(args));return event;},addEventListener:function addEventListener(type,callback){var event;if(typeof(callback)==='undefined'&&typeof(type)==='object'){for(event in type){if(type.hasOwnProperty(event)){this.addEventListener(event,type[event]);}}
|
||||
return this;}
|
||||
if(!(type in this._$events)){this._$events[type]=[];}
|
||||
this._$events[type].push(callback);return this.flushTopicBuffer(type);},once:function once(type,callback){var self=this;function onceCb(){self.removeEventListener(type,onceCb);callback.apply(this,arguments);}
|
||||
this.addEventListener(type,onceCb);return this.flushTopicBuffer(type);},flushTopicBuffer:function flushTopicBuffer(topic){if(!(topic in this.buffer)){return this;}
|
||||
this.buffer[topic].forEach(args=>{args.unshift(topic);this.emit.apply(this,args);});return this;},emit:function emit(){var args=Array.prototype.slice.call(arguments),event=args.shift(),eventList,self=this;if(event in this._$events){eventList=this._$events[event];eventList.forEach(function(callback){if(typeof(callback)==='object'&&callback.handleEvent){callback.handleEvent({type:event,data:args});}else{callback.apply(self,args);}});}
|
||||
return this;},emitWhenListener:function emitWhenListener(){var args=Array.prototype.slice.call(arguments);var event=args.shift();if(event in this._$events&&this._$events[event].length){return this.emit.apply(this,arguments);}
|
||||
if(!(event in this.buffer)){this.buffer[event]=[];}
|
||||
this.buffer[event].push(args);return this;},removeAllEventListeners:function removeAllEventListeners(name){if(name in this._$events){this._$events[name].length=0;}
|
||||
return this;},removeEventListener:function removeEventListener(name,callback){var i,length,events;if(!(name in this._$events)){return false;}
|
||||
events=this._$events[name];for(i=0,length=events.length;i<length;i++){if(events[i]&&events[i]===callback){events.splice(i,1);return true;}}
|
||||
return false;}};Responder.prototype.on=Responder.prototype.addEventListener;Responder.prototype.off=Responder.prototype.removeEventListener;});define('common/debug',['require','exports','module'],function(require,exports,module){'use strict';module.exports=function(name){return function(){var args=Array.prototype.slice.call(arguments).map(JSON.stringify);args.unshift('[calendar] ');args.unshift(name);console.log.apply(console,args);};};});define('common/probably_parse_int',['require','exports','module'],function(require,exports,module){'use strict';var NUMERIC=/^[0-9]+$/;module.exports=function(id){if(typeof id==='string'&&id.match(NUMERIC)){return parseInt(id,10);}
|
||||
return id;};});define('db',['require','exports','module','models/account','common/presets','provider/local','common/responder','core','common/debug','common/promise','common/next_tick','common/constants','common/probably_parse_int','ext/uuid'],function(require,exports,module){'use strict';var Account=require('models/account');var Presets=require('common/presets');var Local=require('provider/local');var Responder=require('common/responder');var core=require('core');var debug=require('common/debug')('db');var denodeifyAll=require('common/promise').denodeifyAll;var nextTick=require('common/next_tick');var localCalendarId=require('common/constants').localCalendarId;var probablyParseInt=require('common/probably_parse_int');var uuid=require('ext/uuid');var idb=window.indexedDB;const VERSION=15;var store=Object.freeze({events:'events',accounts:'accounts',calendars:'calendars',busytimes:'busytimes',settings:'settings',alarms:'alarms',icalComponents:'icalComponents'});function Db(name){this.name=name;Responder.call(this);this._upgradeOperations=[];denodeifyAll(this,['load']);}
|
||||
module.exports=Db;Db.prototype={__proto__:Responder.prototype,connection:null,load:function(callback){debug(`Will load ${this.name} db.`);var self=this;function setupDefaults(){if(self.oldVersion<8){self._setupDefaults(callback);}else{nextTick(callback);}}
|
||||
if(this.isOpen){return setupDefaults();}
|
||||
this.open(VERSION,setupDefaults);},open:function(version,callback){if(typeof(version)==='function'){callback=version;version=VERSION;}
|
||||
var req=idb.open(this.name,version);this.version=version;var self=this;req.onsuccess=function(){self.isOpen=true;self.connection=req.result;if(self._upgradeOperations.length){var pending=self._upgradeOperations.length;var operation;while((operation=self._upgradeOperations.shift())){operation.call(self,function next(){if(!(--pending)){callback(null,self);self.emit('open',self);}});}}else{callback(null,self);self.emit('open',self);}};req.onblocked=function(error){callback(error,null);self.emit('error',error);};req.onupgradeneeded=function(event){self._handleVersionChange(req.result,event);};req.onerror=function(error){callback(error,null);self.emit('error',error);};},transaction:function(list,state){var names;var self=this;if(typeof(list)==='string'){names=[];names.push(this.store[list]||list);}else{names=list.map(function(name){return self.store[name]||name;});}
|
||||
return this.connection.transaction(names,state||'readonly');},_handleVersionChange:function(db,event){var newVersion=event.newVersion;var curVersion=event.oldVersion;var transaction=event.currentTarget.transaction;this.hasUpgraded=true;this.oldVersion=curVersion;this.upgradedVersion=newVersion;for(;curVersion<newVersion;curVersion++){if(curVersion<6){var existingNames=db.objectStoreNames;for(var i=0;i<existingNames.length;i++){db.deleteObjectStore(existingNames[i]);}
|
||||
curVersion=6;var busytimes=db.createObjectStore(store.busytimes,{keyPath:'_id'});busytimes.createIndex('end','end.utc',{unique:false,multiEntry:false});busytimes.createIndex('eventId','eventId',{unique:false,multiEntry:false});var events=db.createObjectStore(store.events,{keyPath:'_id'});events.createIndex('calendarId','calendarId',{unique:false,multiEntry:false});events.createIndex('parentId','parentId',{unique:false,multiEntry:false});db.createObjectStore(store.accounts,{keyPath:'_id',autoIncrement:true});db.createObjectStore(store.calendars,{keyPath:'_id',autoIncrement:true});}else if(curVersion===7){db.createObjectStore(store.settings,{keyPath:'_id'});}else if(curVersion===8){var alarms=db.createObjectStore(store.alarms,{keyPath:'_id',autoIncrement:true});alarms.createIndex('trigger','trigger.utc',{unique:false,multiEntry:false});alarms.createIndex('busytimeId','busytimeId',{unique:false,multiEntry:false});}else if(curVersion===12){var icalComponents=db.createObjectStore(store.icalComponents,{keyPath:'eventId',autoIncrement:false});icalComponents.createIndex('lastRecurrenceId','lastRecurrenceId.utc',{unique:false,multiEntry:false});}else if(curVersion===13){var calendarStore=transaction.objectStore(store.calendars);calendarStore.createIndex('accountId','accountId',{unique:false,multiEntry:false});}else if(curVersion===14){this.sanitizeEvents(transaction);}}},sanitizeEvents:function(trans){var badCalendarIdToEventIds={};var objectStore=trans.objectStore(store.events);objectStore.openCursor().onsuccess=(function(evt){var cursor=evt.target.result;if(!cursor){return this._updateXorDeleteEvents(badCalendarIdToEventIds,trans);}
|
||||
var calendarId=cursor.value.calendarId;if(typeof(calendarId)==='number'){return cursor.continue();}
|
||||
var eventIds=badCalendarIdToEventIds[calendarId]||[];eventIds.push(cursor.key);badCalendarIdToEventIds[calendarId]=eventIds;cursor.continue();}).bind(this);},_updateXorDeleteEvents:function(badCalendarIdToEventIds,trans){var calendarIds=Object.keys(badCalendarIdToEventIds);calendarIds.forEach(function(calendarId){calendarId=probablyParseInt(calendarId);var eventIds=badCalendarIdToEventIds[calendarId];var calendars=trans.objectStore(store.calendars);calendars.get(calendarId).onsuccess=(function(evt){var result=evt.target.result;if(result){this._updateEvents(eventIds,calendarId,trans);}else{this._deleteEvents(eventIds,trans);}}).bind(this);},this);},_updateEvents:function(eventIds,calendarId,trans){var eventStore=trans.objectStore(store.events);var busytimeStore=trans.objectStore(store.busytimes);var busytimeStoreIndexedByEventId=busytimeStore.index('eventId');eventIds.forEach(function(eventId){eventStore.get(eventId).onsuccess=function(evt){var result=evt.target.result;result.calendarId=calendarId;eventStore.put(result);};busytimeStoreIndexedByEventId.get(eventId).onsuccess=function(evt){var result=evt.target.result;result.calendarId=calendarId;busytimeStore.put(result);};});},_deleteEvents:function(eventIds,trans){var events=core.storeFactory.get('Event');eventIds.forEach(function(eventId){events.remove(eventId,trans);});},get store(){return store;},close:function(){if(this.connection){this.isOpen=false;this.connection.close();this.connection=null;}},clearNonCredentials:function(callback){var stores=['events','busytimes'];var trans=this.transaction(stores,'readwrite');trans.addEventListener('complete',callback);stores.forEach(function(store){store=trans.objectStore(store);store.clear();});},_setupDefaults:function(callback){debug('Will setup defaults.');var storeFactory=core.storeFactory;var calendarStore=storeFactory.get('Calendar');var accountStore=storeFactory.get('Account');var trans=this.transaction(['accounts','calendars'],'readwrite');if(callback){trans.addEventListener('error',function(err){callback(err);});trans.addEventListener('complete',function(){callback();});}
|
||||
var options=Presets.local.options;debug('Creating local calendar with options:',options);var account=new Account(options);account.preset='local';account._id=uuid();var calendar={_id:localCalendarId,accountId:account._id,remote:Local.defaultCalendar()};accountStore.persist(account,trans);calendarStore.persist(calendar,trans);},deleteDatabase:function(callback){var req=idb.deleteDatabase(this.name);req.onblocked=function(){callback(new Error('blocked'));};req.onsuccess=function(event){callback(null,event);};req.onerror=function(event){callback(event,null);};}};});define('common/error',['require','exports','module'],function(require,exports,module){'use strict';function Base(name,detail){this.message='oops... why did you throw this?';this.name=name;this.detail=detail;}
|
||||
module.exports=Base;Base.prototype=Object.create(Error.prototype);function errorFactory(name,l10nID){var error=function(detail){this.name=name;this.detail=detail;this.l10nID=l10nID||name;};error.prototype=Object.create(Base.prototype);return error;}
|
||||
Base.Authentication=errorFactory('authentication','unauthenticated');Base.InvalidServer=errorFactory('invalid-server','internal-server-error');Base.ServerFailure=errorFactory('server-failure','internal-server-error');});(function(window){'use strict';window.NotificationHelper={getIconURI:function nh_getIconURI(app,entryPoint){var icons=app.manifest.icons;if(entryPoint){icons=app.manifest.entry_points[entryPoint].icons;}
|
||||
if(!icons){return null;}
|
||||
var sizes=Object.keys(icons).map(function parse(str){return parseInt(str,10);});sizes.sort(function(x,y){return y-x;});var HVGA=document.documentElement.clientWidth<480;var index=sizes[HVGA?sizes.length-1:0];return app.installOrigin+icons[index];},send:function nh_send(titleL10n,options){return Promise.all([titleL10n,options.bodyL10n].map(getL10n)).then(([title,body])=>{if(body){options.body=body;}
|
||||
options.lang=document.documentElement.getAttribute('lang');var notification=new window.Notification(title,options);if(options.closeOnClick!==false){notification.addEventListener('click',function nh_click(){notification.removeEventListener('click',nh_click);notification.close();});}
|
||||
return notification;});},};function getL10n(l10nAttrs){if(!l10nAttrs){return;}
|
||||
if(typeof l10nAttrs==='string'){return document.l10n.formatValue(l10nAttrs);}
|
||||
if(typeof l10nAttrs.raw==="string"){return l10nAttrs.raw;}
|
||||
return document.l10n.formatValue(l10nAttrs.id,l10nAttrs.args);}})(this);define("shared/notification_helper",(function(global){return function(){var ret,fn;return ret||global.NotificationHelper;};}(this)));define('performance',['require','exports','module'],function(require,exports){'use strict';exports._isMonthAgendaInteractive=false;exports._isMonthReady=false;exports._isVisuallyActive=false;exports._isPendingReady=false;var dispatched={};function dispatch(markName){dispatched[markName]=true;window.performance.mark(markName);}
|
||||
exports.isComplete=function(markName){return dispatched[markName];};exports.domLoaded=function(){dispatch('navigationLoaded');};exports.chromeInteractive=function(){dispatch('navigationInteractive');};exports.monthsDayReady=function(){if(exports._isMonthAgendaInteractive){return;}
|
||||
exports._isMonthAgendaInteractive=true;dispatchVisuallyLoadedAndInteractive();};exports.monthReady=function(){if(exports._isMonthReady){return;}
|
||||
exports._isMonthReady=true;dispatchVisuallyLoadedAndInteractive();};function dispatchVisuallyLoadedAndInteractive(){if(exports._isVisuallyActive||!exports._isMonthAgendaInteractive||!exports._isMonthReady){return;}
|
||||
exports._isVisuallyActive=true;dispatch('visuallyLoaded');dispatch('contentInteractive');dispatchAppLoad();}
|
||||
exports.pendingReady=function(){if(exports._isPendingReady){return;}
|
||||
exports._isPendingReady=true;dispatchAppLoad();};function dispatchAppLoad(){if(!exports._isVisuallyActive||!exports._isPendingReady){return;}
|
||||
dispatch('fullyLoaded');window.dispatchEvent(new CustomEvent('fullyLoaded'));}});!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define('ext/page',[],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.page=e()}}(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){var pathtoRegexp=require('path-to-regexp');module.exports=page;var location=window.history.location||window.location;var dispatch=true;var base='';var running;var hashbang=false;function page(path,fn){if('function'===typeof path){return page('*',path);}
|
||||
if('function'===typeof fn){var route=new Route(path);for(var i=1;i<arguments.length;++i){page.callbacks.push(route.middleware(arguments[i]));}}else if('string'==typeof path){'string'===typeof fn?page.redirect(path,fn):page.show(path,fn);}else{page.start(path);}}
|
||||
page.callbacks=[];page.base=function(path){if(0===arguments.length)return base;base=path;};page.start=function(options){options=options||{};if(running)return;running=true;if(false===options.dispatch)dispatch=false;if(false!==options.popstate)window.addEventListener('popstate',onpopstate,false);if(false!==options.click)window.addEventListener('click',onclick,false);if(true===options.hashbang)hashbang=true;if(!dispatch)return;var url=(hashbang&&~location.hash.indexOf('#!'))?location.hash.substr(2)+location.search:location.pathname+location.search+location.hash;page.replace(url,null,true,dispatch);};page.stop=function(){if(!running)return;running=false;window.removeEventListener('click',onclick,false);window.removeEventListener('popstate',onpopstate,false);};page.show=function(path,state,dispatch){var ctx=new Context(path,state);if(false!==dispatch)page.dispatch(ctx);if(false!==ctx.handled)ctx.pushState();return ctx;};page.redirect=function(from,to){page(from,function(e){setTimeout(function(){page.replace(to);});});};page.replace=function(path,state,init,dispatch){var ctx=new Context(path,state);ctx.init=init;ctx.save();if(false!==dispatch)page.dispatch(ctx);return ctx;};page.dispatch=function(ctx){var i=0;function next(){var fn=page.callbacks[i++];if(!fn)return unhandled(ctx);fn(ctx,next);}
|
||||
next();};function unhandled(ctx){if(ctx.handled)return;var current;if(hashbang){current=base+location.hash.replace('#!','');}else{current=location.pathname+location.search;}
|
||||
if(current===ctx.canonicalPath)return;page.stop();ctx.handled=false;location.href=ctx.canonicalPath;}
|
||||
function Context(path,state){if('/'===path[0]&&0!==path.indexOf(base))path=base+path;var i=path.indexOf('?');this.canonicalPath=path;this.path=path.replace(base,'')||'/';this.title=document.title;this.state=state||{};this.state.path=path;this.querystring=~i?path.slice(i+1):'';this.pathname=~i?path.slice(0,i):path;this.params=[];this.hash='';if(!~this.path.indexOf('#'))return;var parts=this.path.split('#');this.path=parts[0];this.hash=parts[1]||'';this.querystring=this.querystring.split('#')[0];}
|
||||
page.Context=Context;Context.prototype.pushState=function(){history.pushState(this.state,this.title,hashbang&&this.path!=='/'?'#!'+this.path:this.canonicalPath);};Context.prototype.save=function(){history.replaceState(this.state,this.title,hashbang&&this.path!=='/'?'#!'+this.path:this.canonicalPath);};function Route(path,options){options=options||{};this.path=(path==='*')?'(.*)':path;this.method='GET';this.regexp=pathtoRegexp(this.path,this.keys=[],options.sensitive,options.strict);}
|
||||
page.Route=Route;Route.prototype.middleware=function(fn){var self=this;return function(ctx,next){if(self.match(ctx.path,ctx.params))return fn(ctx,next);next();};};Route.prototype.match=function(path,params){var keys=this.keys,qsIndex=path.indexOf('?'),pathname=~qsIndex?path.slice(0,qsIndex):path,m=this.regexp.exec(decodeURIComponent(pathname));if(!m)return false;for(var i=1,len=m.length;i<len;++i){var key=keys[i-1];var val='string'===typeof m[i]?decodeURIComponent(m[i]):m[i];if(key){params[key.name]=undefined!==params[key.name]?params[key.name]:val;}else{params.push(val);}}
|
||||
return true;};function onpopstate(e){if(e.state){var path=e.state.path;page.replace(path,e.state);}}
|
||||
function onclick(e){if(1!=which(e))return;if(e.metaKey||e.ctrlKey||e.shiftKey)return;if(e.defaultPrevented)return;var el=e.target;while(el&&'A'!=el.nodeName)el=el.parentNode;if(!el||'A'!=el.nodeName)return;var link=el.getAttribute('href');if(el.pathname===location.pathname&&(el.hash||'#'===link))return;if(link&&link.indexOf("mailto:")>-1)return;if(el.target)return;if(!sameOrigin(el.href))return;var path=el.pathname+el.search+(el.hash||'');var orig=path;path=path.replace(base,'');if(base&&orig===path)return;e.preventDefault();page.show(orig);}
|
||||
function which(e){e=e||window.event;return null===e.which?e.button:e.which;}
|
||||
function sameOrigin(href){var origin=location.protocol+'//'+location.hostname;if(location.port)origin+=':'+location.port;return(href&&(0===href.indexOf(origin)));}
|
||||
page.sameOrigin=sameOrigin;},{"path-to-regexp":2}],2:[function(require,module,exports){module.exports=pathtoRegexp;var PATH_REGEXP=new RegExp(['(\\\\.)','([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?','([.+*?=^!:${}()[\\]|\\/])'].join('|'),'g');function escapeGroup(group){return group.replace(/([=!:$\/()])/g,'\\$1');}
|
||||
var attachKeys=function(re,keys){re.keys=keys;return re;};function pathtoRegexp(path,keys,options){if(keys&&!Array.isArray(keys)){options=keys;keys=null;}
|
||||
keys=keys||[];options=options||{};var strict=options.strict;var end=options.end!==false;var flags=options.sensitive?'':'i';var index=0;if(path instanceof RegExp){var groups=path.source.match(/\((?!\?)/g)||[];keys.push.apply(keys,groups.map(function(match,index){return{name:index,delimiter:null,optional:false,repeat:false};}));return attachKeys(path,keys);}
|
||||
if(Array.isArray(path)){path=path.map(function(value){return pathtoRegexp(value,keys,options).source;});return attachKeys(new RegExp('(?:'+path.join('|')+')',flags),keys);}
|
||||
path=path.replace(PATH_REGEXP,function(match,escaped,prefix,key,capture,group,suffix,escape){if(escaped){return escaped;}
|
||||
if(escape){return'\\'+escape;}
|
||||
var repeat=suffix==='+'||suffix==='*';var optional=suffix==='?'||suffix==='*';keys.push({name:key||index++,delimiter:prefix||'/',optional:optional,repeat:repeat});prefix=prefix?'\\'+prefix:'';capture=escapeGroup(capture||group||'[^'+(prefix||'\\/')+']+?');if(repeat){capture=capture+'(?:'+prefix+capture+')*';}
|
||||
if(optional){return'(?:'+prefix+'('+capture+'))?';}
|
||||
return prefix+'('+capture+')';});var endsWithSlash=path[path.length-1]==='/';if(!strict){path=(endsWithSlash?path.slice(0,-2):path)+'(?:\\/(?=$))?';}
|
||||
if(!end){path+=strict&&endsWithSlash?'':'(?=\\/|$)';}
|
||||
return attachKeys(new RegExp('^'+path+(end?'$':''),flags),keys);};},{}]},{},[1])(1)});define('router',['require','exports','module','core','ext/page'],function(require,exports,module){'use strict';var COPY_METHODS=['start','stop','show'];var core=require('core');var page=require('ext/page');function Router(){var i=0;var len=COPY_METHODS.length;this.page=page;this.page({hashbang:true});this._activeObjects=[];for(;i<len;i++){this[COPY_METHODS[i]]=page[COPY_METHODS[i]].bind(page);}
|
||||
this._lastState=this._lastState.bind(this);}
|
||||
Router.prototype={go:function(path,context){this.show(path,context);},mangeObject:function(){var args=Array.prototype.slice.call(arguments);var object=args.shift();this._activeObjects.push(object);if('onactive'in object){object.onactive.apply(object,args);}},clearObjects:function(){var item;while((item=this._activeObjects.pop())){if('oninactive'in item){item.oninactive();}}},_lastState:function(ctx){this.last=ctx;},resetState:function(){if(!this.currentPath){this.currentPath='/month/';}
|
||||
this.show(this.currentPath);},state:function(path,views,options){options=options||{};if(!Array.isArray(views)){views=[views];}
|
||||
var self=this;var viewObjs=[];function loadAllViews(ctx,next){var len=views.length;var numViews=len;var i;viewObjs=[];for(i=0;i<numViews;i++){core.viewFactory.get(views[i],function(view){viewObjs.push(view);len--;if(!len){next();}});}}
|
||||
function setPath(ctx,next){var meta=document.querySelector('meta[name="theme-color"]');meta.setAttribute('content',meta.dataset[options.color||'default']);if(options.path!==false){document.body.dataset.path=ctx.canonicalPath;document.body.clientWidth;}
|
||||
next();}
|
||||
function handleViews(ctx,next){if(options.clear!==false){self.clearObjects();}
|
||||
for(var i=0;i<viewObjs.length;i++){self.mangeObject(viewObjs[i],ctx);}
|
||||
if(options.appPath!==false){self.currentPath=ctx.canonicalPath;}
|
||||
next();}
|
||||
this.page(path,loadAllViews,setPath,handleViews,this._lastState);},modifier:function(path,view,options){options=options||{};options.appPath=false;options.clear=false;this.state(path,view,options);}};module.exports=new Router();});define('notification',['require','exports','module','shared/notification_helper','ext/co','common/debug','performance','router'],function(require,exports,module){'use strict';var NotificationHelper=require('shared/notification_helper');var co=require('ext/co');var debug=require('common/debug')('notification');var performance=require('performance');var router=require('router');var cachedSelf;exports.sendNotification=co.wrap(function*(titleL10n,bodyL10n,url,data){data=data||{};var app=yield getSelf();if(!app){debug('mozApps.getSelf fail');return;}
|
||||
var icon=`${NotificationHelper.getIconURI(app)}?${url}`;var notification=yield NotificationHelper.send(titleL10n,{bodyL10n:bodyL10n,icon:icon,tag:url,data:data,closeOnClick:false});return new Promise(function(resolve,reject){notification.onshow=resolve;notification.onerror=reject;notification.onclick=()=>launch(url);});});exports.revokeNotificationsForEvent=co.wrap(function*(event){var id=event.remote.id;var notifications=yield Notification.get();notifications.forEach(notification=>{if(notification.data.id===id){notification.close();}});});function getSelf(){if(!cachedSelf){cachedSelf=new Promise((resolve,reject)=>{var request=navigator.mozApps.getSelf();request.onsuccess=(event)=>{resolve(event.target.result);};request.onerror=()=>{reject(new Error('mozApps.getSelf failed!'));};});}
|
||||
return cachedSelf;}
|
||||
function launch(url){closeNotifications(url);if(performance.isComplete('fullyLoaded')){return foreground(url);}
|
||||
window.addEventListener('fullyLoaded',function onMozAppLoaded(){window.removeEventListener('fullyLoaded',onMozAppLoaded);return foreground(url);});}
|
||||
exports.launch=launch;function foreground(url){return getSelf().then(app=>{router.go(url);return app&&app.launch();});}
|
||||
function closeNotifications(url){Notification.get({tag:url}).then(notifications=>{notifications.forEach(n=>n.close());});}});define('controllers/error',['require','exports','module','common/error','common/error','common/responder','common/next_tick','notification'],function(require,exports,module){'use strict';var Authentication=require('common/error').Authentication;var InvalidServer=require('common/error').InvalidServer;var Responder=require('common/responder');var nextTick=require('common/next_tick');var notification=require('notification');function ErrorController(){Responder.call(this);this._handlers=Object.create(null);}
|
||||
module.exports=ErrorController;ErrorController.prototype={__proto__:Responder.prototype,accountErrorUrl:'/update-account/',dispatch:function(error){if(error instanceof Authentication||error instanceof InvalidServer){this.handleAuthenticate(error.detail.account);}
|
||||
this.emit('error',error);},handleAuthenticate:function(account,callback){if(!account){return console.error('attempting to trigger reauth without an account');}
|
||||
if(!account.error||account.error.count!==1){return nextTick(callback);}
|
||||
var lock=navigator.requestWakeLock('cpu');var titleL10n='notification-error-sync-title';var bodyL10n='notification-error-sync-description';var url=this.accountErrorUrl+account._id;notification.sendNotification(titleL10n,bodyL10n,url).then(()=>{callback&&callback();lock.unlock();});}};});define('pending_manager',['require','exports','module','common/next_tick'],function(require,exports,module){'use strict';var nextTick=require('common/next_tick');function PendingManager(){this.objects=[];this.pending=0;this.onstart=this.onstart.bind(this);this.onend=this.onend.bind(this);}
|
||||
module.exports=PendingManager;PendingManager.prototype={register:function(object){object.on(object.startEvent,this.onstart);object.on(object.completeEvent,this.onend);if(!this.isPending()&&object.pending){nextTick(()=>this.onpending&&this.onpending());}
|
||||
this.objects.push(object);},unregister:function(object){this.objects=this.objects.filter(el=>el!==object);},isPending:function(){return this.objects.some(object=>object.pending);},onstart:function(){if(!this.pending){this.onpending&&this.onpending();}
|
||||
this.pending++;},onend:function(){this.pending--;if(!this.pending){this.oncomplete&&this.oncomplete();}}};});define('controllers/recurring_events',['require','exports','module','common/responder','core','common/debug','common/next_tick'],function(require,exports,module){'use strict';var Responder=require('common/responder');var core=require('core');var debug=require('common/debug')('controllers/recurring_events');var nextTick=require('common/next_tick');function RecurringEvents(){this.accounts=core.storeFactory.get('Account');Responder.call(this);}
|
||||
module.exports=RecurringEvents;RecurringEvents.prototype={__proto__:Responder.prototype,startEvent:'expandStart',completeEvent:'expandComplete',paddingInDays:85,waitBeforeMove:750,maximumExpansions:25,_moveTimeout:null,pending:false,unobserve:function(){core.timeController.removeEventListener('monthChange',this);core.syncController.removeEventListener('syncComplete',this);},observe:function(){var time=core.timeController;if(time.position){this.queueExpand(time.position);}
|
||||
time.on('monthChange',this);core.syncController.on('syncComplete',this);},handleEvent:function(event){switch(event.type){case'syncComplete':this.queueExpand(core.timeController.position);break;case'monthChange':if(this._moveTimeout!==null){clearTimeout(this._moveTimeout);this._moveTimeout=null;}
|
||||
this._moveTimeout=setTimeout(this.queueExpand.bind(this,event.data[0]),this.waitBeforeMove);break;}},_expandProvider:function(expandDate,provider,callback){debug('Will attempt to expand provider until:',expandDate);var tries=0;var max=this.maximumExpansions;function attemptCompleteExpand(){debug('Will try to complete expansion (tries = '+tries+')');if(tries>=max){return callback(new Error('could not complete expansion after "'+tries+'"'));}
|
||||
provider.ensureRecurrencesExpanded(expandDate,function(err,didExpand){if(err){return callback(err);}
|
||||
debug('Expansion attempt did expand:',didExpand);if(!didExpand){callback();}else{tries++;nextTick(attemptCompleteExpand);}});}
|
||||
attemptCompleteExpand();},queueExpand:function(expandTo){if(this.pending){if(!this._next){this._next=expandTo;}else if(expandTo>this._next){this._next=expandTo;}
|
||||
return;}
|
||||
this.pending=true;this.emit('expandStart');var self=this;function expandNext(date){self.expand(date,function(){if(date===self._next){self._next=null;}
|
||||
var next=self._next;if(!next){self.pending=false;self.emit('expandComplete');return;}
|
||||
expandNext(next);});}
|
||||
expandNext(expandTo);},expand:function(expandTo,callback){debug('expand',expandTo);this.accounts.all((err,accounts)=>{if(err){return callback(err);}
|
||||
var expandDate=new Date(expandTo.valueOf());expandDate.setDate(expandDate.getDate()+this.paddingInDays);var providers=this._getExpandableProviders(accounts);var pending=providers.length;if(!pending){return nextTick(callback);}
|
||||
providers.forEach(provider=>{this._expandProvider(expandDate,provider,()=>{if(--pending<=0){callback();}});});});},_getExpandableProviders:function(accounts){var providers=[];Object.keys(accounts).forEach(key=>{var account=accounts[key];var provider=core.providerFactory.get(account.providerType);if(provider&&provider.canExpandRecurringEvents&&providers.indexOf(provider)===-1){providers.push(provider);}});return providers;}};});define('worker/manager',['require','exports','module','common/responder','common/debug'],function(require,exports,module){'use strict';var Responder=require('common/responder');var debug=require('common/debug')('worker/manager');const IDLE_CLEANUP_TIME=5000;function Manager(){this._lastId=0;Responder.call(this);this.roles=Object.create(null);this.workers=[];}
|
||||
module.exports=Manager;Manager.prototype={Worker:Worker,__proto__:Responder.prototype,_onLog:debug,_formatData:function(data){if(data[1]&&data[1].stack&&data[1].constructorName){var err=data[1];var builtErr;if(window[err.constructorName]){builtErr=Object.create(window[err.constructorName].prototype);}else{builtErr=Object.create(Error.prototype);}
|
||||
var key;for(key in err){if(err.hasOwnProperty(key)){builtErr[key]=err[key];}}
|
||||
data[1]=builtErr;}
|
||||
return data;},_onWorkerError:function(worker,err){if(/reference to undefined property/.test(err.message)){return;}
|
||||
if(worker.instance){worker.instance.terminate();worker.instance=null;}
|
||||
var pending=worker.pending;worker.pending=Object.create(null);for(var id in pending){if(pending[id].stream){pending[id].stream.emit('error',err);}
|
||||
pending[id].callback(err);}},_onWorkerMessage:function(worker,event){var data=this._formatData(event.data);var type=data.shift();var match=type.match(/^(\d+) (end|stream)$/);if(type=='log'){this._onLog.apply(this,data);}else if(match){var pending=worker.pending[match[1]];if(pending){this._dispatchMessage(worker,pending,match[2],data);}else{throw new Error('Message arrived for unknown consumer: '+
|
||||
type+' '+JSON.stringify(data));}}else{this.respond([type].concat(data));}},_dispatchMessage:function(worker,pending,type,data){if(type=='stream'){pending.stream.respond(data);}else{pending.callback.apply(null,data);delete worker.pending[pending.id];if(Object.keys(worker.pending).length){return;}
|
||||
this._scheduleCleanup(worker);}},_addPending:function(worker,pending){worker.pending[pending.id]=pending;clearTimeout(worker.cleanup);},_scheduleCleanup:function(worker){clearTimeout(worker.cleanup);worker.cleanup=setTimeout(function(){if(Object.keys(worker.pending).length){return;}
|
||||
if(!worker.instance){return;}
|
||||
worker.instance.terminate();worker.instance=null;},IDLE_CLEANUP_TIME);},add:function(role,workerURL){debug('Will add',role,'worker at',workerURL);var worker={instance:null,pending:Object.create(null),url:workerURL,cleanup:null};this.workers.push(worker);[].concat(role).forEach(function(role){if(!(role in this.roles)){this.roles[role]=[worker];}else{this.roles[role].push(worker);}},this);},_ensureActiveWorker:function(role){if(role in this.roles){var workers=this.roles[role];var worker=workers[Math.floor(Math.random()*workers.length)];if(worker.instance){return worker;}else{this._startWorker(worker);return worker;}}else{throw new Error('no worker with role "'+role+'" active');}},_startWorker:function(worker){worker.instance=new this.Worker(worker.url+'?time='+Date.now());worker.instance.onerror=this._onWorkerError.bind(this,worker);worker.instance.onmessage=this._onWorkerMessage.bind(this,worker);this._scheduleCleanup(worker);},request:function(role){var args=Array.prototype.slice.call(arguments,1);var callback=args.pop();var worker=null;try{worker=this._ensureActiveWorker(role);}catch(e){callback(e);return;}
|
||||
var data={id:this._lastId++,role:role,payload:args};this._addPending(worker,{id:data.id,callback:callback});worker.instance.postMessage(['_dispatch',data]);},stream:function(role){var args=Array.prototype.slice.call(arguments,1);var stream=new Responder();var self=this;var data={id:this._lastId++,role:role,payload:args,type:'stream'};stream.request=function(callback){var worker=null;stream.request=function(){throw new Error('stream request has been sent');};try{worker=self._ensureActiveWorker(role);}catch(e){callback(e);return;}
|
||||
self._addPending(worker,{id:data.id,stream:stream,callback:callback});worker.instance.postMessage(['_dispatch',data]);};return stream;}};});define('controllers/service',['require','exports','module','worker/manager','common/debug'],function(require,exports,module){'use strict';var Manager=require('worker/manager');var debug=require('common/debug')('controllers/service');function Service(){Manager.call(this);}
|
||||
module.exports=Service;Service.prototype={__proto__:Manager.prototype,start:function(){debug('Will load and initialize worker...');this.add('caldav','/js/caldav_worker.js');}};});define('common/is_offline',['require','exports','module'],function(require,exports,module){'use strict';module.exports=function(){return(navigator&&'onLine'in navigator)?!navigator.onLine:true;};});define('controllers/sync',['require','exports','module','common/responder','core','common/is_offline'],function(require,exports,module){'use strict';var Responder=require('common/responder');var core=require('core');var isOffline=require('common/is_offline');function Sync(){this.pending=0;Responder.call(this);}
|
||||
module.exports=Sync;Sync.prototype={__proto__:Responder.prototype,startEvent:'syncStart',completeEvent:'syncComplete',_incrementPending:function(){if(!this.pending){this.emit('syncStart');}
|
||||
this.pending++;},_resolvePending:function(){if(!(--this.pending)){this.emit('syncComplete');}
|
||||
if(this.pending<0){dump('\n\n Error calendar sync .pending is < 0 \n\n');}},all:function(callback){if(callback){this.once('syncComplete',callback);}
|
||||
if(isOffline()){this.emit('offline');this.emit('syncComplete');return;}
|
||||
var account=core.storeFactory.get('Account');account.all(function(err,list){for(var key in list){this.account(list[key]);}
|
||||
if(!this.pending){this.emit('syncComplete');}}.bind(this));},calendar:function(account,calendar,callback){var store=core.storeFactory.get('Calendar');var self=this;this._incrementPending();store.sync(account,calendar,err=>{self._resolvePending();this.handleError(err,callback);});},account:function(account,callback){var storeFactory=core.storeFactory;var accountStore=storeFactory.get('Account');var calendarStore=storeFactory.get('Calendar');var self=this;this._incrementPending();accountStore.sync(account,err=>{if(err){self._resolvePending();return this.handleError(err,callback);}
|
||||
var pending=0;function next(){if(!(--pending)){self._resolvePending();if(callback){callback();}}}
|
||||
function fetchCalendars(err,calendars){if(err){self._resolvePending();return self.handleError(err,callback);}
|
||||
for(var key in calendars){pending++;self.calendar(account,calendars[key],next);}}
|
||||
calendarStore.remotesByAccount(account._id,fetchCalendars);});},handleError:function(err,callback){if(callback){return callback(err);}
|
||||
core.errorController.dispatch(err);}};});define('controllers/time',['require','exports','module','common/calc','common/responder'],function(require,exports,module){'use strict';var isSameDate=require('common/calc').isSameDate;var Responder=require('common/responder');function Time(){Responder.call(this);this._timeCache=Object.create(null);}
|
||||
module.exports=Time;Time.prototype={__proto__:Responder.prototype,_position:null,_currentTimespan:null,_timeCache:null,_scale:null,_mostRecentDayType:'day',cacheLocked:false,get mostRecentDayType(){return this._mostRecentDayType;},get mostRecentDay(){if(this.mostRecentDayType==='selectedDay'){return this.selectedDay;}else{return this.position;}},get timespan(){return this._currentTimespan;},get scale(){return this._scale;},set scale(value){var oldValue=this._scale;if(value!==oldValue){this._scale=value;this.emit('scaleChange',value,oldValue);}},get selectedDay(){return this._selectedDay;},set selectedDay(value){var day=this._selectedDay;this._mostRecentDayType='selectedDay';if(!day||!isSameDate(day,value)){this._selectedDay=value;this.emit('selectedDayChange',value,day);}},moveToMostRecentDay:function(){if(this.mostRecentDayType==='selectedDay'){this.move(this.selectedDay);}},_updateCache:function(type,value){var old=this._timeCache[type];if(!old||!isSameDate(value,old)){this._timeCache[type]=value;this.emit(type+'Change',value,old);}},get month(){return this._timeCache.month;},get day(){return this._timeCache.day;},get year(){return this._timeCache.year;},get position(){return this._position;},move:function(date){var year=date.getFullYear();var month=date.getMonth();var yearDate=new Date(year,0,1);var monthDate=new Date(year,month,1);this._position=date;this._mostRecentDayType='day';this._updateCache('year',yearDate);this._updateCache('month',monthDate);this._updateCache('day',date);}};});define('common/async_require',['require','exports','module'],function(require,exports,module){'use strict';module.exports=function(ids){if(!Array.isArray(ids)){ids=[ids];}
|
||||
var requireFn=window.requirejs||window.require||require;return new Promise((resolve,reject)=>requireFn(ids,resolve,reject));};});;!function(undefined){var isArray=Array.isArray?Array.isArray:function _isArray(obj){return Object.prototype.toString.call(obj)==="[object Array]";};var defaultMaxListeners=10;function init(){this._events={};if(this._conf){configure.call(this,this._conf);}}
|
||||
function configure(conf){if(conf){this._conf=conf;conf.delimiter&&(this.delimiter=conf.delimiter);conf.maxListeners&&(this._events.maxListeners=conf.maxListeners);conf.wildcard&&(this.wildcard=conf.wildcard);conf.newListener&&(this.newListener=conf.newListener);if(this.wildcard){this.listenerTree={};}}}
|
||||
function EventEmitter(conf){this._events={};this.newListener=false;configure.call(this,conf);}
|
||||
function searchListenerTree(handlers,type,tree,i){if(!tree){return[];}
|
||||
var listeners=[],leaf,len,branch,xTree,xxTree,isolatedBranch,endReached,typeLength=type.length,currentType=type[i],nextType=type[i+1];if(i===typeLength&&tree._listeners){if(typeof tree._listeners==='function'){handlers&&handlers.push(tree._listeners);return[tree];}else{for(leaf=0,len=tree._listeners.length;leaf<len;leaf++){handlers&&handlers.push(tree._listeners[leaf]);}
|
||||
return[tree];}}
|
||||
if((currentType==='*'||currentType==='**')||tree[currentType]){if(currentType==='*'){for(branch in tree){if(branch!=='_listeners'&&tree.hasOwnProperty(branch)){listeners=listeners.concat(searchListenerTree(handlers,type,tree[branch],i+1));}}
|
||||
return listeners;}else if(currentType==='**'){endReached=(i+1===typeLength||(i+2===typeLength&&nextType==='*'));if(endReached&&tree._listeners){listeners=listeners.concat(searchListenerTree(handlers,type,tree,typeLength));}
|
||||
for(branch in tree){if(branch!=='_listeners'&&tree.hasOwnProperty(branch)){if(branch==='*'||branch==='**'){if(tree[branch]._listeners&&!endReached){listeners=listeners.concat(searchListenerTree(handlers,type,tree[branch],typeLength));}
|
||||
listeners=listeners.concat(searchListenerTree(handlers,type,tree[branch],i));}else if(branch===nextType){listeners=listeners.concat(searchListenerTree(handlers,type,tree[branch],i+2));}else{listeners=listeners.concat(searchListenerTree(handlers,type,tree[branch],i));}}}
|
||||
return listeners;}
|
||||
listeners=listeners.concat(searchListenerTree(handlers,type,tree[currentType],i+1));}
|
||||
xTree=tree['*'];if(xTree){searchListenerTree(handlers,type,xTree,i+1);}
|
||||
xxTree=tree['**'];if(xxTree){if(i<typeLength){if(xxTree._listeners){searchListenerTree(handlers,type,xxTree,typeLength);}
|
||||
for(branch in xxTree){if(branch!=='_listeners'&&xxTree.hasOwnProperty(branch)){if(branch===nextType){searchListenerTree(handlers,type,xxTree[branch],i+2);}else if(branch===currentType){searchListenerTree(handlers,type,xxTree[branch],i+1);}else{isolatedBranch={};isolatedBranch[branch]=xxTree[branch];searchListenerTree(handlers,type,{'**':isolatedBranch},i+1);}}}}else if(xxTree._listeners){searchListenerTree(handlers,type,xxTree,typeLength);}else if(xxTree['*']&&xxTree['*']._listeners){searchListenerTree(handlers,type,xxTree['*'],typeLength);}}
|
||||
return listeners;}
|
||||
function growListenerTree(type,listener){type=typeof type==='string'?type.split(this.delimiter):type.slice();for(var i=0,len=type.length;i+1<len;i++){if(type[i]==='**'&&type[i+1]==='**'){return;}}
|
||||
var tree=this.listenerTree;var name=type.shift();while(name){if(!tree[name]){tree[name]={};}
|
||||
tree=tree[name];if(type.length===0){if(!tree._listeners){tree._listeners=listener;}
|
||||
else if(typeof tree._listeners==='function'){tree._listeners=[tree._listeners,listener];}
|
||||
else if(isArray(tree._listeners)){tree._listeners.push(listener);if(!tree._listeners.warned){var m=defaultMaxListeners;if(typeof this._events.maxListeners!=='undefined'){m=this._events.maxListeners;}
|
||||
if(m>0&&tree._listeners.length>m){tree._listeners.warned=true;console.error('(node) warning: possible EventEmitter memory '+'leak detected. %d listeners added. '+'Use emitter.setMaxListeners() to increase limit.',tree._listeners.length);console.trace();}}}
|
||||
return true;}
|
||||
name=type.shift();}
|
||||
return true;}
|
||||
EventEmitter.prototype.delimiter='.';EventEmitter.prototype.setMaxListeners=function(n){this._events||init.call(this);this._events.maxListeners=n;if(!this._conf)this._conf={};this._conf.maxListeners=n;};EventEmitter.prototype.event='';EventEmitter.prototype.once=function(event,fn){this.many(event,1,fn);return this;};EventEmitter.prototype.many=function(event,ttl,fn){var self=this;if(typeof fn!=='function'){throw new Error('many only accepts instances of Function');}
|
||||
function listener(){if(--ttl===0){self.off(event,listener);}
|
||||
fn.apply(this,arguments);}
|
||||
listener._origin=fn;this.on(event,listener);return self;};EventEmitter.prototype.emit=function(){this._events||init.call(this);var type=arguments[0];if(type==='newListener'&&!this.newListener){if(!this._events.newListener){return false;}}
|
||||
if(this._all){var l=arguments.length;var args=new Array(l-1);for(var i=1;i<l;i++)args[i-1]=arguments[i];for(i=0,l=this._all.length;i<l;i++){this.event=type;this._all[i].apply(this,args);}}
|
||||
if(type==='error'){if(!this._all&&!this._events.error&&!(this.wildcard&&this.listenerTree.error)){if(arguments[1]instanceof Error){throw arguments[1];}else{throw new Error("Uncaught, unspecified 'error' event.");}
|
||||
return false;}}
|
||||
var handler;if(this.wildcard){handler=[];var ns=typeof type==='string'?type.split(this.delimiter):type.slice();searchListenerTree.call(this,handler,ns,this.listenerTree,0);}
|
||||
else{handler=this._events[type];}
|
||||
if(typeof handler==='function'){this.event=type;if(arguments.length===1){handler.call(this);}
|
||||
else if(arguments.length>1)
|
||||
switch(arguments.length){case 2:handler.call(this,arguments[1]);break;case 3:handler.call(this,arguments[1],arguments[2]);break;default:var l=arguments.length;var args=new Array(l-1);for(var i=1;i<l;i++)args[i-1]=arguments[i];handler.apply(this,args);}
|
||||
return true;}
|
||||
else if(handler){var l=arguments.length;var args=new Array(l-1);for(var i=1;i<l;i++)args[i-1]=arguments[i];var listeners=handler.slice();for(var i=0,l=listeners.length;i<l;i++){this.event=type;listeners[i].apply(this,args);}
|
||||
return(listeners.length>0)||!!this._all;}
|
||||
else{return!!this._all;}};EventEmitter.prototype.on=function(type,listener){if(typeof type==='function'){this.onAny(type);return this;}
|
||||
if(typeof listener!=='function'){throw new Error('on only accepts instances of Function');}
|
||||
this._events||init.call(this);this.emit('newListener',type,listener);if(this.wildcard){growListenerTree.call(this,type,listener);return this;}
|
||||
if(!this._events[type]){this._events[type]=listener;}
|
||||
else if(typeof this._events[type]==='function'){this._events[type]=[this._events[type],listener];}
|
||||
else if(isArray(this._events[type])){this._events[type].push(listener);if(!this._events[type].warned){var m=defaultMaxListeners;if(typeof this._events.maxListeners!=='undefined'){m=this._events.maxListeners;}
|
||||
if(m>0&&this._events[type].length>m){this._events[type].warned=true;console.error('(node) warning: possible EventEmitter memory '+'leak detected. %d listeners added. '+'Use emitter.setMaxListeners() to increase limit.',this._events[type].length);console.trace();}}}
|
||||
return this;};EventEmitter.prototype.onAny=function(fn){if(typeof fn!=='function'){throw new Error('onAny only accepts instances of Function');}
|
||||
if(!this._all){this._all=[];}
|
||||
this._all.push(fn);return this;};EventEmitter.prototype.addListener=EventEmitter.prototype.on;EventEmitter.prototype.off=function(type,listener){if(typeof listener!=='function'){throw new Error('removeListener only takes instances of Function');}
|
||||
var handlers,leafs=[];if(this.wildcard){var ns=typeof type==='string'?type.split(this.delimiter):type.slice();leafs=searchListenerTree.call(this,null,ns,this.listenerTree,0);}
|
||||
else{if(!this._events[type])return this;handlers=this._events[type];leafs.push({_listeners:handlers});}
|
||||
for(var iLeaf=0;iLeaf<leafs.length;iLeaf++){var leaf=leafs[iLeaf];handlers=leaf._listeners;if(isArray(handlers)){var position=-1;for(var i=0,length=handlers.length;i<length;i++){if(handlers[i]===listener||(handlers[i].listener&&handlers[i].listener===listener)||(handlers[i]._origin&&handlers[i]._origin===listener)){position=i;break;}}
|
||||
if(position<0){continue;}
|
||||
if(this.wildcard){leaf._listeners.splice(position,1);}
|
||||
else{this._events[type].splice(position,1);}
|
||||
if(handlers.length===0){if(this.wildcard){delete leaf._listeners;}
|
||||
else{delete this._events[type];}}
|
||||
return this;}
|
||||
else if(handlers===listener||(handlers.listener&&handlers.listener===listener)||(handlers._origin&&handlers._origin===listener)){if(this.wildcard){delete leaf._listeners;}
|
||||
else{delete this._events[type];}}}
|
||||
return this;};EventEmitter.prototype.offAny=function(fn){var i=0,l=0,fns;if(fn&&this._all&&this._all.length>0){fns=this._all;for(i=0,l=fns.length;i<l;i++){if(fn===fns[i]){fns.splice(i,1);return this;}}}else{this._all=[];}
|
||||
return this;};EventEmitter.prototype.removeListener=EventEmitter.prototype.off;EventEmitter.prototype.removeAllListeners=function(type){if(arguments.length===0){!this._events||init.call(this);return this;}
|
||||
if(this.wildcard){var ns=typeof type==='string'?type.split(this.delimiter):type.slice();var leafs=searchListenerTree.call(this,null,ns,this.listenerTree,0);for(var iLeaf=0;iLeaf<leafs.length;iLeaf++){var leaf=leafs[iLeaf];leaf._listeners=null;}}
|
||||
else{if(!this._events[type])return this;this._events[type]=null;}
|
||||
return this;};EventEmitter.prototype.listeners=function(type){if(this.wildcard){var handlers=[];var ns=typeof type==='string'?type.split(this.delimiter):type.slice();searchListenerTree.call(this,handlers,ns,this.listenerTree,0);return handlers;}
|
||||
this._events||init.call(this);if(!this._events[type])this._events[type]=[];if(!isArray(this._events[type])){this._events[type]=[this._events[type]];}
|
||||
return this._events[type];};EventEmitter.prototype.listenersAny=function(){if(this._all){return this._all;}
|
||||
else{return[];}};if(typeof define==='function'&&define.amd){define('ext/eventemitter2',[],function(){return EventEmitter;});}else if(typeof exports==='object'){exports.EventEmitter2=EventEmitter;}
|
||||
else{window.EventEmitter2=EventEmitter;}}();define('common/binsearch',['require','exports','module'],function(require,exports){'use strict';exports.find=function(list,seekVal,cmpfunc,aLow,aHigh){var low=((aLow===undefined)?0:aLow),high=((aHigh===undefined)?(list.length-1):aHigh),mid,cmpval;while(low<=high){mid=low+Math.floor((high-low)/2);cmpval=cmpfunc(seekVal,list[mid]);if(cmpval<0){high=mid-1;}else if(cmpval>0){low=mid+1;}else{return mid;}}
|
||||
return null;};exports.insert=function(list,seekVal,cmpfunc){if(!list.length){return 0;}
|
||||
var low=0,high=list.length-1,mid,cmpval;while(low<=high){mid=low+Math.floor((high-low)/2);cmpval=cmpfunc(seekVal,list[mid]);if(cmpval<0){high=mid-1;}else if(cmpval>0){low=mid+1;}else{break;}}
|
||||
if(cmpval<0){return mid;}else if(cmpval>0){return mid+1;}else{return mid;}};});define('common/compare',['require','exports','module'],function(require,exports,module){'use strict';module.exports=function(a,b){if(a>b){return 1;}
|
||||
if(a<b){return-1;}
|
||||
return 0;};});define('utils/mout',['require','exports','module'],function(require,exports){'use strict';exports.norm=function norm(val,min,max){if(val===min&&min===max){return 1;}
|
||||
return(val-min)/(max-min);};exports.clamp=function clamp(val,min,max){return val<min?min:(val>max?max:val);};exports.round=function round(value,radix){radix=radix||1;return Math.round(value/radix)*radix;};exports.floor=function floor(val,step){step=Math.abs(step||1);return Math.floor(val/step)*step;};exports.ceil=function ceil(val,step){step=Math.abs(step||1);return Math.ceil(val/step)*step;};exports.lerp=function lerp(ratio,start,end){return start+(end-start)*ratio;};exports.debounce=function debounce(fn,threshold,isAsap){var timeout,result;function debounced(){var args=arguments,context=this;function delayed(){if(!isAsap){result=fn.apply(context,args);}
|
||||
timeout=null;}
|
||||
if(timeout){clearTimeout(timeout);}else if(isAsap){result=fn.apply(context,args);}
|
||||
timeout=setTimeout(delayed,threshold);return result;}
|
||||
debounced.cancel=function(){clearTimeout(timeout);};return debounced;};exports.throttle=function throttle(fn,delay){var context,timeout,result,args,diff,prevCall=0;function delayed(){prevCall=Date.now();timeout=null;result=fn.apply(context,args);}
|
||||
function throttled(){context=this;args=arguments;diff=delay-(Date.now()-prevCall);if(diff<=0){clearTimeout(timeout);delayed();}else if(!timeout){timeout=setTimeout(delayed,diff);}
|
||||
return result;}
|
||||
throttled.cancel=function(){clearTimeout(timeout);};return throttled;};});define('day_observer',['require','exports','module','common/calc','ext/eventemitter2','common/binsearch','common/compare','core','utils/mout'],function(require,exports){'use strict';var Calc=require('common/calc');var EventEmitter2=require('ext/eventemitter2');var binsearch=require('common/binsearch');var compare=require('common/compare');var core=require('core');var daysBetween=Calc.daysBetween;var debounce=require('utils/mout').debounce;var getDayId=Calc.getDayId;var isAllDay=Calc.isAllDay;var spanOfMonth=Calc.spanOfMonth;var DISPATCH_DELAY=25;var MAX_CACHED_MONTHS=5;var busytimes=new Map();var events=new Map();var eventsToBusytimes=new Map();var cache=new Map();var dayQueue=new Set();var cachedSpans=[];var cacheLocked=false;var emitter=exports.emitter=new EventEmitter2();exports.init=function(){var eventStore=core.storeFactory.get('Event');eventStore.on('persist',(id,event)=>cacheEvent(event));eventStore.on('remove',removeEventById);var busytimeStore=core.storeFactory.get('Busytime');busytimeStore.on('persist',(id,busy)=>cacheBusytime(busy));busytimeStore.on('remove',removeBusytimeById);core.syncController.on('syncStart',()=>{cacheLocked=true;});core.syncController.on('syncComplete',()=>{cacheLocked=false;pruneCache();dispatch();});var calendarStore=core.storeFactory.get('Calendar');calendarStore.on('calendarVisibilityChange',(id,calendar)=>{var type=calendar.localDisplayed?'add':'remove';busytimes.forEach((busy,busyId)=>{if(busy.calendarId===id){registerBusytimeChange(busyId,type);}});});core.timeController.on('monthChange',loadMonth);var month=core.timeController.month;if(month){loadMonth(month);}};exports.on=function(date,callback){var dayId=getDayId(date);callback(getDay(dayId,date));emitter.on(dayId,callback);};function getDay(dayId,date){if(cache.has(dayId)){return cache.get(dayId);}
|
||||
var day={dayId:dayId,date:date,amount:0,basic:[],allday:[]};cache.set(dayId,day);return day;}
|
||||
exports.off=function(date,callback){emitter.off(getDayId(date),callback);};exports.removeAllListeners=function(){emitter.removeAllListeners();};exports.findAssociated=function(busytimeId){return queryBusytime(busytimeId).then(busytime=>{return queryEvent(busytime.eventId).then(event=>{return{busytime:busytime,event:event};});});};function queryBusytime(busytimeId){if(busytimes.has(busytimeId)){return Promise.resolve(busytimes.get(busytimeId));}
|
||||
var busytimeStore=core.storeFactory.get('Busytime');return busytimeStore.get(busytimeId);}
|
||||
function queryEvent(eventId){if(events.has(eventId)){return Promise.resolve(events.get(eventId));}
|
||||
var eventStore=core.storeFactory.get('Event');return eventStore.get(eventId);}
|
||||
function cacheBusytime(busy){var{_id,startDate,endDate,eventId}=busy;if(outsideSpans(startDate)&&outsideSpans(endDate)){return;}
|
||||
busytimes.set(_id,busy);eventsToBusytimes.get(eventId).push(_id);registerBusytimeChange(_id);}
|
||||
function removeBusytimeById(id){var busy=busytimes.get(id);if(!busy){return;}
|
||||
var eventId=busy.eventId;var ids=eventsToBusytimes.get(eventId).filter(i=>i!==id);eventsToBusytimes.set(eventId,ids);removeEventIfNoBusytimes(ids,eventId);registerBusytimeChange(id,'remove');busytimes.delete(id);}
|
||||
function cacheEvent(event){var id=event._id;events.set(id,event);if(!eventsToBusytimes.has(id)){eventsToBusytimes.set(id,[]);}}
|
||||
function removeEventById(id){events.delete(id);eventsToBusytimes.delete(id);}
|
||||
function registerBusytimeChange(id,type){var busy=busytimes.get(id);var{startDate,endDate}=busy;var end=new Date(endDate.getTime()-1);var calendarStore=core.storeFactory.get('Calendar');var isRemove=type==='remove'||!calendarStore.shouldDisplayCalendar(busy.calendarId);daysBetween(startDate,end).forEach(date=>{if(outsideSpans(date)){return;}
|
||||
var dayId=getDayId(date);if(isRemove&&!cache.has(dayId)){return;}
|
||||
var day=getDay(dayId,date);day.basic=day.basic.filter(r=>r.busytime._id!==id);day.allday=day.allday.filter(r=>r.busytime._id!==id);if(!isRemove){var group=isAllDay(date,startDate,endDate)?day.allday:day.basic;sortedInsert(group,busy);}
|
||||
day.amount=day.basic.length+day.allday.length;dayQueue.add(dayId);});dispatch();}
|
||||
function sortedInsert(group,busy){var index=binsearch.insert(group,busy.startDate,(date,record)=>{return compare(date,record.busytime.startDate);});var event=events.get(busy.eventId);var calendarStore=core.storeFactory.get('Calendar');group.splice(index,0,{event:event,busytime:busy,color:calendarStore.getColorByCalendarId(event.calendarId)});}
|
||||
var dispatch=debounce(function(){dayQueue.forEach(id=>{dayQueue.delete(id);if(cache.has(id)){emitter.emit(id,cache.get(id));}});},DISPATCH_DELAY);function loadMonth(newMonth){var span=spanOfMonth(newMonth);var toLoad=span;cachedSpans.every(cached=>toLoad=cached.trimOverlap(toLoad));if(!toLoad){return;}
|
||||
cachedSpans.push(span);var busytimeStore=core.storeFactory.get('Busytime');busytimeStore.loadSpan(toLoad,onBusytimeSpanLoad);}
|
||||
function onBusytimeSpanLoad(err,busytimes){if(err){console.error('Error loading Busytimes from TimeSpan:',err.toString());return;}
|
||||
var eventIds=Array.from(new Set(busytimes.map(b=>b.eventId).filter(id=>!events.has(id))));var eventStore=core.storeFactory.get('Event');eventStore.findByIds(eventIds).then(events=>{Object.keys(events).forEach(key=>cacheEvent(events[key]));busytimes.forEach(cacheBusytime);pruneCache();});}
|
||||
function pruneCache(){if(cacheLocked){return;}
|
||||
trimCachedSpans();cache.forEach(removeDayFromCacheIfOutsideSpans);eventsToBusytimes.forEach(removeEventIfNoBusytimes);}
|
||||
function trimCachedSpans(){while(cachedSpans.length>MAX_CACHED_MONTHS){var baseDate=core.timeController.month;var maxDiff=0;var maxDiffIndex=0;cachedSpans.forEach((span,i)=>{var diff=Math.abs(span.start-baseDate);if(diff>maxDiff){maxDiff=diff;maxDiffIndex=i;}});cachedSpans.splice(maxDiffIndex,1);}}
|
||||
function removeDayFromCacheIfOutsideSpans(day,id){if(outsideSpans(day.date)){day.basic.forEach(removeRecordIfOutsideSpans);day.allday.forEach(removeRecordIfOutsideSpans);cache.delete(id);}}
|
||||
function outsideSpans(date){return!cachedSpans.some(timespan=>timespan.contains(date));}
|
||||
function removeRecordIfOutsideSpans(record){removeBusytimeIfOutsideSpans(record.busytime);}
|
||||
function removeBusytimeIfOutsideSpans(busytime){var{_id,startDate,endDate}=busytime;if(outsideSpans(startDate)&&outsideSpans(endDate)){removeBusytimeById(_id);}}
|
||||
function removeEventIfNoBusytimes(ids,eventId){if(!ids.length){removeEventById(eventId);}}});define('common/object',['require','exports','module'],function(require,exports,module){'use strict';exports.filter=function(obj,fn,thisArg){var results=[];exports.forEach(obj,function(key,value){if(fn.call(thisArg,key,value)){results.push(value);}});return results;};exports.forEach=function(obj,fn,thisArg){exports.map(obj,fn,thisArg);};exports.map=function(obj,fn,thisArg){var results=[];Object.keys(obj).forEach((key)=>{var value=obj[key];var result=fn.call(thisArg,key,value);results.push(result);});return results;};exports.values=function(obj){return exports.map(obj,(key,value)=>{return value;});};});define('bridge',['require','exports','module','ext/eventemitter2','ext/co','core','day_observer','common/next_tick','common/object'],function(require,exports){'use strict';var EventEmitter2=require('ext/eventemitter2');var co=require('ext/co');var core=require('core');var dayObserver=require('day_observer');var nextTick=require('common/next_tick');var object=require('common/object');exports.fetchRecord=function(busytimeId){return co(function*(){var record=yield dayObserver.findAssociated(busytimeId);var eventStore=core.storeFactory.get('Event');var owners=yield eventStore.ownersOf(record.event);var provider=core.providerFactory.get(owners.account.providerType);var capabilities=yield provider.eventCapabilities(record.event);record.calendar=owners.calendar;record.account=owners.account;record.capabilities=capabilities;return record;});};exports.observeCalendars=function(){var stream=new FakeClientStream();var calendarStore=core.storeFactory.get('Calendar');var getAllAndWrite=co.wrap(function*(){var calendars=yield calendarStore.all();var data=yield object.map(calendars,co.wrap(function*(id,calendar){var caps;try{var provider=yield calendarStore.providerFor(calendar);caps=provider.calendarCapabilities(calendar);}catch(error){console.error(error);return false;}
|
||||
return{calendar:calendar,capabilities:caps};}));stream.write(data.filter(element=>!!element));});calendarStore.on('add',getAllAndWrite);calendarStore.on('remove',getAllAndWrite);calendarStore.on('update',getAllAndWrite);stream.cancel=function(){calendarStore.off('add',getAllAndWrite);calendarStore.off('remove',getAllAndWrite);calendarStore.off('update',getAllAndWrite);stream._cancel();};nextTick(getAllAndWrite);return stream;};exports.updateCalendar=function(calendar){var calendarStore=core.storeFactory.get('Calendar');return calendarStore.persist(calendar);};exports.createEvent=function(event){return persistEvent(event,'create','canCreate');};exports.updateEvent=function(event){return persistEvent(event,'update','canUpdate');};exports.deleteEvent=function(event){return persistEvent(event,'delete','canDelete');};var persistEvent=co.wrap(function*(event,action,capability){event=event.data||event;try{var eventStore=core.storeFactory.get('Event');var provider=yield eventStore.providerFor(event);var caps=yield provider.eventCapabilities(event);if(!caps[capability]){return Promise.reject(new Error(`Can't ${action} event`));}
|
||||
return provider[action+'Event'](event);}catch(err){console.error(`${action} Error for event "${event._id}" `+`on calendar "${event.calendarId}"`);console.error(err);return Promise.reject(err);}});exports.getSetting=function(id){var settingStore=core.storeFactory.get('Setting');return settingStore.getValue(id);};exports.setSetting=function(id,value){var settingStore=core.storeFactory.get('Setting');return settingStore.set(id,value);};exports.observeSetting=function(id){var stream=new FakeClientStream();var settingStore=core.storeFactory.get('Setting');var writeOnChange=function(value){stream.write(value);};settingStore.on(`${id}Change`,writeOnChange);stream.cancel=function(){settingStore.off(`${id}Change`,writeOnChange);stream._cancel();};exports.getSetting(id).then(writeOnChange);return stream;};exports.getAccount=function(id){var accountStore=core.storeFactory.get('Account');return accountStore.get(id);};exports.deleteAccount=function(id){var accountStore=core.storeFactory.get('Account');return accountStore.remove(id);};exports.createAccount=co.wrap(function*(model){var storeFactory=core.storeFactory;var accountStore=storeFactory.get('Account');var calendarStore=storeFactory.get('Calendar');var[,result]=yield accountStore.verifyAndPersist(model);yield accountStore.sync(result);var calendars=yield calendarStore.remotesByAccount(result._id);for(var key in calendars){core.syncController.calendar(result,calendars[key]);}
|
||||
return result;});exports.observeAccounts=function(){var stream=new FakeClientStream();var accountStore=core.storeFactory.get('Account');var getAllAndWrite=co.wrap(function*(){try{var accounts=yield accountStore.all();var data=object.map(accounts,(id,account)=>{return{account:account,provider:core.providerFactory.get(account.providerType)};});stream.write(data);}catch(err){console.error(`Error fetching accounts: ${err.message}`);}});accountStore.on('add',getAllAndWrite);accountStore.on('remove',getAllAndWrite);accountStore.on('update',getAllAndWrite);stream.cancel=function(){accountStore.off('add',getAllAndWrite);accountStore.off('remove',getAllAndWrite);accountStore.off('update',getAllAndWrite);stream._cancel();};nextTick(getAllAndWrite);return stream;};exports.observeDay=function(date){var stream=new FakeClientStream();var emit=stream.write.bind(stream);stream.cancel=function(){dayObserver.off(date,emit);stream._cancel();};nextTick(()=>dayObserver.on(date,emit));return stream;};exports.availablePresets=function(presetList){var accountStore=core.storeFactory.get('Account');return accountStore.availablePresets(presetList);};function FakeClientStream(){this._emitter=new EventEmitter2();this._enabled=true;}
|
||||
FakeClientStream.prototype.write=function(data){this._enabled&&this._emitter.emit('data',data);};FakeClientStream.prototype.listen=function(callback){this._enabled&&this._emitter.on('data',callback);};FakeClientStream.prototype.unlisten=function(callback){this._enabled&&this._emitter.off('data',callback);};FakeClientStream.prototype._cancel=function(){this._emitter.removeAllListeners();this._enabled=false;};});(function(global){'use strict';const helperCache=new Map();const knownObjects={datetime:{create:function(options){const customOptions=Object.assign({},options);if(options.hour){customOptions.hour12=navigator.mozHour12;}
|
||||
return Intl.DateTimeFormat(navigator.languages,customOptions);},isAffected:function(obj,reason){if(['languagechange','moztimechange'].includes(reason)){return true;}
|
||||
if(reason==='timeformatchange'){return'hour'in obj.options;}
|
||||
return false;}},mozdatetime:{create:function(options){const customOptions=Object.assign({},options);if(options.hour){customOptions.hour12=navigator.mozHour12;}
|
||||
return mozIntl.DateTimeFormat(navigator.languages,customOptions);},isAffected:function(obj,reason){if(['languagechange','moztimechange'].includes(reason)){return true;}
|
||||
if(reason==='timeformatchange'){return'hour'in obj.options;}
|
||||
return false;}},mozduration:{create:function(options){return mozIntl.DurationFormat(navigator.languages,options);},isAffected:function(obj,reason){return reason==='languagechange';}},number:{create:function(options){return Intl.NumberFormat(navigator.languages,options);},isAffected:function(obj,reason){return reason==='languagechange';}},collator:{create:function(options){return Intl.Collator(navigator.languages,options);},isAffected:function(obj,reason){return reason==='languagechange';}},};global.IntlHelper={define:function(name,type,options){if(!knownObjects.hasOwnProperty(type)){throw new Error('Unknown type: '+type);}
|
||||
if(helperCache.has(name)){throw new Error('Intl Object with name "'+name+'" already exists');}
|
||||
helperCache.set(name,{options,type,intlObject:undefined,listeners:new Set()});},get:function(name){if(!helperCache.has(name)){throw new Error('Intl Object with name "'+name+'" is not defined');}
|
||||
const obj=helperCache.get(name);if(!knownObjects.hasOwnProperty(obj.type)){throw new Error('Unknown type: '+obj.type);}
|
||||
if(obj.intlObject===undefined){obj.intlObject=knownObjects[obj.type].create(obj.options);}
|
||||
return obj.intlObject;},observe:function(names,cb){if(typeof names==='string'){names=[names];}
|
||||
for(var name of names){if(!helperCache.has(name)){throw new Error('Intl Object with name "'+name+'" is not defined');}
|
||||
const obj=helperCache.get(name);obj.listeners.add(cb);}},unobserve:function(name,cb){const obj=helperCache.get(name);obj.listeners.delete(cb);},_fireObservers:function(affectedObjects,...args){const affectedCallbacks=new Set();affectedObjects.forEach(obj=>obj.listeners.forEach(cb=>affectedCallbacks.add(cb)));affectedCallbacks.forEach(cb=>{try{cb(...args);}catch(e){console.error('Error in callback: '+e.toString());console.error(e.stack);}});},handleEvent:function(evt){const affectedObjects=resetObjects(evt.type);if(evt.type==='languagechange'&&navigator.mozL10n){waitOnDOMLocalized(()=>global.IntlHelper._fireObservers(affectedObjects));}else{global.IntlHelper._fireObservers(affectedObjects);}},_resetObjectCache:function(){helperCache.clear();}};function waitOnDOMLocalized(cb){const resolver=()=>{document.removeEventListener('DOMRetranslated',resolver);window.removeEventListener('localized',resolver);cb();};document.addEventListener('DOMRetranslated',resolver,false);window.addEventListener('localized',resolver,false);}
|
||||
function resetObjects(reason){const affectedObjects=new Set();helperCache.forEach((obj,name)=>{if(knownObjects[obj.type].isAffected(obj,reason)){obj.intlObject=undefined;affectedObjects.add(obj);}});return affectedObjects;}
|
||||
window.addEventListener('timeformatchange',global.IntlHelper,false);window.addEventListener('languagechange',global.IntlHelper,false);window.addEventListener('moztimechange',global.IntlHelper,false);})(this);define("shared/intl_helper",(function(global){return function(){var ret,fn;return ret||global.IntlHelper;};}(this)));define('date_l10n',['require','exports','module','shared/intl_helper'],function(require,exports){'use strict';var IntlHelper=require('shared/intl_helper');exports.init=function(){window.addEventListener('localized',exports._localizeElements);window.addEventListener('timeformatchange',exports._localizeElements);};exports._localizeElements=function(){var elements=document.querySelectorAll('[data-l10n-date-format]');for(var i=0;i<elements.length;i++){exports._localizeElement(elements[i]);}};exports._localizeElement=function(element){var date=element.dataset.date;if(!date){return;}
|
||||
var format=element.dataset.l10nDateFormat;var formatter=IntlHelper.get(format);var text;if(format==='week-hour-format'){text=formatter.format(new Date(date),{dayperiod:'<span class="ampm" aria-hidden="true">$&</span>',});}else{text=formatter.format(new Date(date));}
|
||||
element.textContent=text;};});define('intl',['require','exports','module','shared/intl_helper'],function(require,exports){'use strict';var IntlHelper=require('shared/intl_helper');exports.init=function init(){IntlHelper.define('months-day-view-header-format','datetime',{weekday:'long',month:'short',day:'numeric'});IntlHelper.define('multi-month-view-header-format','datetime',{month:'short',year:'numeric'});IntlHelper.define('day-view-header-format','datetime',{month:'short',day:'numeric',weekday:'long'});IntlHelper.define('shortTimeFormat','datetime',{hour:'numeric',minute:'numeric'});};});define('message_handler',['require','exports','module','common/responder','common/debug','notification'],function(require,exports,module){'use strict';var Responder=require('common/responder');var debug=require('common/debug')('message_handler');var notification=require('notification');var responder=exports.responder=new Responder();exports.start=function(){if(!('mozSetMessageHandler'in navigator)){debug('mozSetMessageHandler is missing!');return;}
|
||||
debug('Will listen for alarm messages...');navigator.mozSetMessageHandler('alarm',message=>{debug('Received alarm message!');var data=message.data;switch(data.type){case'sync':responder.emitWhenListener('sync');break;default:responder.emitWhenListener('alarm',data);break;}});debug('Will listen for notification messages...');navigator.mozSetMessageHandler('notification',message=>{debug('Received notification message!');if(!message.clicked){return debug('Notification was not clicked?');}
|
||||
var url=message.imageURL.split('?')[1];notification.launch(url);});};});define('controllers/notifications',['require','exports','module','common/calc','ext/co','core','common/debug','message_handler','notification'],function(require,exports){'use strict';var calc=require('common/calc');var co=require('ext/co');var core=require('core');var debug=require('common/debug')('controllers/notifications');var messageHandler=require('message_handler');var notification=require('notification');exports.observe=function(){debug('Will start notifications controller...');messageHandler.responder.on('alarm',exports.onAlarm);};exports.unobserve=function(){messageHandler.responder.off('alarm',exports.onAlarm);};exports.onAlarm=co.wrap(function*(alarm){var storeFactory=core.storeFactory;var alarmStore=storeFactory.get('Alarm');alarm=yield alarmStore.get(alarm._id);debug('Will request cpu wake lock...');var lock=navigator.requestWakeLock('cpu');debug('Received cpu lock. Will issue notification...');try{yield issueNotification(alarm);}catch(err){console.error('[controllers/notifications]',err.toString());}finally{debug('Will release cpu wake lock...');lock.unlock();}
|
||||
alarm.fired=true;yield alarmStore.persist(alarm);});var issueNotification=co.wrap(function*(alarm){if(alarm.fired){return;}
|
||||
var storeFactory=core.storeFactory;var eventStore=storeFactory.get('Event');var busytimeStore=storeFactory.get('Busytime');var trans=core.db.transaction(['busytimes','events']);var[event,busytime]=yield Promise.all([eventStore.get(alarm.eventId,trans),busytimeStore.get(alarm.busytimeId,trans)]);if(!event){throw new Error(`can't find event with ID: ${alarm.eventId}`);}
|
||||
if(!busytime){throw new Error(`can't find busytime with ID: ${alarm.busytimeId}`);}
|
||||
var begins=calc.dateFromTransport(busytime.start);var formatter=mozIntl._gaia.RelativeDate(navigator.languages);var distance=yield formatter.format(begins);var now=new Date();var alarmType=begins>now?'alarm-start-notice':'alarm-started-notice';var titleL10n={id:alarmType,args:{title:event.remote.title,distance:distance}};var bodyL10n={raw:event.remote.description||''};debug('Will send event notification with title',titleL10n,'body:',bodyL10n);return notification.sendNotification(titleL10n,bodyL10n,`/alarm-display/${busytime._id}`,{id:event.remote.id});});});define('controllers/periodic_sync',['require','exports','module','common/responder','core','common/debug','message_handler'],function(require,exports){'use strict';var Responder=require('common/responder');var core=require('core');var debug=require('common/debug')('controllers/periodic_sync');var messageHandler=require('message_handler');var syncAlarm;var prevSyncFrequency;var syncFrequency;var syncing;var scheduling;var events=new Responder();exports.events=events;var accounts;var settings;exports.observe=function(){debug('Will start periodic sync controller...');var storeFactory=core.storeFactory;accounts=storeFactory.get('Account');settings=storeFactory.get('Setting');return Promise.all([settings.getValue('syncAlarm'),settings.getValue('syncFrequency')]).then(values=>{[syncAlarm,syncFrequency]=values;accounts.on('persist',onAccountsChange);accounts.on('remove',onAccountsChange);debug('Will listen for syncFrequencyChange...');settings.on('syncFrequencyChange',exports);messageHandler.responder.on('sync',exports);return scheduleSync();});};exports.unobserve=function(){syncAlarm=null;prevSyncFrequency=null;syncFrequency=null;syncing=null;scheduling=null;accounts.off('persist',onAccountsChange);accounts.off('remove',onAccountsChange);settings.off('syncFrequencyChange',exports);messageHandler.responder.off('sync',exports);};exports.handleEvent=function(event){switch(event.type){case'sync':return onSync();case'syncFrequencyChange':debug('Received syncFrequencyChange!');return onSyncFrequencyChange(event.data[0]);}};function onSync(){return sync().then(scheduleSync);}
|
||||
function onSyncFrequencyChange(value){debug('Sync frequency changed to',value);syncFrequency=value;return maybeScheduleSync();}
|
||||
function onAccountsChange(){debug('Looking up syncable accounts...');return accounts.syncableAccounts().then(syncable=>{if(!syncable||!syncable.length){debug('There are no syncable accounts!');revokePreviousAlarm();events.emit('pause');return;}
|
||||
debug('There are',syncable.length,'syncable accounts');if(!syncAlarmIssued()){debug('The first syncable account was just added.');return scheduleSync();}});}
|
||||
function sync(){if(!syncing){syncing=new Promise((resolve,reject)=>{debug('Will request cpu and wifi wake locks...');var cpuLock=navigator.requestWakeLock('cpu');var wifiLock=navigator.requestWakeLock('wifi');debug('Will start periodic sync...');core.syncController.all(()=>{debug('Sync complete! Will release cpu and wifi wake locks...');cpuLock.unlock();wifiLock.unlock();events.emit('sync');syncing=null;resolve();});});}
|
||||
return syncing;}
|
||||
function scheduleSync(){if(scheduling){return scheduling;}
|
||||
scheduling=accounts.syncableAccounts().then(syncable=>{if(!syncable||!syncable.length){debug('There seem to be no syncable accounts, will defer scheduling...');return Promise.resolve();}
|
||||
debug('Will schedule periodic sync in:',syncFrequency);prevSyncFrequency=syncFrequency;revokePreviousAlarm();return issueSyncAlarm().then(cacheSyncAlarm).then(maybeScheduleSync);}).then(()=>{events.emit('schedule');scheduling=null;}).catch(error=>{debug('Error scheduling sync:',error);console.error(error.toString());scheduling=null;});return scheduling;}
|
||||
function revokePreviousAlarm(){if(!syncAlarmIssued()){debug('No sync alarms issued, nothing to revoke...');return;}
|
||||
debug('Will revoke alarm',syncAlarm.alarmId);var alarms=navigator.mozAlarms;alarms.remove(syncAlarm.alarmId);}
|
||||
function issueSyncAlarm(){if(!prevSyncFrequency){debug('Periodic sync disabled!');return Promise.resolve({alarmId:null,start:null,end:null});}
|
||||
var start=new Date();var end=new Date(start.getTime()+
|
||||
prevSyncFrequency*60*1000);var alarms=navigator.mozAlarms;var request=alarms.add(end,'ignoreTimezone',{type:'sync'});return new Promise((resolve,reject)=>{request.onsuccess=function(){resolve({alarmId:this.result,start:start,end:end});};request.onerror=function(){reject(this.error);};});}
|
||||
function cacheSyncAlarm(alarm){debug('Will save alarm:',alarm);syncAlarm=alarm;return settings.set('syncAlarm',syncAlarm);}
|
||||
function maybeScheduleSync(){if(syncFrequency===prevSyncFrequency){return Promise.resolve();}
|
||||
if(scheduling){return scheduling.then(scheduleSync);}
|
||||
return scheduleSync();}
|
||||
function syncAlarmIssued(){return syncAlarm&&!!syncAlarm.alarmId;}});define('provider/caldav_pull_events',['require','exports','module','common/calc','core','common/debug','ext/uuid'],function(require,exports,module){'use strict';var Calc=require('common/calc');var core=require('core');var debug=require('common/debug')('pull events');var uuid=require('ext/uuid');function PullEvents(stream,options){if(options.calendar){this.calendar=options.calendar;}else{throw new Error('.calendar option must be given');}
|
||||
if(options.account){this.account=options.account;}else{throw new Error('.account option must be given');}
|
||||
stream.on('event',this);stream.on('component',this);stream.on('occurrence',this);stream.on('missingEvents',this);this.icalQueue=[];this.eventQueue=[];this.busytimeQueue=[];this.alarmQueue=[];var storeFactory=core.storeFactory;this._busytimeStore=storeFactory.get('Busytime');this._accountStore=storeFactory.get('Account');this._accountStore.on('remove',this._onRemoveAccount.bind(this));this._aborted=false;this._trans=null;}
|
||||
module.exports=PullEvents;PullEvents.prototype={eventQueue:null,busytimeQueue:null,busytimeIdFromRemote:function(busytime){var eventId=this.eventIdFromRemote(busytime,!busytime.isException);return busytime.start.utc+'-'+
|
||||
busytime.end.utc+'-'+
|
||||
eventId;},eventIdFromRemote:function(event,ignoreException){var id=event.eventId||event.id;if(!ignoreException&&event.recurrenceId){id+='-'+event.recurrenceId.utc;}
|
||||
return this.calendar._id+'-'+id;},formatEvent:function(event){var id=this.eventIdFromRemote(event,true);var result=Object.create(null);result.calendarId=this.calendar._id;result.remote=event;if(event.recurrenceId){result.parentId=id;result._id=this.eventIdFromRemote(event);}else{result._id=id;}
|
||||
return result;},formatBusytime:function(time){var eventId=this.eventIdFromRemote(time,!time.isException);var id=eventId+'-'+uuid.v4();var calendarId=this.calendar._id;time._id=id;time.calendarId=calendarId;time.eventId=eventId;if(time.alarms){var i=0;var len=time.alarms.length;var alarm;for(;i<len;i++){alarm=time.alarms[i];alarm.eventId=eventId;alarm.busytimeId=id;}}
|
||||
return this._busytimeStore.initRecord(time);},handleOccurrenceSync:function(item){var alarms;if('alarms'in item){alarms=item.alarms;delete item.alarms;if(alarms.length){var i=0;var len=alarms.length;var now=Date.now();for(;i<len;i++){var alarm={startDate:{},eventId:item.eventId,busytimeId:item._id};for(var j in item.start){alarm.startDate[j]=item.start[j];}
|
||||
alarm.startDate.utc+=(alarms[i].trigger*1000);var alarmDate=Calc.dateFromTransport(item.end);if(alarmDate.valueOf()<now){continue;}
|
||||
this.alarmQueue.push(alarm);}}}
|
||||
this.busytimeQueue.push(item);},handleComponentSync:function(component){component.eventId=this.eventIdFromRemote(component);component.calendarId=this.calendar._id;if(!component.lastRecurrenceId){delete component.lastRecurrenceId;}
|
||||
this.icalQueue.push(component);},handleEventSync:function(event){var exceptions=event.remote.exceptions;delete event.remote.exceptions;var id=event._id;this._busytimeStore.removeEvent(id);this.eventQueue.push(event);var component=event.remote.icalComponent;delete event.remote.icalComponent;if(!event.remote.recurrenceId){this.icalQueue.push({data:component,eventId:event._id});}
|
||||
if(exceptions){for(var i=0;i<exceptions.length;i++){this.handleEventSync(this.formatEvent(exceptions[i]));}}},_onRemoveAccount:function(id){if(id===this.account._id){this.abort();}},abort:function(){if(this._aborted){return;}
|
||||
this._aborted=true;if(this._trans){this._trans.abort();}},handleEvent:function(event){if(this._aborted){return;}
|
||||
var data=event.data;switch(event.type){case'missingEvents':this.removeList=data[0];break;case'occurrence':var occur=this.formatBusytime(data[0]);this.handleOccurrenceSync(occur);break;case'component':this.handleComponentSync(data[0]);break;case'event':var e=this.formatEvent(data[0]);this.handleEventSync(e);break;}},commit:function(trans,callback){var storeFactory=core.storeFactory;var eventStore=storeFactory.get('Event');var icalComponentStore=storeFactory.get('IcalComponent');var busytimeStore=storeFactory.get('Busytime');var alarmStore=storeFactory.get('Alarm');if(typeof(trans)==='function'){callback=trans;trans=core.db.transaction(['calendars','events','busytimes','alarms','icalComponents'],'readwrite');}
|
||||
if(this._aborted){return callback&&callback(null);}
|
||||
this._trans=trans;var self=this;this.eventQueue.forEach(function(event){debug('add event',event);eventStore.persist(event,trans);});this.icalQueue.forEach(function(ical){debug('add component',ical);icalComponentStore.persist(ical,trans);});this.busytimeQueue.forEach(function(busy){debug('add busytime',busy);busytimeStore.persist(busy,trans);});this.alarmQueue.forEach(function(alarm){debug('add alarm',alarm);alarmStore.persist(alarm,trans);});if(this.removeList){this.removeList.forEach(function(id){eventStore.remove(id,trans);});}
|
||||
function handleError(e){if(e&&e.type!=='abort'){console.error('Error persisting sync results',e);}
|
||||
if(e&&e.preventDefault){e.preventDefault();}
|
||||
self._trans=null;callback&&callback(e);}
|
||||
trans.addEventListener('error',handleError);trans.addEventListener('abort',handleError);trans.addEventListener('complete',function(){self._trans=null;callback&&callback(null);});return trans;}};});define('provider/caldav',['require','exports','module','./abstract','common/error','common/calc','./caldav_pull_events','common/error','common/error','./local','common/error','core','common/is_offline','event_mutations','common/next_tick'],function(require,exports,module){'use strict';var Abstract=require('./abstract');var Authentication=require('common/error').Authentication;var Calc=require('common/calc');var CaldavPullEvents=require('./caldav_pull_events');var CalendarError=require('common/error');var InvalidServer=require('common/error').InvalidServer;var Local=require('./local');var ServerFailure=require('common/error').ServerFailure;var core=require('core');var isOffline=require('common/is_offline');var mutations=require('event_mutations');var nextTick=require('common/next_tick');var CALDAV_ERROR_MAP={'caldav-authentication':Authentication,'caldav-invalid-entrypoint':InvalidServer,'caldav-server-failure':ServerFailure};function mapError(error,detail){console.error('Error with name:',error.name);var calError=CALDAV_ERROR_MAP[error.name];if(!calError){calError=new CalendarError(error.name,detail);}else{calError=new calError(detail);}
|
||||
return calError;}
|
||||
function CaldavProvider(){Abstract.apply(this,arguments);var storeFactory=core.storeFactory;this.service=core.serviceController;this.accounts=storeFactory.get('Account');this.busytimes=storeFactory.get('Busytime');this.events=storeFactory.get('Event');this.icalComponents=storeFactory.get('IcalComponent');}
|
||||
module.exports=CaldavProvider;CaldavProvider.prototype={__proto__:Abstract.prototype,role:'caldav',useUrl:true,useCredentials:true,canSync:true,canExpandRecurringEvents:true,isOffline:isOffline,daysToSyncInPast:30,canCreateEvent:true,canUpdateEvent:true,canDeleteEvent:true,hasAccountSettings:true,_handleServiceError:function(rawErr,detail){var calendarErr=mapError(rawErr,detail);if(calendarErr instanceof Authentication||calendarErr instanceof InvalidServer){if(detail.account){if(detail.account._id){this.accounts.markWithError(detail.account,calendarErr);}}else{console.error('Permanent server error without an account!');}}
|
||||
return calendarErr;},calendarCapabilities:function(calendar){var remote=calendar.remote;if(!remote.privilegeSet){return{canUpdateEvent:true,canDeleteEvent:true,canCreateEvent:true};}
|
||||
var privilegeSet=remote.privilegeSet;var canWriteConent=privilegeSet.indexOf('write-content')!==-1;return{canUpdateEvent:canWriteConent,canCreateEvent:canWriteConent,canDeleteEvent:privilegeSet.indexOf('unbind')!==-1};},eventCapabilities:function(event,callback){if(event.remote.isRecurring){nextTick(function(){callback(null,{canUpdate:false,canDelete:false,canCreate:false});});}else{var calendarStore=core.storeFactory.get('Calendar');calendarStore.get(event.calendarId,function(err,calendar){if(err){return callback(err);}
|
||||
var caps=this.calendarCapabilities(calendar);callback(null,{canCreate:caps.canCreateEvent,canUpdate:caps.canUpdateEvent,canDelete:caps.canDeleteEvent});}.bind(this));}},getAccount:function(account,callback){if(this.bailWhenOffline(callback)){return;}
|
||||
var self=this;this.service.request('caldav','getAccount',account,function(err,data){if(err){return callback(self._handleServiceError(err,{account:account}));}
|
||||
callback(null,data);});},formatRemoteCalendar:function(calendar){if(!calendar.color){calendar.color=this.defaultColor;}
|
||||
return calendar;},findCalendars:function(account,callback){if(this.bailWhenOffline(callback)){return;}
|
||||
var self=this;function formatCalendars(err,data){if(err){return callback(self._handleServiceError(err,{account:account}));}
|
||||
if(data){for(var key in data){data[key]=self.formatRemoteCalendar(data[key]);}}
|
||||
callback(err,data);}
|
||||
this.service.request('caldav','findCalendars',account.toJSON(),formatCalendars);},_syncEvents:function(account,calendar,cached,callback){var startDate;if(!calendar.firstEventSyncDate){startDate=Calc.createDay(new Date());calendar.firstEventSyncDate=new Date(startDate.valueOf());}else{startDate=new Date(calendar.firstEventSyncDate.valueOf());}
|
||||
startDate.setDate(startDate.getDate()-this.daysToSyncInPast);var options={startDate:startDate,cached:cached};var stream=this.service.stream('caldav','streamEvents',account.toJSON(),calendar.remote,options);var pull=new CaldavPullEvents(stream,{account:account,calendar:calendar});var calendarStore=core.storeFactory.get('Calendar');var syncStart=new Date();var self=this;stream.request(function(err){if(err){return callback(self._handleServiceError(err,{account:account,calendar:calendar}));}
|
||||
var trans=pull.commit(function(commitErr){if(commitErr){callback(err);return;}
|
||||
callback(null);});calendar.error=undefined;calendar.lastEventSyncToken=calendar.remote.syncToken;calendar.lastEventSyncDate=syncStart;calendarStore.persist(calendar,trans);});return pull;},_cachedEventsFor:function(calendar,callback){var store=core.storeFactory.get('Event');store.eventsForCalendar(calendar._id,function(err,results){if(err){callback(err);return;}
|
||||
var list=Object.create(null);var i=0;var len=results.length;var item;for(;i<len;i++){item=results[i];list[item.remote.url]={syncToken:item.remote.syncToken,id:item._id};}
|
||||
callback(null,list);});},syncEvents:function(account,calendar,callback){var self=this;if(this.bailWhenOffline(callback)){return;}
|
||||
if(!calendar._id){throw new Error('calendar must be assigned an _id');}
|
||||
if((calendar.lastEventSyncToken&&calendar.lastEventSyncToken===calendar.remote.syncToken)){return nextTick(callback);}
|
||||
this._cachedEventsFor(calendar,function(err,results){if(err){callback(err);return;}
|
||||
self._syncEvents(account,calendar,results,callback);});},ensureRecurrencesExpanded:function(maxDate,callback){var self=this;this.icalComponents.findRecurrencesBefore(maxDate,function(err,results){if(err){callback(err);return;}
|
||||
if(!results.length){callback(null,false);return;}
|
||||
var groups=Object.create(null);results.forEach(function(comp){var calendarId=comp.calendarId;if(!(calendarId in groups)){groups[calendarId]=[];}
|
||||
groups[calendarId].push(comp);});var pullGroups=[];var pending=0;var options={maxDate:Calc.dateToTransport(maxDate)};function next(err,pull){pullGroups.push(pull);if(!(--pending)){var trans=core.db.transaction(['icalComponents','alarms','busytimes'],'readwrite');trans.oncomplete=function(){callback(null,true);};trans.onerror=function(event){callback(event.result.error.name);};pullGroups.forEach(function(pull){pull.commit(trans);});}}
|
||||
for(var calendarId in groups){pending++;self._expandComponents(calendarId,groups[calendarId],options,next);}});},_expandComponents:function(calendarId,comps,options,callback){var calStore=core.storeFactory.get('Calendar');calStore.ownersOf(calendarId,function(err,owners){if(err){return callback(err);}
|
||||
var calendar=owners.calendar;var account=owners.account;var stream=this.service.stream('caldav','expandComponents',comps,options);var pull=new CaldavPullEvents(stream,{account:account,calendar:calendar,stores:['busytimes','alarms','icalComponents']});stream.request(function(err){if(err){callback(err);return;}
|
||||
callback(null,pull);});}.bind(this));},createEvent:function(event,busytime,callback){if(typeof(busytime)==='function'){callback=busytime;busytime=null;}
|
||||
if(this.bailWhenOffline(callback)){return;}
|
||||
this.events.ownersOf(event,fetchOwners);var self=this;var calendar;var account;function fetchOwners(err,owners){calendar=owners.calendar;account=owners.account;self.service.request('caldav','createEvent',account,calendar.remote,event.remote,handleRequest);}
|
||||
function handleRequest(err,remote){if(err){return callback(self._handleServiceError(err,{account:account,calendar:calendar}));}
|
||||
var event={_id:calendar._id+'-'+remote.id,calendarId:calendar._id};var component={eventId:event._id,ical:remote.icalComponent};delete remote.icalComponent;event.remote=remote;var create=mutations.create({event:event,icalComponent:component});create.commit(function(err){if(err){callback(err);return;}
|
||||
callback(null,create.busytime,create.event);});}},updateEvent:function(event,busytime,callback){if(typeof(busytime)==='function'){callback=busytime;busytime=null;}
|
||||
if(this.bailWhenOffline(callback)){return;}
|
||||
this.events.ownersOf(event,fetchOwners);var self=this;var calendar;var account;function fetchOwners(err,owners){calendar=owners.calendar;account=owners.account;self.icalComponents.get(event._id,fetchComponent);}
|
||||
function fetchComponent(err,ical){if(err){callback(err);return;}
|
||||
var details={event:event.remote,icalComponent:ical.ical};self.service.request('caldav','updateEvent',account,calendar.remote,details,handleUpdate);}
|
||||
function handleUpdate(err,remote){if(err){callback(self._handleServiceError(err,{account:account,calendar:calendar}));return;}
|
||||
var component={eventId:event._id,ical:remote.icalComponent};delete remote.icalComponent;event.remote=remote;var update=mutations.update({event:event,icalComponent:component});update.commit(function(err){if(err){callback(err);return;}
|
||||
callback(null,update.busytime,update.event);});}},deleteEvent:function(event,busytime,callback){if(typeof(busytime)==='function'){callback=busytime;busytime=null;}
|
||||
if(this.bailWhenOffline(callback)){return;}
|
||||
this.events.ownersOf(event,fetchOwners);var calendar;var account;var self=this;function fetchOwners(err,owners){calendar=owners.calendar;account=owners.account;self.service.request('caldav','deleteEvent',account,calendar.remote,event.remote,handleRequest);}
|
||||
function handleRequest(err){if(err){callback(self._handleServiceError(err,{account:account,calendar:calendar}));return;}
|
||||
Local.prototype.deleteEvent.call(self,event,busytime,callback);}},bailWhenOffline:function(callback){if(!this.offlineMessage&&'mozL10n'in window.navigator){this.offlineMessage=window.navigator.mozL10n.get('error-offline');}
|
||||
var ret=this.isOffline()&&callback;if(ret){var error=new Error();error.name='offline';error.message=this.offlineMessage;callback(error);}
|
||||
return ret;}};});define('provider/factory',['require','exports','module','common/debug','./caldav','./local'],function(require,exports){'use strict';var debug=require('common/debug')('provider/factory');var ctors={Caldav:require('./caldav'),Local:require('./local')};var providers=exports.providers=Object.create(null);exports.get=function(name){if(!providers[name]){try{debug(`Initializing provider ${name}`);providers[name]=new ctors[name]();}catch(e){console.error('Error',e.name,e.message);console.error('Failed to initialize provider',name,e.stack);}}
|
||||
return providers[name];};});define('store/abstract',['require','exports','module','common/responder','core','common/promise','common/next_tick'],function(require,exports,module){'use strict';var Responder=require('common/responder');var core=require('core');var denodeifyAll=require('common/promise').denodeifyAll;var nextTick=require('common/next_tick');function Abstract(){this._cached=Object.create(null);Responder.call(this);denodeifyAll(this,['persist','all','_allCached','removeByIndex','get','remove','count']);}
|
||||
module.exports=Abstract;Abstract.prototype={__proto__:Responder.prototype,_store:null,_dependentStores:null,_createModel:function(object,id){if(typeof(id)!=='undefined'){object._id=id;}
|
||||
return object;},_addToCache:function(object){this._cached[object._id]=object;},_clearCache:function(){this._cached=Object.create(null);},_removeFromCache:function(id){if(id in this._cached){delete this._cached[id];}},_transactionCallback:function(trans,callback){if(callback){trans.addEventListener('error',function(e){callback(e);});trans.addEventListener('complete',function(){callback(null);});}},persist:function(object,trans,callback){if(typeof(trans)==='function'){callback=trans;trans=undefined;}
|
||||
if(!trans){trans=core.db.transaction(this._dependentStores||this._store,'readwrite');}
|
||||
var self=this;var store=trans.objectStore(this._store);var data=this._objectData(object);var id;var model;var putReq;var reqType=this._detectPersistType(object);trans.addEventListener('error',function(event){if(callback){callback(event);}});if(reqType==='update'){putReq=store.put(data);}else{this._assignId(data);putReq=store.add(data);this._addDependents(object,trans);}
|
||||
if(data._id){id=data._id;model=self._createModel(object,id);self._addToCache(model);}
|
||||
trans.addEventListener('complete',function(data){if(!model){id=putReq.result;model=self._createModel(object,id);self._addToCache(model);}
|
||||
if(callback){callback(null,id,model);}
|
||||
self.emit(reqType,id,model);self.emit('persist',id,model);});},_allCached:function(callback){var list=this._cached;nextTick(function(){callback(null,list);});},all:function(callback){if(this._allCallbacks){this._allCallbacks.push(callback);return;}
|
||||
this._allCallbacks=[callback];var self=this;var trans=core.db.transaction(this._store);var store=trans.objectStore(this._store);function process(data){return self._addToCache(self._createModel(data));}
|
||||
function fireQueue(err,value){var callback;while((callback=self._allCallbacks.shift())){callback(err,value);}}
|
||||
store.mozGetAll().onsuccess=function(event){event.target.result.forEach(process);};trans.onerror=function(event){fireQueue(event.target.error.name);};trans.oncomplete=function(){fireQueue(null,self._cached);self.all=self._allCached;};},_addDependents:function(){},_removeDependents:function(trans){},_detectPersistType:function(object){return('_id'in object)?'update':'add';},_parseId:function(id){return id;},_assignId:function(obj){},removeByIndex:function(indexName,indexValue,trans,callback){var self=this;if(typeof(trans)==='function'){callback=trans;trans=undefined;}
|
||||
if(!trans){trans=core.db.transaction(this._dependentStores||this._store,'readwrite');}
|
||||
if(callback){trans.addEventListener('complete',function(){callback(null);});trans.addEventListener('error',function(event){callback(event);});}
|
||||
var index=trans.objectStore(this._store).index(indexName);var req=index.openCursor(IDBKeyRange.only(indexValue));req.onsuccess=function(event){var cursor=event.target.result;if(cursor){self._removeDependents(cursor.primaryKey,trans);self._removeFromCache(cursor.primaryKey);cursor.delete();cursor.continue();}};return req;},get:function(id,trans,callback){var self=this;if(typeof(trans)==='function'){callback=trans;trans=null;}
|
||||
if(!trans){trans=core.db.transaction(this._store);}
|
||||
var store=trans.objectStore(this._store);var req=store.get(this._parseId(id));req.onsuccess=function(){var model;if(req.result){model=self._createModel(req.result);}
|
||||
callback(null,model);};req.onerror=function(event){callback(event);};},remove:function(id,trans,callback){if(typeof(trans)==='function'){callback=trans;trans=undefined;}
|
||||
if(!trans){trans=core.db.transaction(this._dependentStores||this._store,'readwrite');}
|
||||
var self=this;var store=trans.objectStore(this._store);id=this._parseId(id);store.delete(id);this._removeDependents(id,trans);self.emit('preRemove',id);trans.addEventListener('error',function(event){if(callback){callback(event);}});trans.addEventListener('complete',function(){if(callback){callback(null,id);}
|
||||
self.emit('remove',id);self._removeFromCache(id);});},count:function(callback){var trans=core.db.transaction(this._store);var store=trans.objectStore(this._store);var req=store.count();req.onsuccess=function(){callback(null,req.result);};req.onerror=function(e){callback(e);};},_objectData:function(data){if('toJSON'in data){return data.toJSON();}
|
||||
return data;}};});define('common/extend',['require','exports','module'],function(require,exports,module){'use strict';module.exports=function(target,input){for(var key in input){if(hasOwnProperty.call(input,key)){target[key]=input[key];}}
|
||||
return target;};});define('store/account',['require','exports','module','models/account','./abstract','core','common/debug','common/promise','common/extend','common/next_tick','common/probably_parse_int'],function(require,exports,module){'use strict';var AccountModel=require('models/account');var Abstract=require('./abstract');var core=require('core');var debug=require('common/debug')('store/account');var denodeifyAll=require('common/promise').denodeifyAll;var extend=require('common/extend');var nextTick=require('common/next_tick');var probablyParseInt=require('common/probably_parse_int');function Account(){Abstract.apply(this,arguments);denodeifyAll(this,['verifyAndPersist','sync','markWithError','syncableAccounts','availablePresets']);}
|
||||
module.exports=Account;Account.prototype={__proto__:Abstract.prototype,_store:'accounts',_parseId:probablyParseInt,_validateModel:function(model,callback){this.all(function(err,allAccounts){if(err){callback(err);return;}
|
||||
for(var index in allAccounts){if(allAccounts[index].user===model.user&&allAccounts[index].fullUrl===model.fullUrl&&allAccounts[index]._id!==model._id){var dupErr=new Error('Cannot add two accounts with the same url / entry point');dupErr.name='account-exist';callback(dupErr);return;}}
|
||||
callback();});},verifyAndPersist:function(model,callback){var self=this;var provider=core.providerFactory.get(model.providerType);provider.getAccount(model.toJSON(),function(err,data){if(err){callback(err);return;}
|
||||
model.error=undefined;model.calendarHome=data.calendarHome;extend(model,data);self._validateModel(model,function(err){if(err){return callback(err);}
|
||||
self.persist(model,callback);});});},_dependentStores:['accounts','calendars','events','busytimes','alarms','icalComponents'],_removeDependents:function(id,trans){var store=core.storeFactory.get('Calendar');store.remotesByAccount(id,trans,function(err,related){if(err){return console.error('Error removing deps for account: ',id);}
|
||||
var key;for(key in related){store.remove(related[key]._id,trans);}});},sync:function(account,callback){var self=this;var provider=core.providerFactory.get(account.providerType);var calendarStore=core.storeFactory.get('Calendar');var persist=[];var calendars;var originalIds;function fetchExistingCalendars(err,results){if(err){return callback(err);}
|
||||
calendars=results;originalIds=Object.keys(calendars);provider.findCalendars(account,persistCalendars);}
|
||||
function persistCalendars(err,remoteCals){var key;if(err){callback(err);return;}
|
||||
for(key in remoteCals){if(remoteCals.hasOwnProperty(key)){var cal=remoteCals[key];var idx=originalIds.indexOf(key);if(idx!==-1){originalIds.splice(idx,1);var original=calendars[key];original.remote=cal;original.error=undefined;persist.push(original);}else{persist.push(calendarStore._createModel({remote:new Object(cal),accountId:account._id}));}}}
|
||||
if(persist.length||originalIds.length){var trans=core.db.transaction(self._dependentStores,'readwrite');originalIds.forEach(function(id){calendarStore.remove(calendars[id]._id,trans);});persist.forEach(function(object){calendarStore.persist(object,trans);});trans.addEventListener('error',function(err){callback(err);});trans.addEventListener('complete',function(){callback(null);});}else{callback(null);}}
|
||||
calendarStore.remotesByAccount(account._id,fetchExistingCalendars);},_createModel:function(obj,id){if(!(obj instanceof AccountModel)){obj=new AccountModel(obj);}
|
||||
if(typeof(id)!=='undefined'){obj._id=id;}
|
||||
return obj;},markWithError:function(account,error,trans,callback){if(typeof(trans)==='function'){callback=trans;trans=null;}
|
||||
if(!account._id){throw new Error('given account must be persisted');}
|
||||
if(!account.error){account.error={name:error.name,date:new Date(),count:0};}
|
||||
account.error.count++;var calendarStore=core.storeFactory.get('Calendar');var self=this;function fetchedCalendars(err,calendars){if(!trans){trans=core.db.transaction(self._dependentStores,'readwrite');}
|
||||
if(err){console.error('Cannot fetch all calendars',err);return self.persist(account,trans,callback);}
|
||||
for(var id in calendars){calendarStore.markWithError(calendars[id],error,trans);}
|
||||
self.persist(account,trans);self._transactionCallback(trans,callback);}
|
||||
calendarStore.remotesByAccount(account._id,fetchedCalendars);},syncableAccounts:function(callback){debug('Will find syncable accounts...');this.all((err,list)=>{if(err){return callback(err);}
|
||||
var results=[];for(var key in list){var account=list[key];var provider=core.providerFactory.get(account.providerType);if(provider.canSync){results.push(account);}}
|
||||
callback(null,results);});},availablePresets:function(presetList,callback){var results=[];var singleUse={};var hasSingleUses=false;for(var preset in presetList){if(presetList[preset].singleUse){hasSingleUses=true;singleUse[preset]=true;}else{results.push(preset);}}
|
||||
if(!hasSingleUses){return nextTick(function(){callback(null,results);});}
|
||||
this.all(function(err,list){if(err){callback(err);return;}
|
||||
for(var id in list){var preset=list[id].preset;if(singleUse[preset]){delete singleUse[preset];}}
|
||||
callback(null,results.concat(Object.keys(singleUse)));});}};});define('common/create_dom_promise',['require','exports','module'],function(require,exports,module){'use strict';module.exports=function createDOMPromise(request){return new Promise((resolve,reject)=>{request.onsuccess=resolve;request.onerror=reject;});};});define('store/alarm',['require','exports','module','./abstract','common/calc','common/create_dom_promise','core','common/debug','common/promise','common/object'],function(require,exports,module){'use strict';var Abstract=require('./abstract');var Calc=require('common/calc');var createDOMPromise=require('common/create_dom_promise');var core=require('core');var debug=require('common/debug')('store/alarm');var denodeifyAll=require('common/promise').denodeifyAll;var object=require('common/object');function Alarm(){Abstract.apply(this,arguments);this._processQueue=this._processQueue.bind(this);denodeifyAll(this,['findAllByBusytimeId','workQueue']);}
|
||||
module.exports=Alarm;Alarm.prototype={__proto__:Abstract.prototype,_store:'alarms',_dependentStores:['alarms'],_alarmAddThresholdHours:48,_addToCache:function(){},_removeFromCache:function(){},autoQueue:false,_processQueue:function(){this.workQueue();},_objectData:function(object){var data=Abstract.prototype._objectData.call(this,object);if(data.startDate){data.trigger=data.startDate;}
|
||||
return data;},_addDependents:function(obj,trans){if(!this.autoQueue){return;}
|
||||
trans.addEventListener('complete',this._processQueue);},_moveAlarms:function(now,requiresAlarm,callback){var time=Calc.dateToTransport(now);var utc=time.utc;var minimum=utc+(this._alarmAddThresholdHours*Calc.HOUR);var request=core.db.transaction('alarms','readwrite').objectStore('alarms').index('trigger').openCursor();request.onerror=function(){callback(new Error('Alarm cursor failed to open.'));};var past=[];var future=[];request.onsuccess=function(event){var cursor=event.target.result;if(!cursor||(cursor.key>=minimum&&(!requiresAlarm||future.length))){return dispatchAlarms(past,future).then(callback).catch(error=>debug('Error dispatching alarms:',error));}
|
||||
var record=cursor.value;var date=Calc.dateFromTransport(record.trigger);var bucket=date<Date.now()?past:future;bucket.push(record);record.triggered=record.trigger;delete record.trigger;cursor.update(record);cursor.continue();};},findAllByBusytimeId:function(busytimeId,trans,callback){if(typeof(trans)==='function'){callback=trans;trans=null;}
|
||||
if(!trans){trans=core.db.transaction(this._dependentStores);}
|
||||
var store=trans.objectStore(this._store);var index=store.index('busytimeId');var key=IDBKeyRange.only(busytimeId);index.mozGetAll(key).onsuccess=function(e){callback(null,e.target.result);};},workQueue:function(now,callback){if(typeof(now)==='function'){callback=now;now=null;}
|
||||
now=now||new Date();var alarms=navigator.mozAlarms;if(!alarms){if(callback){callback(null);}
|
||||
return;}
|
||||
var self=this;var requiresAlarm=false;var req=alarms.getAll();req.onsuccess=function(e){var data=e.target.result;var len=data.length;var mozAlarm;requiresAlarm=true;for(var i=0;i<len;i++){mozAlarm=data[i].data;if(mozAlarm&&'eventId'in mozAlarm&&'trigger'in mozAlarm){requiresAlarm=false;break;}}
|
||||
callback=callback||function(){};self._moveAlarms(now,requiresAlarm,callback);};req.onerror=function(){var msg='failed to get alarms';console.error('CALENDAR:',msg);if(callback){callback(new Error(msg));}};}};function dispatchAlarms(past,future){var eventToAlarm={};past.forEach(alarm=>{var event=alarm.eventId;if(!event||event in eventToAlarm){return;}
|
||||
eventToAlarm[event]=alarm;});object.forEach(eventToAlarm,(event,alarm)=>{core.notificationsController.onAlarm(alarm);});var alarms=navigator.mozAlarms;return Promise.all(future.map(alarm=>{var timezone=alarm.triggered.tzid===Calc.FLOATING?'ignoreTimezone':'honorTimezone';return createDOMPromise(alarms.add(Calc.dateFromTransport(alarm.triggered),timezone,alarm));}));}});define('store/busytime',['require','exports','module','./abstract','common/calc','common/binsearch','core','common/compare','common/promise'],function(require,exports,module){'use strict';var Abstract=require('./abstract');var Calc=require('common/calc');var binsearch=require('common/binsearch');var core=require('core');var compare=require('common/compare');var denodeifyAll=require('common/promise').denodeifyAll;function Busytime(){Abstract.apply(this,arguments);this._setupCache();denodeifyAll(this,['removeEvent','loadSpan']);}
|
||||
module.exports=Busytime;Busytime.prototype={__proto__:Abstract.prototype,_store:'busytimes',_dependentStores:['alarms','busytimes'],_setupCache:function(){this._byEventId=Object.create(null);},_createModel:function(input,id){return this.initRecord(input,id);},initRecord:function(input,id){var _super=Abstract.prototype._createModel;var model=_super.apply(this,arguments);model.startDate=Calc.dateFromTransport(model.start);model.endDate=Calc.dateFromTransport(model.end);return model;},_removeDependents:function(id,trans){core.storeFactory.get('Alarm').removeByIndex('busytimeId',id,trans);},removeEvent:function(id,trans,callback){if(typeof(trans)==='function'){callback=trans;trans=undefined;}
|
||||
if(typeof(trans)==='undefined'){trans=core.db.transaction(this._dependentStores,'readwrite');}
|
||||
var req=this.removeByIndex('eventId',id,trans);var success=req.onsuccess;var self=this;req.onsuccess=function(e){var cursor=e.target.result;if(cursor){var id=cursor.primaryKey;self.emit('remove',id);}
|
||||
success(e);};this._transactionCallback(trans,callback);},_startCompare:function(aObj,bObj){var a=aObj.start.utc;var b=bObj.start.utc;return compare(a,b);},loadSpan:function(span,callback){var trans=core.db.transaction(this._store);var store=trans.objectStore(this._store);var startPoint=Calc.dateToTransport(new Date(span.start));var endPoint=Calc.dateToTransport(new Date(span.end));var keyRange=IDBKeyRange.lowerBound(startPoint.utc);var index=store.index('end');var self=this;index.mozGetAll(keyRange).onsuccess=function(e){var data=e.target.result;data=data.sort(self._startCompare);var idx=binsearch.insert(data,{start:{utc:endPoint.utc+1}},self._startCompare);data=data.slice(0,idx);if(callback){callback(null,data.map(function(item){return self.initRecord(item);}));}};},_addToCache:function(){},_removeFromCache:function(){}};});define('models/calendar',['require','exports','module'],function(require,exports,module){'use strict';function Cal(options){if(typeof(options)==='undefined'){options={};}
|
||||
this.remote={};for(var key in options){if(options.hasOwnProperty(key)){this[key]=options[key];}}}
|
||||
module.exports=Cal;Cal.prototype={remote:null,firstEventSyncDate:null,lastEventSyncToken:'',lastEventSyncDate:'',localDisplayed:true,accountId:'',updateRemote:function(provider){var data=provider;if('toJSON'in provider){data=provider.toJSON();}
|
||||
this.remote=data;},eventSyncNeeded:function(){var local=this.lastEventSyncToken;var remote=this.remote.syncToken;return local!=remote;},set name(name){this.remote.name=name;return this.remote.name;},set color(color){this.remote.color=color;return this.remote.color;},set description(description){this.remote.description=description;return this.remote.description;},get name(){return this.remote.name;},get color(){var color=this.remote.color;if(color){if(color.substr(0,1)==='#'){return color.substr(0,7);}}
|
||||
return this.remote.color;},get description(){return this.remote.description;},toJSON:function(){var result={error:this.error,remote:this.remote,accountId:this.accountId,localDisplayed:this.localDisplayed,lastEventSyncDate:this.lastEventSyncDate,lastEventSyncToken:this.lastEventSyncToken,firstEventSyncDate:this.firstEventSyncDate};if(this._id||this._id===0){result._id=this._id;}
|
||||
return result;}};});define('store/calendar',['require','exports','module','./abstract','models/calendar','core','common/promise','common/constants','common/probably_parse_int'],function(require,exports,module){'use strict';var Abstract=require('./abstract');var CalendarModel=require('models/calendar');var core=require('core');var denodeifyAll=require('common/promise').denodeifyAll;var localCalendarId=require('common/constants').localCalendarId;var probablyParseInt=require('common/probably_parse_int');function Store(){Abstract.apply(this,arguments);this._usedColors=[];denodeifyAll(this,['markWithError','remotesByAccount','sync','providerFor','ownersOf']);}
|
||||
module.exports=Store;Store.REMOTE_COLORS=['#00aacc','#bad600','#df4784','#f9bc17','#0766b7','#76a408','#33a185'];Store.LOCAL_COLOR='#f97c17',Store.capabilities={createEvent:'canCreateEvent',updateEvent:'canUpdateEvent',deleteEvent:'canDeleteEvent'};Store.prototype={__proto__:Abstract.prototype,_store:'calendars',_dependentStores:['calendars','events','busytimes','alarms','icalComponents'],_parseId:probablyParseInt,_createModel:function(obj,id){if(!(obj instanceof CalendarModel)){obj=new CalendarModel(obj);}
|
||||
if(typeof(id)!=='undefined'){obj._id=id;}
|
||||
return obj;},_removeDependents:function(id,trans){var store=core.storeFactory.get('Event');store.removeByIndex('calendarId',id,trans);},markWithError:function(calendar,error,trans,callback){if(typeof(trans)==='function'){callback=trans;trans=null;}
|
||||
if(!calendar._id){throw new Error('given calendar must be persisted.');}
|
||||
calendar.error={name:error.name,date:new Date()};this.persist(calendar,trans,callback);},persist:function(calendar,trans,callback){if(typeof(trans)==='function'){callback=trans;trans=undefined;}
|
||||
this._updateCalendarColor(calendar);var cb=callback;var cached=this._cached[calendar._id];if(cached&&cached.localDisplayed!==calendar.localDisplayed){cb=function(err,id,model){this.emit('calendarVisibilityChange',id,model);callback(err,id,model);}.bind(this);}
|
||||
Abstract.prototype.persist.call(this,calendar,trans,cb);},remove:function(id,trans,callback){this._removeCalendarColorFromCache(id);Abstract.prototype.remove.apply(this,arguments);},_clearCache:function(){Abstract.prototype._clearCache.call(this);this._usedColors=[];},_updateCalendarColor:function(calendar){this._removeCalendarColorFromCache(calendar._id);this._setCalendarColor(calendar);this._usedColors.push(calendar.color);},_removeCalendarColorFromCache:function(id){var color=this.getColorByCalendarId(id);var index=this._usedColors.indexOf(color);if(index!==-1){this._usedColors.splice(index,1);}},getColorByCalendarId:function(id){return this._cached[id]&&this._cached[id].color;},_setCalendarColor:function(calendar){if(calendar._id===localCalendarId){calendar.color=Store.LOCAL_COLOR;return;}
|
||||
var prevColor=this.getColorByCalendarId(calendar._id);if(prevColor&&Store.REMOTE_COLORS.indexOf(prevColor)!==-1){calendar.color=prevColor;}else{calendar.color=this._getNextColor();}},_getNextColor:function(){var available=Store.REMOTE_COLORS.filter(function(color){return this._usedColors.indexOf(color)===-1;},this);return available.length?available[0]:this._getLeastUsedColor();},_getLeastUsedColor:function(){var counter={};this._usedColors.forEach(function(color){counter[color]=(counter[color]||0)+1;});var leastUsedColor;var leastUsedCount=Infinity;for(var color in counter){if(counter[color]<leastUsedCount){leastUsedCount=counter[color];leastUsedColor=color;}}
|
||||
return leastUsedColor;},shouldDisplayCalendar:function(calendarId){var calendar=this._cached[calendarId];return calendar&&calendar.localDisplayed;},remotesByAccount:function(accountId,trans,callback){if(typeof(trans)==='function'){callback=trans;trans=null;}
|
||||
if(!trans){trans=core.db.transaction(this._store);}
|
||||
var store=trans.objectStore(this._store);var reqKey=IDBKeyRange.only(accountId);var req=store.index('accountId').mozGetAll(reqKey);req.onerror=function remotesError(e){callback(e.target.error);};var self=this;req.onsuccess=function remotesSuccess(e){var result=Object.create(null);e.target.result.forEach(function(calendar){result[calendar.remote.id]=self._createModel(calendar,calendar._id);});callback(null,result);};},sync:function(account,calendar,callback){var provider=core.providerFactory.get(account.providerType);provider.syncEvents(account,calendar,callback);},providerFor:function(calendar,callback){this.ownersOf(calendar,function(err,owners){if(err){return callback(err);}
|
||||
var provider;try{var account=owners.account;var providerType=account.providerType;provider=core.providerFactory.get(providerType);}catch(error){return callback(error);}
|
||||
callback(null,provider);});},ownersOf:function(objectOrId,callback){var result={};var accountStore=core.storeFactory.get('Account');if(objectOrId instanceof CalendarModel){result.calendar=objectOrId;accountStore.get(objectOrId.accountId,fetchAccount);return;}
|
||||
if(typeof objectOrId==='object'){objectOrId=objectOrId.calendarId;}
|
||||
var calendarStore=core.storeFactory.get('Calendar');calendarStore.get(objectOrId,fetchCalendar);function fetchCalendar(err,calendar){if(err){return callback(err);}
|
||||
result.calendar=calendar;accountStore.get(calendar.accountId,fetchAccount);}
|
||||
function fetchAccount(err,account){if(err){return callback(err);}
|
||||
result.account=account;callback(null,result);}}};});define('store/event',['require','exports','module','./abstract','common/calc','./calendar','core','common/promise'],function(require,exports,module){'use strict';var Abstract=require('./abstract');var Calc=require('common/calc');var Calendar=require('./calendar');var core=require('core');var denodeifyAll=require('common/promise').denodeifyAll;function Events(){Abstract.apply(this,arguments);denodeifyAll(this,['providerFor','findByIds','ownersOf','eventsForCalendar']);}
|
||||
module.exports=Events;Events.prototype={__proto__:Abstract.prototype,_store:'events',_dependentStores:['events','busytimes','alarms','icalComponents'],_addToCache:function(){},_removeFromCache:function(){},_createModel:function(input,id){var _super=Abstract.prototype._createModel;var model=_super.apply(this,arguments);model.remote.startDate=Calc.dateFromTransport(model.remote.start);model.remote.endDate=Calc.dateFromTransport(model.remote.end);return model;},_removeDependents:function(id,trans){this.removeByIndex('parentId',id,trans);var busy=core.storeFactory.get('Busytime');busy.removeEvent(id,trans);var component=core.storeFactory.get('IcalComponent');component.remove(id,trans);},_assignId:function(obj){var id=obj.calendarId+'-'+obj.remote.id;obj._id=id;return id;},providerFor:function(event,callback){this.ownersOf(event,function(err,owners){callback(null,core.providerFactory.get(owners.account.providerType));});},findByIds:function(ids,callback){var results={};var pending=ids.length;var self=this;if(!pending){callback(null,results);}
|
||||
function next(){if(!(--pending)){callback(null,results);}}
|
||||
function success(e){var item=e.target.result;if(item){results[item._id]=self._createModel(item);}
|
||||
next();}
|
||||
function error(){next();}
|
||||
ids.forEach(function(id){var trans=core.db.transaction('events');var store=trans.objectStore('events');var req=store.get(id);req.onsuccess=success;req.onerror=error;},this);},ownersOf:Calendar.prototype.ownersOf,eventsForCalendar:function(calendarId,callback){var trans=core.db.transaction('events');var store=trans.objectStore('events');var index=store.index('calendarId');var key=IDBKeyRange.only(calendarId);var req=index.mozGetAll(key);req.onsuccess=function(e){callback(null,e.target.result);};req.onerror=function(e){callback(e);};}};});define('store/ical_component',['require','exports','module','./abstract','common/calc','core','common/promise'],function(require,exports,module){'use strict';var Abstract=require('./abstract');var Calc=require('common/calc');var core=require('core');var denodeifyAll=require('common/promise').denodeifyAll;function IcalComponent(){Abstract.apply(this,arguments);denodeifyAll(this,['findRecurrencesBefore']);}
|
||||
module.exports=IcalComponent;IcalComponent.prototype={__proto__:Abstract.prototype,_store:'icalComponents',_addToCache:function(){},_removeFromCache:function(){},_createModel:function(object){return object;},_detectPersistType:function(object){return'update';},findRecurrencesBefore:function(maxDate,callback){var trans=core.db.transaction(this._store,'readwrite');trans.onerror=function(event){callback(event.target.error.name);};var time=Calc.dateToTransport(maxDate);var utc=time.utc;var range=IDBKeyRange.bound(0,utc);var store=trans.objectStore(this._store);var idx=store.index('lastRecurrenceId');var req=idx.mozGetAll(range);req.onsuccess=function(event){callback(null,event.target.result);};}};});define('store/setting',['require','exports','module','./abstract','common/promise','common/next_tick'],function(require,exports,module){'use strict';var Abstract=require('./abstract');var denodeifyAll=require('common/promise').denodeifyAll;var nextTick=require('common/next_tick');function Setting(){Abstract.apply(this,arguments);denodeifyAll(this,['getValue','set']);}
|
||||
module.exports=Setting;Setting.prototype={__proto__:Abstract.prototype,_store:'settings',defaults:{standardAlarmDefault:-300,alldayAlarmDefault:32400,syncFrequency:15,syncAlarm:{alarmId:null,start:null,end:null}},_addToCache:function(){},_removeFromCache:function(){},getValue:function(key,callback){var self=this;if(key in this._cached){nextTick(function handleCached(){callback(null,self._cached[key].value);});return;}
|
||||
this.get(key,function handleStored(err,value){if(err){return callback(err);}
|
||||
if(value===undefined&&self.defaults[key]!==undefined){value={value:self.defaults[key]};}
|
||||
self._cached[key]=value;callback(null,value.value);});},set:function(key,value,trans,callback){if(typeof(trans)==='function'){callback=trans;trans=null;}
|
||||
var cached=this._cached[key];var record;if(cached&&cached._id){cached.value=value;cached.updatedAt=new Date();record=cached;}else{var created=new Date();this._cached[key]=record={_id:key,createdAt:created,updatedAt:created,value:value};}
|
||||
this.emit(key+'Change',value,record);this.persist(record,trans,callback);}};});define('store/factory',['require','exports','module','common/debug','./account','./alarm','./busytime','./calendar','./event','./ical_component','./setting'],function(require,exports){'use strict';var debug=require('common/debug')('store/factory');var ctors={Account:require('./account'),Alarm:require('./alarm'),Busytime:require('./busytime'),Calendar:require('./calendar'),Event:require('./event'),IcalComponent:require('./ical_component'),Setting:require('./setting')};exports._instances=Object.create(null);exports.get=function(name){if(!exports._instances[name]){try{debug(`Initializing store ${name}`);exports._instances[name]=new ctors[name]();}catch(e){console.error('Error',e.name,e.message);console.error('Failed to initialize store',name,e.stack);}}
|
||||
return exports._instances[name];};});define('time_observer',['require','exports','module','ext/eventemitter2'],function(require,exports,module){'use strict';var EventEmitter2=require('ext/eventemitter2');exports=module.exports=new EventEmitter2();exports.on=function(){EventEmitter2.prototype.on.apply(this,arguments);this._start();};exports.once=function(){EventEmitter2.prototype.once.apply(this,arguments);this._start();};exports.off=function(){EventEmitter2.prototype.off.apply(this,arguments);this._autoStop();};exports.removeAllListeners=function(){EventEmitter2.prototype.removeAllListeners.apply(this,arguments);this._autoStop();};exports._autoStop=function(){if(!this._hasListeners()){this._stop();}};exports._hasListeners=function(){return this.listeners('day').length>0||this.listeners('minute').length>0;};exports._timeout=null;exports._prevTick=null;exports._start=function(){if(this._timeout||!this._hasListeners()){return;}
|
||||
this._prevTick=new Date();this._timeout=setTimeout(this._tick,this._nextMinute());};exports._stop=function(){if(this._timeout){window.clearTimeout(this._timeout);this._timeout=null;}};exports._tick=function(){this._stop();this._exec();this._start();}.bind(exports);exports._nextMinute=function(){var now=new Date();return(60-now.getSeconds())*1000;};exports._exec=function(){var now=new Date();var prev=this._prevTick;if(prev.getMinutes()!==now.getMinutes()){this.emit('minute');}
|
||||
if(prev.getDate()!==now.getDate()){this.emit('day');}};exports.init=function(){document.addEventListener('visibilitychange',()=>{exports._toggleStatusOnVisibilityChange(document.hidden);});exports._start();};exports._toggleStatusOnVisibilityChange=function(hidden){if(!hidden){exports._exec();}
|
||||
var method=hidden?'_stop':'_start';exports[method]();};});define('snake_case',['require','exports','module'],function(require,exports,module){'use strict';module.exports=function(name){return name.replace(/^./,chr=>chr.toLowerCase()).replace(/[A-Z]/g,chr=>'_'+chr.toLowerCase());};});define('views/factory',['require','exports','module','common/async_require','common/debug','common/next_tick','snake_case'],function(require,exports,module){'use strict';var asyncRequire=require('common/async_require');var debug=require('common/debug')('viewFactory');var nextTick=require('common/next_tick');var snakeCase=require('snake_case');exports._instances=Object.create(null);exports.get=function(name,cb){if(this._registered(name)){debug(`Found view named ${name}`);this._get(name,cb);return;}
|
||||
var path=`views/${snakeCase(name)}`;try{var Ctor=require(path);debug(`Initializing view ${name} registered as ${path}`);this._initView(name,Ctor,cb);}catch(e){debug(`Will try to load view ${name} at ${path}`);asyncRequire(path).then(Ctor=>{debug(`Loaded view ${name}`);this._initView(name,Ctor,cb);});}},exports._registered=function(name){return name in this._instances;};exports._get=function(name,cb){var view=this._instances[name];cb&&nextTick(()=>cb.call(null,view));};exports._initView=function(name,Ctor,cb){if(this._registered(name)){return;}
|
||||
var view=new Ctor();this._instances[name]=view;this._get(name,cb);};});define('app',['require','exports','module','db','controllers/error','pending_manager','controllers/recurring_events','controllers/service','controllers/sync','controllers/time','common/async_require','bridge','ext/co','core','date_l10n','day_observer','common/debug','./intl','message_handler','common/next_tick','controllers/notifications','performance','controllers/periodic_sync','provider/factory','router','store/factory','time_observer','views/factory'],function(require,exports){'use strict';var Db=require('db');var ErrorController=require('controllers/error');var PendingManager=require('pending_manager');var RecurringEventsController=require('controllers/recurring_events');var ServiceController=require('controllers/service');var SyncController=require('controllers/sync');var TimeController=require('controllers/time');var asyncRequire=require('common/async_require');var bridge=require('bridge');var co=require('ext/co');var core=require('core');var dateL10n=require('date_l10n');var dayObserver=require('day_observer');var debug=require('common/debug')('app');var intl=require('./intl');var messageHandler=require('message_handler');var nextTick=require('common/next_tick');var notificationsController=require('controllers/notifications');var performance=require('performance');var periodicSyncController=require('controllers/periodic_sync');var providerFactory=require('provider/factory');var router=require('router');var storeFactory=require('store/factory');var timeObserver=require('time_observer');var viewFactory=require('views/factory');var loadLazyStyles=null;var l10nReady=new Promise(resolve=>navigator.mozL10n.once(()=>resolve()));var pendingManager=new PendingManager();var startingURL=window.location.href;function setupCore(dbName){if(core.db){return;}
|
||||
core.bridge=bridge;core.db=new Db(dbName||'b2g-calendar');core.errorController=new ErrorController();core.notificationsController=notificationsController;core.periodicSyncController=periodicSyncController;core.providerFactory=providerFactory;core.serviceController=new ServiceController();core.storeFactory=storeFactory;core.syncController=new SyncController();core.timeController=new TimeController();core.viewFactory=viewFactory;}
|
||||
function setupPendingManager(){pendingManager.onpending=()=>{document.body.classList.add('pending-operation');};pendingManager.oncomplete=()=>{document.body.classList.remove('pending-operation');performance.pendingReady();if(!loadLazyStyles){loadLazyStyles=asyncRequire('css!lazy_loaded');}};pendingManager.register(core.syncController);}
|
||||
function setupRouter(){router.state('/week/','Week');router.state('/day/','Day');router.state('/month/',['Month','MonthDayAgenda']);router.modifier('/settings/','Settings',{clear:false});router.modifier('/advanced-settings/','AdvancedSettings',{color:'settings'});router.state('/alarm-display/:id','ViewEvent',{path:false});router.state('/event/add/','ModifyEvent');router.state('/event/edit/:id','ModifyEvent');router.state('/event/show/:id','ViewEvent');router.modifier('/select-preset/','CreateAccount',{color:'settings'});router.modifier('/create-account/:preset','ModifyAccount');router.modifier('/update-account/:id','ModifyAccount');router.start();performance.chromeInteractive();var pathname=window.location.pathname;if(pathname==='/index.html'||pathname==='/'){router.go('/month/');}}
|
||||
function setupControllers(){core.serviceController.start(false);notificationsController.observe();periodicSyncController.observe();var recurringEventsController=new RecurringEventsController();pendingManager.register(recurringEventsController);recurringEventsController.observe();var alarms=core.storeFactory.get('Alarm');alarms.autoQueue=true;}
|
||||
function setupUI(){intl.init();return co(function*(){dateL10n.init();timeObserver.init();core.timeController.move(new Date());window.addEventListener('moztimechange',()=>{debug('Noticed timezone change!');nextTick(()=>window.location.href=startingURL);});nextTick(()=>viewFactory.get('Errors'));yield[renderView('TimeHeader'),renderView('ViewSelector')];document.body.classList.remove('loading');performance.domLoaded();setupRouter();});}
|
||||
function renderView(viewName){return new Promise(accept=>{viewFactory.get(viewName,view=>{view.render();accept();});});}
|
||||
function startDayObserver(){return co(function*(){var storeFactory=core.storeFactory;var calendars=storeFactory.get('Calendar');yield calendars.all();dayObserver.init();});}
|
||||
function startUI(){return co(function*(){yield l10nReady;yield setupUI();});}
|
||||
function configureAudioChannelManager(){var audioChannelManager=navigator.mozAudioChannelManager;if(audioChannelManager){audioChannelManager.volumeControlChannel='notification';}}
|
||||
function init(){return co(function*(){debug('Will initialize calendar app');setupCore();setupPendingManager();yield core.db.load();setupControllers();yield[startDayObserver(),startUI()];messageHandler.start();configureAudioChannelManager();});}
|
||||
exports.init=init;exports.pendingManager=pendingManager;exports.setupCore=setupCore;});(function(){'use strict';window.require=window.require||window.curl;require.config({baseUrl:'/js',waitSeconds:60,paths:{shared:'/shared/js',dom:'curl/plugin/dom',css:'curl/plugin/css'},shim:{'ext/caldav':{exports:'Caldav'},'ext/ical':{exports:'ICAL'},'ext/page':{exports:'page'},'shared/gesture_detector':{exports:'GestureDetector'},'shared/input_parser':{exports:'InputParser'},'shared/intl_helper':{exports:'IntlHelper'},'shared/lazy_loader':{exports:'LazyLoader'},'shared/notification_helper':{exports:'NotificationHelper'}}});require.config({paths:{'views/week':'lazy_loaded','views/advanced_settings':'lazy_loaded','views/create_account':'lazy_loaded','views/day':'lazy_loaded','views/modify_account':'lazy_loaded','views/modify_event':'lazy_loaded','views/settings':'lazy_loaded','views/view_event':'lazy_loaded'}});require(['app'],app=>app.init());}());define("main",function(){});define('view',['require','exports','module'],function(require,exports,module){'use strict';var DEFAULT_ERROR_ID='error-default';const INVALID_CSS=/([^a-zA-Z\-\_0-9])/g;function View(options){if(typeof(options)==='undefined'){options={};}
|
||||
if(typeof(options)==='string'){this.selectors={element:options};}else{var key;if(typeof(options)==='undefined'){options={};}
|
||||
for(key in options){if(options.hasOwnProperty(key)){this[key]=options[key];}}}
|
||||
this.hideErrors=this.hideErrors.bind(this);}
|
||||
module.exports=View;View.ACTIVE='active';View.prototype={seen:false,activeClass:View.ACTIVE,errorVisible:false,get element(){return this._findElement('element');},get status(){return this._findElement('status');},get errors(){return this._findElement('errors');},idForModel:function(prefix,objectOrString){prefix+=(typeof(objectOrString)==='object')?objectOrString._id:objectOrString;return prefix;},calendarId:function(input){if(typeof(input)!=='string'){input=input.calendarId;}
|
||||
input=this.cssClean(input);return'calendar-id-'+input;},delegate:function(element,type,selector,handler){if(typeof(handler)==='object'){var context=handler;handler=function(){context.handleEvent.apply(context,arguments);};}
|
||||
element.addEventListener(type,function(e){var target=e.target;while(target!==element){if('mozMatchesSelector'in target&&target.mozMatchesSelector(selector)){return handler(e,target);}
|
||||
target=target.parentNode;}});},cssClean:function(string){if(typeof(string)!=='string'){return string;}
|
||||
return string.replace(INVALID_CSS,'-');},_findElement:function(name,all,element){if(typeof(all)==='object'){element=all;all=false;}
|
||||
element=element||document;var cacheName;var selector;if(typeof(all)==='undefined'){all=false;}
|
||||
if(name in this.selectors){cacheName='_'+name+'Element';selector=this.selectors[name];if(!this[cacheName]){if(all){this[cacheName]=element.querySelectorAll(selector);}else{this[cacheName]=element.querySelector(selector);}}
|
||||
return this[cacheName];}
|
||||
return null;},showErrors:function(list){var _=navigator.mozL10n.get;var errors='';if(!Array.isArray(list)){list=[list];}
|
||||
var i=0;var len=list.length;for(;i<len;i++){var name=list[i].l10nID||list[i].name;errors+=_('error-'+name)||_(DEFAULT_ERROR_ID);}
|
||||
this.errors.textContent=errors;this.errorVisible=true;this.status.classList.add(this.activeClass);this.status.addEventListener('animationend',this.hideErrors);},hideErrors:function(){this.status.classList.remove(this.activeClass);this.status.removeEventListener('animationend',this.hideErrors);this.errorVisible=false;},onactive:function(){if(this.errorVisible){this.hideErrors();}
|
||||
if(this.seen===false){this.onfirstseen();}
|
||||
if('dispatch'in this){this.dispatch.apply(this,arguments);}
|
||||
this.seen=true;if(this.element){this.element.classList.add(this.activeClass);}},oninactive:function(){if(this.element){this.element.classList.remove(this.activeClass);}},onfirstseen:function(){}};});var LazyLoader=(function(){function LazyLoader(){this._loaded={};this._isLoading={};}
|
||||
LazyLoader.prototype={_js:function(file,callback){var script=document.createElement('script');script.src=file;script.async=false;script.addEventListener('load',callback);document.head.appendChild(script);this._isLoading[file]=script;},_css:function(file,callback){var style=document.createElement('link');style.type='text/css';style.rel='stylesheet';style.href=file;document.head.appendChild(style);callback();},_html:function(domNode,callback){if(domNode.getAttribute('is')){this.load(['/shared/js/html_imports.js'],function(){HtmlImports.populate(callback);}.bind(this));return;}
|
||||
for(var i=0;i<domNode.childNodes.length;i++){if(domNode.childNodes[i].nodeType==document.COMMENT_NODE){domNode.innerHTML=domNode.childNodes[i].nodeValue;break;}}
|
||||
window.dispatchEvent(new CustomEvent('lazyload',{detail:domNode}));callback();},getJSON:function(file,mozSystem){return new Promise(function(resolve,reject){var xhr;if(mozSystem){xhr=new XMLHttpRequest({mozSystem:true});}else{xhr=new XMLHttpRequest();}
|
||||
xhr.open('GET',file,true);xhr.responseType='json';xhr.onerror=function(error){reject(error);};xhr.onload=function(){if(xhr.response!==null){resolve(xhr.response);}else{reject(new Error('No valid JSON object was found ('+
|
||||
xhr.status+' '+xhr.statusText+')'));}};xhr.send();});},load:function(files,callback){var deferred={};deferred.promise=new Promise(resolve=>{deferred.resolve=resolve;});if(!Array.isArray(files)){files=[files];}
|
||||
var loadsRemaining=files.length,self=this;function perFileCallback(file){if(self._isLoading[file]){delete self._isLoading[file];}
|
||||
self._loaded[file]=true;if(--loadsRemaining===0){deferred.resolve();if(callback){callback();}}}
|
||||
for(var i=0;i<files.length;i++){var file=files[i];if(this._loaded[file.id||file]){perFileCallback(file);}else if(this._isLoading[file]){this._isLoading[file].addEventListener('load',perFileCallback.bind(null,file));}else{var method,idx;if(typeof file==='string'){method=file.match(/\.([^.]+)$/)[1];idx=file;}else{method='html';idx=file.id;}
|
||||
this['_'+method](file,perFileCallback.bind(null,idx));}}
|
||||
return deferred.promise;}};return new LazyLoader();}());define("shared/lazy_loader",(function(global){return function(){var ret,fn;return ret||global.LazyLoader;};}(this)));define('dom',['require','exports','module','shared/lazy_loader'],function(require,exports){'use strict';var LazyLoader=require('shared/lazy_loader');exports.load=function(id,require,onLoad,config){if(config.isBuild){return onLoad();}
|
||||
var node=document.getElementById(id);if(!node){onLoad.error('can\'t find element with id #'+id);return;}
|
||||
LazyLoader.load(node,function(){onLoad(node);});};});define('views/errors',['require','exports','module','view','core','dom!errors'],function(require,exports,module){'use strict';var View=require('view');var core=require('core');require('dom!errors');function Errors(){View.apply(this,arguments);core.syncController.on('offline',this);}
|
||||
module.exports=Errors;Errors.prototype={__proto__:View.prototype,selectors:{status:'*[role="application"] > section[role="status"]',errors:'*[role="application"] > section > .errors'},handleEvent:function(event){switch(event.type){case'offline':this.showErrors([{name:'offline'}]);break;}}};});var GestureDetector=(function(){function GD(e,options){this.element=e;this.options=options||{};this.options.panThreshold=this.options.panThreshold||GD.PAN_THRESHOLD;this.state=initialState;this.timers={};}
|
||||
GD.prototype.startDetecting=function(){var self=this;eventtypes.forEach(function(t){self.element.addEventListener(t,self);});};GD.prototype.stopDetecting=function(){var self=this;eventtypes.forEach(function(t){self.element.removeEventListener(t,self);});};GD.prototype.handleEvent=function(e){var handler=this.state[e.type];if(!handler){return;}
|
||||
if(e.changedTouches){for(var i=0;i<e.changedTouches.length;i++){handler(this,e,e.changedTouches[i]);handler=this.state[e.type];if(!handler){return;}}}
|
||||
else{handler(this,e);}};GD.prototype.startTimer=function(type,time){this.clearTimer(type);var self=this;this.timers[type]=setTimeout(function(){self.timers[type]=null;var handler=self.state[type];if(handler){handler(self,type);}},time);};GD.prototype.clearTimer=function(type){if(this.timers[type]){clearTimeout(this.timers[type]);this.timers[type]=null;}};GD.prototype.switchTo=function(state,event,touch){this.state=state;if(state.init){state.init(this,event,touch);}};GD.prototype.emitEvent=function(type,detail){if(!this.target){console.error('Attempt to emit event with no target');return;}
|
||||
var event=this.element.ownerDocument.createEvent('CustomEvent');event.initCustomEvent(type,true,true,detail);this.target.dispatchEvent(event);};GD.HOLD_INTERVAL=1000;GD.PAN_THRESHOLD=20;GD.DOUBLE_TAP_DISTANCE=50;GD.DOUBLE_TAP_TIME=500;GD.VELOCITY_SMOOTHING=0.5;GD.SCALE_THRESHOLD=20;GD.ROTATE_THRESHOLD=22.5;GD.THRESHOLD_SMOOTHING=0.9;var abs=Math.abs,floor=Math.floor,sqrt=Math.sqrt,atan2=Math.atan2;var PI=Math.PI;var eventtypes=['touchstart','touchmove','touchend'];function eventTime(e){var ts=e.timeStamp;if(ts>2*Date.now()){return Math.floor(ts/1000);}else{return ts;}}
|
||||
function coordinates(e,t){return Object.freeze({screenX:t.screenX,screenY:t.screenY,clientX:t.clientX,clientY:t.clientY,timeStamp:eventTime(e)});}
|
||||
function midpoints(e,t1,t2){return Object.freeze({screenX:floor((t1.screenX+t2.screenX)/2),screenY:floor((t1.screenY+t2.screenY)/2),clientX:floor((t1.clientX+t2.clientX)/2),clientY:floor((t1.clientY+t2.clientY)/2),timeStamp:eventTime(e)});}
|
||||
function between(c1,c2){var r=GD.THRESHOLD_SMOOTHING;return Object.freeze({screenX:floor(c1.screenX+r*(c2.screenX-c1.screenX)),screenY:floor(c1.screenY+r*(c2.screenY-c1.screenY)),clientX:floor(c1.clientX+r*(c2.clientX-c1.clientX)),clientY:floor(c1.clientY+r*(c2.clientY-c1.clientY)),timeStamp:floor(c1.timeStamp+r*(c2.timeStamp-c1.timeStamp))});}
|
||||
function touchDistance(t1,t2){var dx=t2.screenX-t1.screenX;var dy=t2.screenY-t1.screenY;return sqrt(dx*dx+dy*dy);}
|
||||
function touchDirection(t1,t2){return atan2(t2.screenY-t1.screenY,t2.screenX-t1.screenX)*180/PI;}
|
||||
function touchRotation(d1,d2){var angle=d2-d1;if(angle>180){angle-=360;}else if(angle<=-180){angle+=360;}
|
||||
return angle;}
|
||||
function isDoubleTap(lastTap,thisTap){var dx=abs(thisTap.screenX-lastTap.screenX);var dy=abs(thisTap.screenY-lastTap.screenY);var dt=thisTap.timeStamp-lastTap.timeStamp;return(dx<GD.DOUBLE_TAP_DISTANCE&&dy<GD.DOUBLE_TAP_DISTANCE&&dt<GD.DOUBLE_TAP_TIME);}
|
||||
var initialState={name:'initialState',init:function(d,e,t){d.target=null;d.start=d.last=null;d.touch1=d.touch2=null;d.vx=d.vy=null;d.startDistance=d.lastDistance=null;d.startDirection=d.lastDirection=null;d.lastMidpoint=null;d.scaled=d.rotated=null;if(e&&t&&e.type==='touchstart'){initialState.touchstart(d,e,t);}},touchstart:function(d,e,t){d.switchTo(touchStartedState,e,t);}};var touchStartedState={name:'touchStartedState',init:function(d,e,t){d.target=e.target;d.touch1=t.identifier;d.start=d.last=coordinates(e,t);if(d.options.holdEvents){d.startTimer('holdtimeout',GD.HOLD_INTERVAL);}},touchstart:function(d,e,t){d.clearTimer('holdtimeout');if(e.touches.length>1){d.switchTo(transformState,e,t);}
|
||||
else{console.warn('Ignoring missing touchend event. See bug 1162771.');d.switchTo(initialState,e,t);}},touchmove:function(d,e,t){if(t.identifier!==d.touch1){return;}
|
||||
if(abs(t.screenX-d.start.screenX)>d.options.panThreshold||abs(t.screenY-d.start.screenY)>d.options.panThreshold){d.clearTimer('holdtimeout');d.switchTo(panStartedState,e,t);}},touchend:function(d,e,t){if(t.identifier!==d.touch1){return;}
|
||||
if(d.lastTap&&isDoubleTap(d.lastTap,d.start)){d.emitEvent('tap',d.start);d.emitEvent('dbltap',d.start);d.lastTap=null;}
|
||||
else{d.emitEvent('tap',d.start);d.lastTap=coordinates(e,t);}
|
||||
d.clearTimer('holdtimeout');d.switchTo(initialState);},holdtimeout:function(d){d.switchTo(holdState);}};var panStartedState={name:'panStartedState',init:function(d,e,t){d.start=d.last=between(d.start,coordinates(e,t));if(e.type==='touchmove'){panStartedState.touchmove(d,e,t);}},touchmove:function(d,e,t){if(t.identifier!==d.touch1){return;}
|
||||
var current=coordinates(e,t);d.emitEvent('pan',{absolute:{dx:current.screenX-d.start.screenX,dy:current.screenY-d.start.screenY},relative:{dx:current.screenX-d.last.screenX,dy:current.screenY-d.last.screenY},position:current});var dt=current.timeStamp-d.last.timeStamp;var vx=(current.screenX-d.last.screenX)/dt;var vy=(current.screenY-d.last.screenY)/dt;if(d.vx==null){d.vx=vx;d.vy=vy;}
|
||||
else{d.vx=d.vx*GD.VELOCITY_SMOOTHING+
|
||||
vx*(1-GD.VELOCITY_SMOOTHING);d.vy=d.vy*GD.VELOCITY_SMOOTHING+
|
||||
vy*(1-GD.VELOCITY_SMOOTHING);}
|
||||
d.last=current;},touchend:function(d,e,t){if(t.identifier!==d.touch1){return;}
|
||||
var current=coordinates(e,t);var dx=current.screenX-d.start.screenX;var dy=current.screenY-d.start.screenY;var angle=atan2(dy,dx)*180/PI;if(angle<0){angle+=360;}
|
||||
var direction;if(angle>=315||angle<45){direction='right';}else if(angle>=45&&angle<135){direction='down';}else if(angle>=135&&angle<225){direction='left';}else if(angle>=225&&angle<315){direction='up';}
|
||||
d.emitEvent('swipe',{start:d.start,end:current,dx:dx,dy:dy,dt:e.timeStamp-d.start.timeStamp,vx:d.vx,vy:d.vy,direction:direction,angle:angle});d.switchTo(initialState);}};var holdState={name:'holdState',init:function(d){d.emitEvent('holdstart',d.start);},touchmove:function(d,e,t){var current=coordinates(e,t);d.emitEvent('holdmove',{absolute:{dx:current.screenX-d.start.screenX,dy:current.screenY-d.start.screenY},relative:{dx:current.screenX-d.last.screenX,dy:current.screenY-d.last.screenY},position:current});d.last=current;},touchend:function(d,e,t){var current=coordinates(e,t);d.emitEvent('holdend',{start:d.start,end:current,dx:current.screenX-d.start.screenX,dy:current.screenY-d.start.screenY});d.switchTo(initialState);}};var transformState={name:'transformState',init:function(d,e,t){d.touch2=t.identifier;var t1=e.touches.identifiedTouch(d.touch1);var t2=e.touches.identifiedTouch(d.touch2);d.startDistance=d.lastDistance=touchDistance(t1,t2);d.startDirection=d.lastDirection=touchDirection(t1,t2);d.scaled=d.rotated=false;},touchmove:function(d,e,t){if(t.identifier!==d.touch1&&t.identifier!==d.touch2){return;}
|
||||
var t1=e.touches.identifiedTouch(d.touch1);var t2=e.touches.identifiedTouch(d.touch2);var midpoint=midpoints(e,t1,t2);var distance=touchDistance(t1,t2);var direction=touchDirection(t1,t2);var rotation=touchRotation(d.startDirection,direction);if(!d.scaled){if(abs(distance-d.startDistance)>GD.SCALE_THRESHOLD){d.scaled=true;d.startDistance=d.lastDistance=floor(d.startDistance+
|
||||
GD.THRESHOLD_SMOOTHING*(distance-d.startDistance));}else{distance=d.startDistance;}}
|
||||
if(!d.rotated){if(abs(rotation)>GD.ROTATE_THRESHOLD){d.rotated=true;}else{direction=d.startDirection;}}
|
||||
if(d.scaled||d.rotated){d.emitEvent('transform',{absolute:{scale:distance/d.startDistance,rotate:touchRotation(d.startDirection,direction)},relative:{scale:distance/d.lastDistance,rotate:touchRotation(d.lastDirection,direction)},midpoint:midpoint});d.lastDistance=distance;d.lastDirection=direction;d.lastMidpoint=midpoint;}},touchend:function(d,e,t){if(t.identifier===d.touch2){d.touch2=null;}else if(t.identifier===d.touch1){d.touch1=d.touch2;d.touch2=null;}else{return;}
|
||||
if(d.scaled||d.rotated){d.emitEvent('transformend',{absolute:{scale:d.lastDistance/d.startDistance,rotate:touchRotation(d.startDirection,d.lastDirection)},relative:{scale:1,rotate:0},midpoint:d.lastMidpoint});}
|
||||
d.switchTo(afterTransformState);}};var afterTransformState={name:'afterTransformState',touchstart:function(d,e,t){d.switchTo(transformState,e,t);},touchend:function(d,e,t){if(t.identifier===d.touch1){d.switchTo(initialState);}}};return GD;}());define("shared/gesture_detector",(function(global){return function(){var ret,fn;return ret||global.GestureDetector;};}(this)));define('views/month_day',['require','exports','module','common/calc','core'],function(require,exports,module){'use strict';var Calc=require('common/calc');var core=require('core');function MonthDay(options){this.container=options.container;this.date=options.date;this.month=options.month;this._updateBusyCount=this._updateBusyCount.bind(this);}
|
||||
module.exports=MonthDay;MonthDay.prototype={container:null,date:null,element:null,month:null,_observer:null,create:function(){var dayId=Calc.getDayId(this.date);var id='month-view-day-'+dayId;var state=Calc.relativeState(this.date,this.month);var l10nStateId=state.replace(/\s/g,'-');var date=this.date.getDate();if(l10nStateId==='future'||l10nStateId==='past'){l10nStateId='';}else{l10nStateId+='-description';}
|
||||
var el=document.createElement('li');el.setAttribute('role','gridcell');el.id=id;el.tabindex=0;el.setAttribute('aria-describedby',`${id}-busy-indicator ${id}-description`);el.dataset.date=dayId;el.className=state;el.classList.add('month-day');el.innerHTML=`<span class="day" role="button">${date}</span>
|
||||
<div id="${id}-busy-indicator" class="busy-indicator"
|
||||
aria-hidden="true"></div>
|
||||
<span id="${id}-description" aria-hidden="true"
|
||||
data-l10n-id="${l10nStateId}"></span>`;this.element=el;this.container.appendChild(el);},activate:function(){this._observer=core.bridge.observeDay(this.date);this._observer.listen(this._updateBusyCount);},deactivate:function(){this._observer&&this._observer.cancel();},destroy:function(){this.deactivate();this.container=this.element=null;},_updateBusyCount:function(data){var count=Math.min(3,data.amount);var holder=this.element.querySelector('.busy-indicator');if(count>0){holder.setAttribute('aria-label',navigator.mozL10n.get('busy',{n:count}));}else{holder.removeAttribute('aria-label');}
|
||||
var diff=count-holder.childNodes.length;if(diff===0){return;}
|
||||
if(diff>0){var dot;while(diff--){dot=document.createElement('div');dot.className='gaia-icon icon-calendar-dot';holder.appendChild(dot);}
|
||||
return;}
|
||||
while(diff++){holder.removeChild(holder.firstChild);}}};});define('views/single_month',['require','exports','module','common/calc','./month_day','view','core'],function(require,exports,module){'use strict';var Calc=require('common/calc');var MonthDay=require('./month_day');var View=require('view');var core=require('core');var daysBetween=Calc.daysBetween;var daysInWeek=Calc.daysInWeek;var getDayId=Calc.getDayId;var spanOfMonth=Calc.spanOfMonth;var SELECTED='selected';function SingleMonth(){View.apply(this,arguments);this.days=[];this.timespan=spanOfMonth(this.date);}
|
||||
module.exports=SingleMonth;SingleMonth.prototype={__proto__:View.prototype,active:false,container:null,date:null,days:null,element:null,create:function(){var element=document.createElement('section');element.className='month';element.setAttribute('role','grid');element.setAttribute('aria-labelledby','current-month-year');element.setAttribute('aria-readonly',true);element.innerHTML=this._renderDayHeaders();element.dataset.date=getDayId(this.date);this.element=element;this._buildWeeks();},_renderDayHeaders:function(){var startDay=Calc.startDay;var days=[];var i;for(i=startDay;i<7;i++){days.push(this._dayHeader(i));}
|
||||
if(startDay>0){for(i=0;i<startDay;i++){days.push(this._dayHeader(i));}}
|
||||
var html=`<header id="month-days" role="presentation">
|
||||
<ol role="row">${days.join('')}</ol>
|
||||
</header>`;return html;},_dayHeader:function(dayIndex){return`<li data-l10n-id="weekday-${dayIndex}-single-char"
|
||||
role="columnheader"></li>`;},_buildWeeks:function(){var weekLength=daysInWeek();var week;daysBetween(this.timespan).forEach((date,i)=>{if(i%weekLength===0){week=document.createElement('ol');week.setAttribute('role','row');this.element.appendChild(week);}
|
||||
var day=new MonthDay({date:date,month:this.date,container:week});day.create();this.days.push(day);});},activate:function(){if(this.active){return;}
|
||||
this.active=true;this.onactive();this.days.forEach(day=>day.activate());this._onSelectedDayChange(core.timeController.selectedDay);core.timeController.on('selectedDayChange',this);},deactivate:function(){if(!this.active){return;}
|
||||
this.active=false;this.oninactive();this.days.forEach(day=>day.deactivate());core.timeController.off('selectedDayChange',this);},destroy:function(){this.deactivate();this._detach();this.days.forEach(day=>day.destroy());this.days=[];},append:function(){this.container.appendChild(this.element);},_detach:function(){if(this.element&&this.element.parentNode){this.element.parentNode.removeChild(this.element);}},handleEvent:function(e){if(e.type==='selectedDayChange'){this._onSelectedDayChange(e.data[0]);}},_clearSelectedDay:function(){var day=this.element.querySelector(`li.${SELECTED}`);if(day){day.classList.remove(SELECTED);day.removeAttribute('aria-selected');}},_onSelectedDayChange:function(date){this._clearSelectedDay();if(!date||!this.timespan.contains(date)){return;}
|
||||
var el=this.element.querySelector(`li[data-date="${getDayId(date)}"]`);el.classList.add(SELECTED);el.setAttribute('aria-selected',true);el.focus();}};});define('views/month',['require','exports','module','common/calc','shared/gesture_detector','./single_month','view','core','performance','router','time_observer'],function(require,exports,module){'use strict';var Calc=require('common/calc');var GestureDetector=require('shared/gesture_detector');var SingleMonth=require('./single_month');var View=require('view');var core=require('core');var dateFromId=Calc.dateFromId;var monthStart=Calc.monthStart;var performance=require('performance');var router=require('router');var timeObserver=require('time_observer');var XSWIPE_OFFSET=window.innerWidth/10;function Month(){View.apply(this,arguments);this.frames=new Map();window.addEventListener('localized',this);this._setPresentDate=this._setPresentDate.bind(this);}
|
||||
module.exports=Month;Month.prototype={__proto__:View.prototype,SCALE:'month',selectors:{element:'#month-view',},date:null,currentFrame:null,_lastTarget:null,frames:null,onactive:function(){View.prototype.onactive.apply(this,arguments);core.timeController.scale=this.SCALE;if(this.currentFrame){this.currentFrame.activate();}},_onswipe:function(data){if(Math.abs(data.dy)>(Math.abs(data.dx)-XSWIPE_OFFSET)){return;}
|
||||
var dir=document.documentElement.dir==='rtl'?-1:1;this._move(dir*data.dx<0);},_onwheel:function(event){if(event.deltaMode!==event.DOM_DELTA_PAGE||event.deltaX===0){return;}
|
||||
this._move(event.deltaX>0);},_move:function(isNext){var controller=core.timeController;var date=isNext?this._nextTime():this._previousTime();controller.selectedDay=date;controller.move(date);},_nextTime:function(){return monthStart(this.date,1);},_previousTime:function(){return monthStart(this.date,-1);},_initEvents:function(){this.controller=core.timeController;this.element.addEventListener('swipe',this);this.element.addEventListener('wheel',this);this.controller.on('monthChange',this);this.delegate(this.element,'click','.month-day',this);this.delegate(this.element,'dbltap','.month-day',this);timeObserver.on('day',this._setPresentDate);this.gd=new GestureDetector(this.element);this.gd.startDetecting();},handleEvent:function(e,target){switch(e.type){case'swipe':this._onswipe(e.detail);break;case'wheel':this._onwheel(e);break;case'click':var date=dateFromId(target.dataset.date);this.controller.selectedDay=date;break;case'dbltap':if(this._lastTarget===target){this._goToAddEvent();}
|
||||
break;case'monthChange':this.changeDate(e.data[0]);break;case'localized':this.reconstruct();break;}
|
||||
this._lastTarget=target;},_goToAddEvent:function(date){setTimeout(()=>{router.go('/event/add/');},50);},changeDate:function(time){this.date=monthStart(time);if(this.currentFrame){this.currentFrame.deactivate();}
|
||||
this.currentFrame=this._getFrame(this.date);this._trimFrames();this._appendFrames();this.currentFrame.activate();},_getFrame:function(date){var id=date.getTime();var frame=this.frames.get(id);if(!frame){frame=new SingleMonth({date:date,container:this.element});frame.create();this.frames.set(id,frame);}
|
||||
return frame;},_trimFrames:function(){if(this.frames.size<=3){return;}
|
||||
var delta=31*24*60*60*1000;this.frames.forEach((frame,ts)=>{var base=Number(this.date);if(Math.abs(base-ts)>delta){frame.destroy();this.frames.delete(ts);}});},_appendFrames:function(){Array.from(this.frames.keys()).sort((a,b)=>a-b).forEach(key=>this.frames.get(key).append());},_setPresentDate:function(){var id=Calc.getDayId(new Date());var presentDate=this.element.querySelector('[data-date="'+id+'"]');var previousDate=this.element.querySelector('.present');if(previousDate){previousDate.classList.remove('present');previousDate.classList.add('past');}
|
||||
if(presentDate){presentDate.classList.add('present');}},oninactive:function(){View.prototype.oninactive.call(this);if(this.currentFrame){this.currentFrame.deactivate();}},onfirstseen:function(){this._initEvents();this.changeDate(this.controller.month);performance.monthReady();},destroy:function(){this.frames.forEach((frame,key)=>{this.frames.delete(key);frame.destroy();});},reconstruct:function(){this.destroy();this.changeDate(this.controller.month);}};});define('template',['require','exports','module'],function(require,exports,module){'use strict';var POSSIBLE_HTML=/[&<>"'`]/;var span=document.createElement('span');function create(templates){var key,result={};for(key in templates){if(templates.hasOwnProperty(key)){result[key]=new Template(templates[key]);}}
|
||||
return result;}
|
||||
function Template(fn){this.template=fn;}
|
||||
module.exports=Template;Template.prototype={arg:function(key){if(typeof(this.data)==='undefined'){return'';}else if(typeof(this.data)!=='object'){return this.data;}
|
||||
return this.data[key];},h:function(a){var arg=this.arg(a);arg=arg==null?'':String(arg);if(POSSIBLE_HTML.test(arg)){span.textContent=arg;return span.innerHTML.replace(/"/g,'"').replace(/'/g,''');}else{return arg;}},s:function(a){var arg=this.arg(a);return String((arg||''));},bool:function(key,onTrue){if(this.data[key]){return onTrue;}else{return'';}},l10n:function(key,prefix){var value=this.arg(key);if(prefix){value=prefix+value;}
|
||||
return navigator.mozL10n.get(value);},l10nId:function(a){return this.s(a).replace(/\s/g,'-');},render:function(data){this.data=data;return this.template();},renderEach:function(objects,join){var i=0,len=objects.length,result=[];for(;i<len;i++){result.push(this.render(objects[i]));}
|
||||
if(typeof(join)!=='undefined'){return result.join(join);}
|
||||
return result;}};Template.create=create;});define('templates/date_span',['require','exports','module','common/calc','shared/intl_helper','template'],function(require,exports,module){'use strict';var Calc=require('common/calc');var IntlHelper=require('shared/intl_helper');var create=require('template').create;module.exports=create({time:function(){var time=this.arg('time');var format=this.h('format');var formatter=IntlHelper.get(format);var displayTime=formatter.format(time);return`<span data-l10n-date-format="${format}"
|
||||
data-date="${time}">${displayTime}</span>`;},hour:function(){var hour=this.h('hour');var format=this.h('format');var className=this.h('className');var date=new Date(0);date.setHours(hour,0,0,0);var displayHour;var formatter=IntlHelper.get(format);if(this.arg('addAmPmClass')){displayHour=formatter.format(date,{dayperiod:'<span class="ampm" aria-hidden="true">$&</span>',});}else{displayHour=formatter.format(date);}
|
||||
var l10nAttr=(hour===Calc.ALLDAY)?'data-l10n-id="hour-allday"':`data-l10n-date-format="${format}"`;return`<span class="${className}" data-date="${date}"
|
||||
${l10nAttr}>${displayHour}</span>`;}});});define('templates/month_day_agenda',['require','exports','module','./date_span','template'],function(require,exports,module){'use strict';var DateSpan=require('./date_span');var create=require('template').create;var MonthDayAgenda=create({event:function(){var busytimeId=this.h('busytimeId');var color=this.h('color');var eventTime;if(this.arg('isAllDay')){eventTime='<div class="all-day" data-l10n-id="hour-allday"></div>';}else{var startTime=formatTime(this.arg('startTime'));var endTime=formatTime(this.arg('endTime'));eventTime=`<div class="start-time">${startTime}</div>
|
||||
<div class="end-time">${endTime}</div>`;}
|
||||
var title=this.h('title');var eventDetails=`<h5 role="presentation" dir="auto">${title}</h5>`;var location=this.h('location');if(location&&location.length>0){eventDetails+=`<span class="details" dir="auto">
|
||||
<span class="location">${location}</span>
|
||||
</span>`;}
|
||||
var alarmClass=this.arg('hasAlarms')?'has-alarms':'';return`<a href="/event/show/${busytimeId}/" class="event ${alarmClass}"
|
||||
role="option" aria-describedby="${busytimeId}-icon-calendar-alarm">
|
||||
<div class="container">
|
||||
<div class="gaia-icon icon-calendar-dot" style="color:${color}"
|
||||
aria-hidden="true"></div>
|
||||
<div class="event-time">${eventTime}</div>
|
||||
<div class="event-details">${eventDetails}</div>
|
||||
<div id="${busytimeId}-icon-calendar-alarm" aria-hidden="true"
|
||||
class="gaia-icon icon-calendar-alarm" style="color:${color}"
|
||||
data-l10n-id="icon-calendar-alarm"></div>
|
||||
</div>
|
||||
</a>`;}});module.exports=MonthDayAgenda;function formatTime(time){return DateSpan.time.render({time:time,format:'shortTimeFormat'});}});define('views/month_day_agenda',['require','exports','module','shared/intl_helper','view','core','common/calc','common/calc','performance','templates/month_day_agenda'],function(require,exports,module){'use strict';var IntlHelper=require('shared/intl_helper');var Parent=require('view');var core=require('core');var createDay=require('common/calc').createDay;var isAllDay=require('common/calc').isAllDay;var performance=require('performance');var template=require('templates/month_day_agenda');function MonthDayAgenda(){Parent.apply(this,arguments);this._render=this._render.bind(this);this.controller=core.timeController;}
|
||||
module.exports=MonthDayAgenda;MonthDayAgenda.prototype={__proto__:Parent.prototype,date:null,_observer:null,selectors:{element:'#month-day-agenda',events:'.day-events',currentDate:'#event-list-date',emptyMessage:'#empty-message'},get element(){return this._findElement('element');},get events(){return this._findElement('events');},get currentDate(){return this._findElement('currentDate');},get emptyMessage(){return this._findElement('emptyMessage');},onactive:function(){Parent.prototype.onactive.call(this);this.controller.on('selectedDayChange',this);this.changeDate(this.controller.selectedDay);},oninactive:function(){Parent.prototype.oninactive.call(this);this._unobserve();this.controller.removeEventListener('selectedDayChange',this);this.date=null;},_unobserve:function(){this._observer&&this._observer.cancel();this._observer=null;},changeDate:function(date){date=date||createDay(new Date());this.date=date;this._unobserve();this._observer=core.bridge.observeDay(date);this._observer.listen(this._render);var formatId='months-day-view-header-format';var formatter=IntlHelper.get(formatId);this.currentDate.textContent=formatter.format(date);this.currentDate.dataset.date=date;this.currentDate.dataset.l10nDateFormat=formatId;},_render:function(records){this.events.innerHTML=records.allday.concat(records.basic).map(this._renderEvent,this).join('');this.emptyMessage.classList.toggle('active',records.amount===0);performance.monthsDayReady();},_renderEvent:function(record){var{event,busytime,color}=record;var{startDate,endDate}=busytime;return template.event.render({hasAlarms:!!(event.remote.alarms&&event.remote.alarms.length),busytimeId:busytime._id,color:color,title:event.remote.title,location:event.remote.location,startTime:startDate,endTime:endDate,isAllDay:isAllDay(this.date,startDate,endDate)});},handleEvent:function(e){switch(e.type){case'selectedDayChange':this.changeDate(e.data[0]);break;}}};});define('views/time_header',['require','exports','module','shared/intl_helper','view','core','router'],function(require,exports,module){'use strict';var IntlHelper=require('shared/intl_helper');var View=require('view');var core=require('core');var router=require('router');var SETTINGS=/settings/;function TimeHeader(){View.apply(this,arguments);this.controller=core.timeController;this.controller.on('scaleChange',this);this.element.addEventListener('action',(e)=>{e.stopPropagation();var path=window.location.pathname;if(SETTINGS.test(path)){router.resetState();}else{router.show('/settings/');}});}
|
||||
module.exports=TimeHeader;TimeHeader.prototype={__proto__:View.prototype,selectors:{element:'#time-header',title:'#time-header h1'},scales:{month:'multi-month-view-header-format',day:'day-view-header-format',multiMonth:'multi-month-view-header-format'},handleEvent:function(e){switch(e.type){case'yearChange':case'monthChange':case'dayChange':case'weekChange':this._updateTitle();break;case'scaleChange':this._updateScale.apply(this,e.data);break;}},get title(){return this._findElement('title');},_scaleEvent:function(event){switch(event){case'month':return'monthChange';case'year':return'yearChange';case'week':return'weekChange';}
|
||||
return'dayChange';},_updateScale:function(newScale,oldScale){if(oldScale){this.controller.removeEventListener(this._scaleEvent(oldScale),this);}
|
||||
this.controller.addEventListener(this._scaleEvent(newScale),this);this._updateTitle();},getScale:function(type){var position=this.controller.position;if(type==='week'){var lastWeekday=this._getLastWeekday();if(position.getMonth()!==lastWeekday.getMonth()){return this._localeFormat(position,'multiMonth')+' '+
|
||||
this._localeFormat(lastWeekday,'multiMonth');}
|
||||
type='month';}
|
||||
return this._localeFormat(position,type||'month');},_getLastWeekday:function(){var position=this.controller.position;return new Date(position.getFullYear(),position.getMonth(),position.getDate()+4);},_localeFormat:function(date,scale){if(!this.scales[scale]){return;}
|
||||
var formatter=IntlHelper.get(this.scales[scale]);return formatter.format(date);},_updateTitle:function(){var con=core.timeController;var title=this.title;title.dataset.l10nDateFormat=this.scales[con.scale]||this.scales.month;title.dataset.date=con.position.toString();title.textContent=this.getScale(con.scale);},render:function(){this._updateScale(this.controller.scale);}};});define('views/view_selector',['require','exports','module','view','core','common/next_tick','time_observer'],function(require,exports,module){'use strict';var View=require('view');var core=require('core');var nextTick=require('common/next_tick');var timeObserver=require('time_observer');function ViewSelector(opts){View.call(this,opts);this._showTodayDate=this._showTodayDate.bind(this);}
|
||||
module.exports=ViewSelector;ViewSelector.prototype={__proto__:View.prototype,selectors:{element:'#view-selector'},render:function(){this._showTodayDate();this.delegate(this.element,'click','a',this);timeObserver.on('day',this._showTodayDate);},handleEvent:function(event,target){if(target.matches('[role="tab"]')){this._toggleActiveTabOnClick(event);}else{this._moveToTodayOnClick(event);}},_moveToTodayOnClick:function(event){var date=new Date();core.timeController.move(date);core.timeController.selectedDay=date;event.preventDefault();},_toggleActiveTabOnClick:function(event){var tabs=this.element.querySelectorAll('[role="tab"]');Array.from(tabs).forEach(tab=>{if(tab!==event.target){tab.setAttribute('aria-selected',false);return;}
|
||||
nextTick(()=>tab.setAttribute('aria-selected',true));});},_showTodayDate:function(){var icon=this.element.querySelector('.icon-calendar-today');icon.innerHTML=(new Date()).getDate();}};});define("bundle",function(){});
|
||||
3329
assets/webapps/calendar.gaiamobile.org/js/caldav_worker.js
Normal file
3329
assets/webapps/calendar.gaiamobile.org/js/caldav_worker.js
Normal file
File diff suppressed because it is too large
Load diff
22
assets/webapps/calendar.gaiamobile.org/js/curl/plugin/css.js
Normal file
22
assets/webapps/calendar.gaiamobile.org/js/curl/plugin/css.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Loader plugin for loading CSS. Does not guarantee loading via onload
|
||||
// watching, just inserts link tag.
|
||||
// PS: borrowed from email app and edited it to include the baseUrl
|
||||
define(['require','exports','module'],function(require, exports) {
|
||||
'use strict';
|
||||
|
||||
exports.baseUrl = '/style/';
|
||||
|
||||
exports.load = function(id, require, onload, config) {
|
||||
if (config.isBuild) {
|
||||
return onload();
|
||||
}
|
||||
|
||||
var style = document.createElement('link');
|
||||
style.type = 'text/css';
|
||||
style.rel = 'stylesheet';
|
||||
style.href = require.toUrl(exports.baseUrl + id + '.css');
|
||||
style.addEventListener('load', onload, false);
|
||||
document.head.appendChild(style);
|
||||
};
|
||||
|
||||
});
|
||||
4095
assets/webapps/calendar.gaiamobile.org/js/ext/caldav.js
Normal file
4095
assets/webapps/calendar.gaiamobile.org/js/ext/caldav.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,369 @@
|
|||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Module systems magic dance.
|
||||
|
||||
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
|
||||
// NodeJS
|
||||
module.exports = chaiAsPromised;
|
||||
} else if (typeof define === "function" && define.amd) {
|
||||
// AMD
|
||||
define([],function () {
|
||||
return chaiAsPromised;
|
||||
});
|
||||
} else {
|
||||
/*global self: false */
|
||||
|
||||
// Other environment (usually <script> tag): plug in to global chai instance directly.
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
// Expose as a property of the global object so that consumers can configure the `transferPromiseness` property.
|
||||
self.chaiAsPromised = chaiAsPromised;
|
||||
}
|
||||
|
||||
chaiAsPromised.transferPromiseness = function (assertion, promise) {
|
||||
assertion.then = promise.then.bind(promise);
|
||||
};
|
||||
|
||||
function chaiAsPromised(chai, utils) {
|
||||
var Assertion = chai.Assertion;
|
||||
var assert = chai.assert;
|
||||
|
||||
function isJQueryPromise(thenable) {
|
||||
return typeof thenable.always === "function" &&
|
||||
typeof thenable.done === "function" &&
|
||||
typeof thenable.fail === "function" &&
|
||||
typeof thenable.pipe === "function" &&
|
||||
typeof thenable.progress === "function" &&
|
||||
typeof thenable.state === "function";
|
||||
}
|
||||
|
||||
function assertIsAboutPromise(assertion) {
|
||||
if (typeof assertion._obj.then !== "function") {
|
||||
throw new TypeError(utils.inspect(assertion._obj) + " is not a thenable.");
|
||||
}
|
||||
if (isJQueryPromise(assertion._obj)) {
|
||||
throw new TypeError("Chai as Promised is incompatible with jQuery's thenables, sorry! Please use a " +
|
||||
"Promises/A+ compatible library (see http://promisesaplus.com/).");
|
||||
}
|
||||
}
|
||||
|
||||
function method(name, asserter) {
|
||||
utils.addMethod(Assertion.prototype, name, function () {
|
||||
assertIsAboutPromise(this);
|
||||
return asserter.apply(this, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
function property(name, asserter) {
|
||||
utils.addProperty(Assertion.prototype, name, function () {
|
||||
assertIsAboutPromise(this);
|
||||
return asserter.apply(this, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
function doNotify(promise, done) {
|
||||
promise.then(function () { done(); }, done);
|
||||
}
|
||||
|
||||
// These are for clarity and to bypass Chai refusing to allow `undefined` as actual when used with `assert`.
|
||||
function assertIfNegated(assertion, message, extra) {
|
||||
assertion.assert(true, null, message, extra.expected, extra.actual);
|
||||
}
|
||||
|
||||
function assertIfNotNegated(assertion, message, extra) {
|
||||
assertion.assert(false, message, null, extra.expected, extra.actual);
|
||||
}
|
||||
|
||||
function getBasePromise(assertion) {
|
||||
// We need to chain subsequent asserters on top of ones in the chain already (consider
|
||||
// `eventually.have.property("foo").that.equals("bar")`), only running them after the existing ones pass.
|
||||
// So the first base-promise is `assertion._obj`, but after that we use the assertions themselves, i.e.
|
||||
// previously derived promises, to chain off of.
|
||||
return typeof assertion.then === "function" ? assertion : assertion._obj;
|
||||
}
|
||||
|
||||
// Grab these first, before we modify `Assertion.prototype`.
|
||||
|
||||
var propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
|
||||
|
||||
var propertyDescs = {};
|
||||
propertyNames.forEach(function (name) {
|
||||
propertyDescs[name] = Object.getOwnPropertyDescriptor(Assertion.prototype, name);
|
||||
});
|
||||
|
||||
property("fulfilled", function () {
|
||||
var that = this;
|
||||
var derivedPromise = getBasePromise(that).then(
|
||||
function (value) {
|
||||
that._obj = value;
|
||||
assertIfNegated(that,
|
||||
"expected promise not to be fulfilled but it was fulfilled with #{act}",
|
||||
{ actual: value });
|
||||
return value;
|
||||
},
|
||||
function (reason) {
|
||||
assertIfNotNegated(that,
|
||||
"expected promise to be fulfilled but it was rejected with #{act}",
|
||||
{ actual: reason });
|
||||
}
|
||||
);
|
||||
|
||||
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||
});
|
||||
|
||||
property("rejected", function () {
|
||||
var that = this;
|
||||
var derivedPromise = getBasePromise(that).then(
|
||||
function (value) {
|
||||
that._obj = value;
|
||||
assertIfNotNegated(that,
|
||||
"expected promise to be rejected but it was fulfilled with #{act}",
|
||||
{ actual: value });
|
||||
return value;
|
||||
},
|
||||
function (reason) {
|
||||
assertIfNegated(that,
|
||||
"expected promise not to be rejected but it was rejected with #{act}",
|
||||
{ actual: reason });
|
||||
|
||||
// Return the reason, transforming this into a fulfillment, to allow further assertions, e.g.
|
||||
// `promise.should.be.rejected.and.eventually.equal("reason")`.
|
||||
return reason;
|
||||
}
|
||||
);
|
||||
|
||||
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||
});
|
||||
|
||||
method("rejectedWith", function (Constructor, message) {
|
||||
var desiredReason = null;
|
||||
var constructorName = null;
|
||||
|
||||
if (Constructor instanceof RegExp || typeof Constructor === "string") {
|
||||
message = Constructor;
|
||||
Constructor = null;
|
||||
} else if (Constructor && Constructor instanceof Error) {
|
||||
desiredReason = Constructor;
|
||||
Constructor = null;
|
||||
message = null;
|
||||
} else if (typeof Constructor === "function") {
|
||||
constructorName = (new Constructor()).name;
|
||||
} else {
|
||||
Constructor = null;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
var derivedPromise = getBasePromise(that).then(
|
||||
function (value) {
|
||||
var assertionMessage = null;
|
||||
var expected = null;
|
||||
|
||||
if (Constructor) {
|
||||
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
|
||||
"#{act}";
|
||||
expected = constructorName;
|
||||
} else if (message) {
|
||||
var verb = message instanceof RegExp ? "matching" : "including";
|
||||
assertionMessage = "expected promise to be rejected with an error " + verb + " #{exp} but it " +
|
||||
"was fulfilled with #{act}";
|
||||
expected = message;
|
||||
} else if (desiredReason) {
|
||||
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
|
||||
"#{act}";
|
||||
expected = desiredReason;
|
||||
}
|
||||
|
||||
that._obj = value;
|
||||
|
||||
assertIfNotNegated(that, assertionMessage, { expected: expected, actual: value });
|
||||
},
|
||||
function (reason) {
|
||||
if (Constructor) {
|
||||
that.assert(reason instanceof Constructor,
|
||||
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
|
||||
"expected promise not to be rejected with #{exp} but it was rejected with #{act}",
|
||||
constructorName,
|
||||
reason);
|
||||
}
|
||||
|
||||
var reasonMessage = utils.type(reason) === "object" && "message" in reason ?
|
||||
reason.message :
|
||||
"" + reason;
|
||||
if (message && reasonMessage !== null && reasonMessage !== undefined) {
|
||||
if (message instanceof RegExp) {
|
||||
that.assert(message.test(reasonMessage),
|
||||
"expected promise to be rejected with an error matching #{exp} but got #{act}",
|
||||
"expected promise not to be rejected with an error matching #{exp}",
|
||||
message,
|
||||
reasonMessage);
|
||||
}
|
||||
if (typeof message === "string") {
|
||||
that.assert(reasonMessage.indexOf(message) !== -1,
|
||||
"expected promise to be rejected with an error including #{exp} but got #{act}",
|
||||
"expected promise not to be rejected with an error including #{exp}",
|
||||
message,
|
||||
reasonMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (desiredReason) {
|
||||
that.assert(reason === desiredReason,
|
||||
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
|
||||
"expected promise not to be rejected with #{exp}",
|
||||
desiredReason,
|
||||
reason);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||
});
|
||||
|
||||
property("eventually", function () {
|
||||
utils.flag(this, "eventually", true);
|
||||
});
|
||||
|
||||
method("notify", function (done) {
|
||||
doNotify(getBasePromise(this), done);
|
||||
});
|
||||
|
||||
method("become", function (value) {
|
||||
return this.eventually.deep.equal(value);
|
||||
});
|
||||
|
||||
////////
|
||||
// `eventually`
|
||||
|
||||
// We need to be careful not to trigger any getters, thus `Object.getOwnPropertyDescriptor` usage.
|
||||
var methodNames = propertyNames.filter(function (name) {
|
||||
return name !== "assert" && typeof propertyDescs[name].value === "function";
|
||||
});
|
||||
|
||||
methodNames.forEach(function (methodName) {
|
||||
Assertion.overwriteMethod(methodName, function (originalMethod) {
|
||||
return function () {
|
||||
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
var getterNames = propertyNames.filter(function (name) {
|
||||
return name !== "_obj" && typeof propertyDescs[name].get === "function";
|
||||
});
|
||||
|
||||
getterNames.forEach(function (getterName) {
|
||||
var propertyDesc = propertyDescs[getterName];
|
||||
|
||||
// Chainable methods are things like `an`, which can work both for `.should.be.an.instanceOf` and as
|
||||
// `should.be.an("object")`. We need to handle those specially.
|
||||
var isChainableMethod = false;
|
||||
try {
|
||||
isChainableMethod = typeof propertyDesc.get.call({}) === "function";
|
||||
} catch (e) { }
|
||||
|
||||
if (isChainableMethod) {
|
||||
Assertion.addChainableMethod(
|
||||
getterName,
|
||||
function () {
|
||||
var assertion = this;
|
||||
function originalMethod() {
|
||||
return propertyDesc.get.call(assertion).apply(assertion, arguments);
|
||||
}
|
||||
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
||||
},
|
||||
function () {
|
||||
var originalGetter = propertyDesc.get;
|
||||
doAsserterAsyncAndAddThen(originalGetter, this);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Assertion.overwriteProperty(getterName, function (originalGetter) {
|
||||
return function () {
|
||||
doAsserterAsyncAndAddThen(originalGetter, this);
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function doAsserterAsyncAndAddThen(asserter, assertion, args) {
|
||||
// Since we're intercepting all methods/properties, we need to just pass through if they don't want
|
||||
// `eventually`, or if we've already fulfilled the promise (see below).
|
||||
if (!utils.flag(assertion, "eventually")) {
|
||||
return asserter.apply(assertion, args);
|
||||
}
|
||||
|
||||
var derivedPromise = getBasePromise(assertion).then(function (value) {
|
||||
// Set up the environment for the asserter to actually run: `_obj` should be the fulfillment value, and
|
||||
// now that we have the value, we're no longer in "eventually" mode, so we won't run any of this code,
|
||||
// just the base Chai code that we get to via the short-circuit above.
|
||||
assertion._obj = value;
|
||||
utils.flag(assertion, "eventually", false);
|
||||
asserter.apply(assertion, args);
|
||||
|
||||
// Because asserters, for example `property`, can change the value of `_obj` (i.e. change the "object"
|
||||
// flag), we need to communicate this value change to subsequent chained asserters. Since we build a
|
||||
// promise chain paralleling the asserter chain, we can use it to communicate such changes.
|
||||
return assertion._obj;
|
||||
});
|
||||
|
||||
chaiAsPromised.transferPromiseness(assertion, derivedPromise);
|
||||
}
|
||||
|
||||
///////
|
||||
// Now use the `Assertion` framework to build an `assert` interface.
|
||||
var originalAssertMethods = Object.getOwnPropertyNames(assert).filter(function (propName) {
|
||||
return typeof assert[propName] === "function";
|
||||
});
|
||||
|
||||
assert.isFulfilled = function (promise, message) {
|
||||
return (new Assertion(promise, message)).to.be.fulfilled;
|
||||
};
|
||||
|
||||
assert.isRejected = function (promise, toTestAgainst, message) {
|
||||
if (typeof toTestAgainst === "string") {
|
||||
message = toTestAgainst;
|
||||
toTestAgainst = undefined;
|
||||
}
|
||||
|
||||
var assertion = (new Assertion(promise, message));
|
||||
return toTestAgainst !== undefined ? assertion.to.be.rejectedWith(toTestAgainst) : assertion.to.be.rejected;
|
||||
};
|
||||
|
||||
assert.becomes = function (promise, value, message) {
|
||||
return assert.eventually.deepEqual(promise, value, message);
|
||||
};
|
||||
|
||||
assert.doesNotBecome = function (promise, value, message) {
|
||||
return assert.eventually.notDeepEqual(promise, value, message);
|
||||
};
|
||||
|
||||
assert.eventually = {};
|
||||
originalAssertMethods.forEach(function (assertMethodName) {
|
||||
assert.eventually[assertMethodName] = function (promise) {
|
||||
var otherArgs = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
var customRejectionHandler;
|
||||
var message = arguments[assert[assertMethodName].length - 1];
|
||||
if (typeof message === "string") {
|
||||
customRejectionHandler = function (reason) {
|
||||
throw new chai.AssertionError(message + "\n\nOriginal reason: " + utils.inspect(reason));
|
||||
};
|
||||
}
|
||||
|
||||
var returnedPromise = promise.then(
|
||||
function (fulfillmentValue) {
|
||||
return assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs));
|
||||
},
|
||||
customRejectionHandler
|
||||
);
|
||||
|
||||
returnedPromise.notify = function (done) {
|
||||
doNotify(returnedPromise, done);
|
||||
};
|
||||
|
||||
return returnedPromise;
|
||||
};
|
||||
});
|
||||
}
|
||||
}());
|
||||
4804
assets/webapps/calendar.gaiamobile.org/js/ext/chai.js
Normal file
4804
assets/webapps/calendar.gaiamobile.org/js/ext/chai.js
Normal file
File diff suppressed because it is too large
Load diff
6679
assets/webapps/calendar.gaiamobile.org/js/ext/ical.js
Normal file
6679
assets/webapps/calendar.gaiamobile.org/js/ext/ical.js
Normal file
File diff suppressed because it is too large
Load diff
1625
assets/webapps/calendar.gaiamobile.org/js/ext/threads.js
Normal file
1625
assets/webapps/calendar.gaiamobile.org/js/ext/threads.js
Normal file
File diff suppressed because it is too large
Load diff
17
assets/webapps/calendar.gaiamobile.org/js/format.js
Normal file
17
assets/webapps/calendar.gaiamobile.org/js/format.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
define(['require','exports','module'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var FORMAT_REGEX = /%([0-9])?s/g;
|
||||
|
||||
module.exports = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
|
||||
var i = 0;
|
||||
var str = args.shift();
|
||||
return str.replace(FORMAT_REGEX, function(match, pos) {
|
||||
var index = parseInt(pos || i++, 10);
|
||||
return args[index];
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
5902
assets/webapps/calendar.gaiamobile.org/js/lazy_loaded.js
Normal file
5902
assets/webapps/calendar.gaiamobile.org/js/lazy_loaded.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,71 @@
|
|||
define(['require','exports','module','common/calc'],function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Calc = require('common/calc');
|
||||
|
||||
/**
|
||||
* This is a more crude version of what asuth does in email.
|
||||
* Right now this class is here only for debugging sync issues.
|
||||
* We need to add the settings so we can optionally turn this on.
|
||||
*/
|
||||
function EventLogger() {
|
||||
this.events = Object.create(null);
|
||||
this.occurs = Object.create(null);
|
||||
|
||||
this.richLog = {};
|
||||
}
|
||||
module.exports = EventLogger;
|
||||
|
||||
EventLogger.prototype = {
|
||||
displayLog: function(id, string) {
|
||||
if (!(id in this.richLog)) {
|
||||
this.richLog[id] = [];
|
||||
}
|
||||
this.richLog[id].push(string);
|
||||
},
|
||||
|
||||
addEvent: function(event) {
|
||||
var id = event.id;
|
||||
this.events[id] = event;
|
||||
var log = this.formatEvent(event);
|
||||
this.displayLog(id, log);
|
||||
},
|
||||
|
||||
addBusytime: function(busy) {
|
||||
var id = busy.eventId;
|
||||
this.occurs[id] = busy;
|
||||
this.displayLog(id, this.formatBusytime(busy));
|
||||
},
|
||||
|
||||
formatEvent: function(event) {
|
||||
var format = [
|
||||
'add event: (' + event.id + ')',
|
||||
'title:' + event.title,
|
||||
'start:' + (Calc.dateFromTransport(event.start)).toString(),
|
||||
'end:' + (Calc.dateFromTransport(event.end)).toString(),
|
||||
'isException:' + event.isException
|
||||
];
|
||||
|
||||
return format.join(' || ');
|
||||
},
|
||||
|
||||
formatBusytime: function(busy) {
|
||||
var event = this.events[busy.eventId];
|
||||
var title = 'busytime for event: ' + busy.eventId;
|
||||
|
||||
if (event) {
|
||||
title = 'busytime for event: ' + event.title;
|
||||
}
|
||||
|
||||
var format = [
|
||||
title,
|
||||
'start:' + (Calc.dateFromTransport(busy.start)).toString(),
|
||||
'end:' + (Calc.dateFromTransport(busy.end)).toString(),
|
||||
'isException:' + busy.isException
|
||||
];
|
||||
|
||||
return format.join(' || ');
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
18
assets/webapps/calendar.gaiamobile.org/jsdoc.json
Normal file
18
assets/webapps/calendar.gaiamobile.org/jsdoc.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "calendar",
|
||||
"tags": {
|
||||
"dictionaries": ["jsdoc"]
|
||||
},
|
||||
"source": {
|
||||
"include": ["!apps/calendar/js/ext/**/*.js", "apps/calendar/js/**/*.js"]
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown",
|
||||
"plugins/summarize"
|
||||
],
|
||||
"opts": {
|
||||
"private": true,
|
||||
"recurse": true,
|
||||
"destination": "docs/calendar"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
assets/webapps/calendar.gaiamobile.org/manifest.webapp
Executable file
1
assets/webapps/calendar.gaiamobile.org/manifest.webapp
Executable file
|
|
@ -0,0 +1 @@
|
|||
{"name":"Calendar","description":"Gaia Calendar","type":"certified","launch_path":"/index.html","developer":{"name":"The Gaia Team","url":"https://github.com/mozilla-b2g/gaia"},"messages":[{"alarm":"/index.html"},{"notification":"/index.html"}],"permissions":{"themeable":{},"systemXHR":{},"alarms":{},"browser":{},"settings":{"access":"readonly"},"desktop-notification":{},"audio-channel-notification":{},"moz-extremely-unstable-and-will-change-webcomponents":{}},"locales":{"ar":{"name":"التقويم","description":"تقويم چايا"},"bg":{"name":"Календар","description":"Gaia Календар"},"bn-BD":{"name":"ক্যালেন্ডার","description":"গায়া ক্যালেন্ডার"},"ca":{"name":"Calendari","description":"Calendari del Gaia"},"cs":{"name":"Kalendář","description":"Kalendář systému Gaia"},"da":{"name":"Kalender","description":"Gaia-kalender"},"de":{"name":"Kalender","description":"Kalender für Gaia"},"el":{"name":"Ημερολόγιο","description":"Ημερολόγιο του Gaia"},"en-US":{"name":"Calendar","description":"Gaia Calendar"},"es":{"name":"Calendario","description":"Calendario de Gaia"},"eu":{"name":"Egutegia","description":"Gaia egutegia"},"fr":{"name":"Agenda","description":"Agenda de Gaia"},"gl":{"name":"Calendario","description":"Calendario de Gaia"},"hr":{"name":"Kalendar","description":"Gaia kalendar"},"hu":{"name":"Naptár","description":"Gaia naptár"},"it":{"name":"Agenda","description":"Agenda Gaia"},"ja":{"name":"カレンダー","description":"Gaia カレンダー"},"km":{"name":"ប្រតិទិន","description":"ប្រតិទិន Gaia"},"ko":{"name":"캘린더","description":"Gaia 캘린더"},"lt":{"name":"Kalendorius","description":"„Gaia“ kalendorius"},"mk":{"name":"Календар","description":"Календар на Гаја"},"ms":{"name":"Kalendar","description":"Kalendar Gaia"},"ne-NP":{"name":"पात्रो","description":"गाइया पात्रो"},"nl":{"name":"Agenda","description":"Gaia-agenda"},"pa":{"name":"ਕੈਲੰਡਰ","description":"ਗੀਗਾ ਕੈਲੰਡਰ"},"pl":{"name":"Kalendarz","description":"Kalendarz Gaia"},"pt-BR":{"name":"Calendário","description":"Gaia Calendar"},"ro":{"name":"Calendar","description":"Calendar Gaia"},"ru":{"name":"Календарь","description":"Календарь Gaia"},"sk":{"name":"Kalendár","description":"Kalendár Gaia"},"sq":{"name":"Kalendar","description":"Kalendari Gaia"},"sr-Cyrl":{"name":"Календар","description":"Gaia календар"},"sr-Latn":{"name":"Kalendar","description":"Gaia kalendar"},"sv-SE":{"name":"Kalender","description":"Gaia kalender"},"tr":{"name":"Takvim","description":"Gaia Takvim"},"zh-CN":{"name":"日历","description":"Gaia 日历"},"zh-TW":{"name":"行事曆","description":"Gaia 行事曆"}},"default_locale":"en-US","icons":{"84":"/style/icons/calendar_84.png","126":"/style/icons/calendar_126.png","142":"/style/icons/calendar_142.png","189":"/style/icons/calendar_189.png","284":"/style/icons/calendar_284.png"},"orientation":"default","origin":"app://calendar.gaiamobile.org","version":"2.5"}
|
||||
|
|
@ -0,0 +1 @@
|
|||
;window.COMPONENTS_BASE_URL='/shared/elements/';
|
||||
373
assets/webapps/calendar.gaiamobile.org/shared/elements/gaia-header/dist/gaia-header.js
vendored
Normal file
373
assets/webapps/calendar.gaiamobile.org/shared/elements/gaia-header/dist/gaia-header.js
vendored
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
;(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.GaiaHeader=f()}})(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){;(function(define){define(function(require,exports,module){'use strict';var debug=0?console.log.bind(console):function(){};var cache={};var MIN=16;var MAX=24;var BUFFER=3;module.exports=function(config){debug('font fit',config);var space=config.space-BUFFER;var min=config.min||MIN;var max=config.max||MAX;var text=trim(config.text);var fontSize=max;var textWidth;var font;do{font=config.font.replace(/\d+px/,fontSize+'px');textWidth=getTextWidth(text,font);}while(textWidth>space&&fontSize!==min&&fontSize--);return{textWidth:textWidth,fontSize:fontSize,overflowing:textWidth>space};};function getTextWidth(text,font){var ctx=getCanvasContext(font);var width=ctx.measureText(text).width;debug('got text width',width);return width;}
|
||||
function getCanvasContext(font){debug('get canvas context',font);var cached=cache[font];if(cached){return cached;}
|
||||
var canvas=document.createElement('canvas');canvas.setAttribute('moz-opaque','true');canvas.setAttribute('width','1px');canvas.setAttribute('height','1px');debug('created canvas',canvas);var ctx=canvas.getContext('2d',{willReadFrequently:true});ctx.font=font;return cache[font]=ctx;}
|
||||
function trim(text){return text.replace(/\s+/g,' ').trim();}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('font-fit',this));},{}],2:[function(require,module,exports){;(function(define){'use strict';define(function(require,exports,module){var textContent=Object.getOwnPropertyDescriptor(Node.prototype,'textContent');var innerHTML=Object.getOwnPropertyDescriptor(Element.prototype,'innerHTML');var removeAttribute=Element.prototype.removeAttribute;var setAttribute=Element.prototype.setAttribute;var noop=function(){};exports.register=function(name,props){var baseProto=getBaseProto(props.extends);var template=props.template||baseProto.templateString;var extensible=props.extensible=props.hasOwnProperty('extensible')?props.extensible:true;delete props.extends;if(template){if(extensible&&props.template){props.templateString=props.template;}
|
||||
var output=processCss(template,name);props.template=document.createElement('template');props.template.innerHTML=output.template;props.lightCss=output.lightCss;props.globalCss=props.globalCss||'';props.globalCss+=output.globalCss;}
|
||||
injectGlobalCss(props.globalCss);delete props.globalCss;var descriptors=mixin(props.attrs||{},base.descriptors);props._attrs=props.attrs;delete props.attrs;var proto=createProto(baseProto,props);Object.defineProperties(proto,descriptors);try{return document.registerElement(name,{prototype:proto});}catch(e){if(e.name!=='NotSupportedError'){throw e;}}};var base={properties:{GaiaComponent:true,attributeChanged:noop,attached:noop,detached:noop,created:noop,createdCallback:function(){if(this.dirObserver){addDirObserver();}
|
||||
injectLightCss(this);this.created();},attributeChangedCallback:function(name,from,to){var prop=toCamelCase(name);if(this._attrs&&this._attrs[prop]){this[prop]=to;}
|
||||
this.attributeChanged(name,from,to);},attachedCallback:function(){if(this.dirObserver){this.setInnerDirAttributes=setInnerDirAttributes.bind(null,this);document.addEventListener('dirchanged',this.setInnerDirAttributes);}
|
||||
this.attached();},detachedCallback:function(){if(this.dirObserver){document.removeEventListener('dirchanged',this.setInnerDirAttributes);}
|
||||
this.detached();},setupShadowRoot:function(){if(!this.template){return;}
|
||||
var node=document.importNode(this.template.content,true);this.createShadowRoot().appendChild(node);if(this.dirObserver){setInnerDirAttributes(this);}
|
||||
return this.shadowRoot;},setAttr:function(name,value){var internal=this.shadowRoot.firstElementChild;setAttribute.call(internal,name,value);setAttribute.call(this,name,value);},removeAttr:function(name){var internal=this.shadowRoot.firstElementChild;removeAttribute.call(internal,name);removeAttribute.call(this,name);}},descriptors:{textContent:{set:function(value){textContent.set.call(this,value);if(this.lightStyle){this.appendChild(this.lightStyle);}},get:function(){return textContent.get();}},innerHTML:{set:function(value){innerHTML.set.call(this,value);if(this.lightStyle){this.appendChild(this.lightStyle);}},get:innerHTML.get}}};var defaultPrototype=createProto(HTMLElement.prototype,base.properties);function getBaseProto(proto){if(!proto){return defaultPrototype;}
|
||||
proto=proto.prototype||proto;return!proto.GaiaComponent?createProto(proto,base.properties):proto;}
|
||||
function createProto(proto,props){return mixin(Object.create(proto),props);}
|
||||
var hasShadowCSS=(function(){var div=document.createElement('div');try{div.querySelector(':host');div.querySelector('::content');return true;}
|
||||
catch(e){return false;}})();var regex={shadowCss:/(?:\:host|\:\:content)[^{]*\{[^}]*\}/g,':host':/(?:\:host)/g,':host()':/\:host\((.+)\)(?: \:\:content)?/g,':host-context':/\:host-context\((.+)\)([^{,]+)?/g,'::content':/(?:\:\:content)/g};function processCss(template,name){var globalCss='';var lightCss='';if(!hasShadowCSS){template=template.replace(regex.shadowCss,function(match){var hostContext=regex[':host-context'].exec(match);if(hostContext){globalCss+=match.replace(regex['::content'],'').replace(regex[':host-context'],'$1 '+name+'$2').replace(/ +/g,' ');}else{lightCss+=match.replace(regex[':host()'],name+'$1').replace(regex[':host'],name).replace(regex['::content'],name);}
|
||||
return'';});}
|
||||
return{template:template,lightCss:lightCss,globalCss:globalCss};}
|
||||
function injectGlobalCss(css){if(!css){return;}
|
||||
var style=document.createElement('style');style.innerHTML=css.trim();headReady().then(function(){document.head.appendChild(style);});}
|
||||
function headReady(){return new Promise(function(resolve){if(document.head){return resolve();}
|
||||
window.addEventListener('load',function fn(){window.removeEventListener('load',fn);resolve();});});}
|
||||
function injectLightCss(el){if(hasShadowCSS){return;}
|
||||
var stylesheet=el.querySelector('style');if(!stylesheet){stylesheet=document.createElement('style');stylesheet.setAttribute('scoped','');stylesheet.appendChild(document.createTextNode(el.lightCss));el.appendChild(stylesheet);}
|
||||
el.lightStyle=stylesheet;}
|
||||
function toCamelCase(string){return string.replace(/-(.)/g,function replacer(string,p1){return p1.toUpperCase();});}
|
||||
var dirObserver;function setInnerDirAttributes(component){var dir=component.dir||document.dir;Array.from(component.shadowRoot.children).forEach(element=>{if(element.nodeName!=='STYLE'){element.dir=dir;}});}
|
||||
function addDirObserver(){if(dirObserver){return;}
|
||||
dirObserver=new MutationObserver(onChanged);dirObserver.observe(document.documentElement,{attributeFilter:['dir'],attributes:true});function onChanged(mutations){document.dispatchEvent(new Event('dirchanged'));}}
|
||||
function mixin(target,source){for(var key in source){target[key]=source[key];}
|
||||
return target;}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-component',this));},{}],3:[function(require,module,exports){(function(define){'use strict';define(function(require,exports,module){var base=window.GAIA_ICONS_BASE_URL||window.COMPONENTS_BASE_URL||'bower_components/';if(!document.documentElement){window.addEventListener('load',load);}else{load();}
|
||||
function load(){if(isLoaded()){return;}
|
||||
var link=document.createElement('link');link.rel='stylesheet';link.type='text/css';link.href=base+'gaia-icons/gaia-icons.css';document.head.appendChild(link);exports.loaded=true;}
|
||||
function isLoaded(){return exports.loaded||document.querySelector('link[href*=gaia-icons]')||document.documentElement.classList.contains('gaia-icons-loaded');}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-icons',this));},{}],4:[function(require,module,exports){;(function(define){'use strict';define(function(require,exports,module){var component=require('gaia-component');var fontFit=require('font-fit');require('gaia-icons');var debug=0?console.log.bind(console):function(){};const KNOWN_ACTIONS={menu:'menu',back:'back',close:'close'};const TITLE_FONT='italic 300 24px FiraSans';const TITLE_PADDING=10;const MINIMUM_FONT_SIZE_CENTERED=20;const MINIMUM_FONT_SIZE_UNCENTERED=16;const MAXIMUM_FONT_SIZE=23;module.exports=component.register('gaia-header',{extensible:false,dirObserver:true,created:function(){debug('created');this.setupShadowRoot();this.els={actionButton:this.shadowRoot.querySelector('.action-button'),titles:this.getElementsByTagName('h1')};this.els.actionButton.addEventListener('click',e=>this.onActionButtonClick(e));this.observer=new MutationObserver(this.onMutation.bind(this));this.ignoreDir=this.hasAttribute('ignore-dir');this.titleEnd=this.getAttribute('title-end');this.titleStart=this.getAttribute('title-start');this.noFontFit=this.getAttribute('no-font-fit');this.notFlush=this.hasAttribute('not-flush');this.action=this.getAttribute('action');this.unresolved={};this.pending={};this._resizeThrottlingId=null;this.onResize=this.onResize.bind(this);},attached:function(){debug('attached');this.runFontFitSoon();this.observerStart();window.addEventListener('resize',this.onResize);},detached:function(){debug('detached');window.removeEventListener('resize',this.onResize);this.observerStop();this.clearPending();},clearPending:function(){for(var key in this.pending){this.pending[key].clear();delete this.pending[key];}
|
||||
window.cancelAnimationFrame(this._resizeThrottlingId);this._resizeThrottlingId=null;},runFontFit:function(){debug('run font-fit');if(this.noFontFit){return Promise.resolve();}
|
||||
var titles=this.els.titles;var space=this.getTitleSpace();var styles=[].map.call(titles,el=>this.getTitleStyle(el,space));return this.setTitleStylesSoon(styles);},runFontFitSoon:function(){debug('run font-fit soon');if(this.pending.runFontFitSoon){return;}
|
||||
this.pending.runFontFitSoon=this.nextTick(()=>{delete this.pending.runFontFitSoon;this.runFontFit();});},getTitleStyle:function(el,space){debug('get el style',el,space);var text=el.textContent;var styleId=space.start+text+space.end+'#'+space.value;if(!text||!text.trim()){return debug('exit: no text');}
|
||||
if(getStyleId(el)===styleId){return debug('exit: no change');}
|
||||
var marginStart=this.getTitleMarginStart();var textSpace=space.value-Math.abs(marginStart);var fontFitResult=this.fontFit(text,textSpace,{min:MINIMUM_FONT_SIZE_CENTERED});var overflowing=fontFitResult.overflowing;var padding={start:0,end:0};if(overflowing){debug('title overflowing');padding.start=!space.start?TITLE_PADDING:0;padding.end=!space.end?TITLE_PADDING:0;textSpace=space.value-padding.start-padding.end;fontFitResult=this.fontFit(text,textSpace);marginStart=0;}
|
||||
return{id:styleId,fontSize:fontFitResult.fontSize,marginStart:marginStart,overflowing:overflowing,padding:padding};},setTitleStylesSoon:function(styles){debug('set title styles soon',styles);var key='setStyleTitlesSoon';this._titleStyles=styles;if(this.unresolved[key]){return this.unresolved[key];}
|
||||
this.unresolved[key]=new Promise((resolve)=>{this.pending[key]=this.nextTick(()=>{var styles=this._titleStyles;var els=this.els.titles;[].forEach.call(els,(el,i)=>{if(!styles[i]){return debug('exit');}
|
||||
this.setTitleStyle(el,styles[i]);});delete this._titleStyles;delete this.unresolved[key];delete this.pending[key];resolve();});});},setTitleStyle:function(el,style){debug('set title style',style);this.observerStop();if(this.ignoreDir){el.style.marginLeft=style.marginStart+'px';el.style.paddingLeft=style.padding.start+'px';el.style.paddingRight=style.padding.end+'px';}else{el.style.marginInlineStart=style.marginStart+'px';el.style.paddingInlineStart=style.padding.start+'px';el.style.paddingInlineEnd=style.padding.end+'px';}
|
||||
el.style.fontSize=style.fontSize+'px';setStyleId(el,style.id);this.observerStart();},fontFit:function(text,space,opts={}){debug('font fit:',text,space,opts);var fontFitArgs={font:TITLE_FONT,min:opts.min||MINIMUM_FONT_SIZE_UNCENTERED,max:MAXIMUM_FONT_SIZE,text:text,space:space};return fontFit(fontFitArgs);},observerStart:function(){if(this.observing){return;}
|
||||
this.observer.observe(this,{childList:true,attributes:true,subtree:true});this.observing=true;debug('observer started');},observerStop:function(){if(!this.observing){return;}
|
||||
this.observer.disconnect();this.observing=false;debug('observer stopped');},onResize:function(e){debug('onResize',this._resizeThrottlingId);if(this._resizeThrottlingId!==null){return;}
|
||||
this._resizeThrottlingId=window.requestAnimationFrame(()=>{this._resizeThrottlingId=null;this.runFontFitSoon();});},onMutation:function(mutations){debug('on mutation',mutations);if(!this.pending.runFontFitSoon){this.runFontFit();}},getTitleSpace:function(){var start=this.titleStart;var end=this.titleEnd;var space=this.getWidth()-start-end;var result={value:space,start:start,end:end};debug('get title space',result);return result;},getWidth:function(){var value=this.notFlush?this.clientWidth:window.innerWidth;debug('get width',value);return value;},triggerAction:function(){if(this.action){this.els.actionButton.click();}},onActionButtonClick:function(){debug('action button click');var config={detail:{type:this.action}};var e=new CustomEvent('action',config);setTimeout(()=>this.dispatchEvent(e));},getTitleMarginStart:function(){var start=this.titleStart;var end=this.titleEnd;var marginStart=end-start;debug('get title margin start',marginStart);return marginStart;},getButtonsBeforeTitle:function(){var children=this.children;var l=children.length;var els=[];for(var i=0;i<l;i++){var el=children[i];if(el.tagName==='H1'){break;}
|
||||
if(!contributesToLayout(el)){continue;}
|
||||
els.push(el);}
|
||||
if(this.action){els.push(this.els.actionButton);}
|
||||
return els;},getButtonsAfterTitle:function(){var children=this.children;var els=[];for(var i=children.length-1;i>=0;i--){var el=children[i];if(el.tagName==='H1'){break;}
|
||||
if(!contributesToLayout(el)){continue;}
|
||||
els.push(el);}
|
||||
return els;},sumButtonWidths:function(buttons){var defaultWidth=50;var sum=buttons.reduce((prev,button)=>{var isStandardButton=button===this.els.actionButton;var width=isStandardButton?defaultWidth:button.clientWidth;return prev+width;},0);debug('sum button widths',buttons,sum);return sum;},attrs:{action:{get:function(){return this._action;},set:function(value){var action=KNOWN_ACTIONS[value];if(action===this._action){return;}
|
||||
this.setAttr('action',action);this._action=action;}},titleStart:{get:function(){debug('get title-start');if('_titleStart'in this){return this._titleStart;}
|
||||
var buttons=this.getButtonsBeforeTitle();var value=this.sumButtonWidths(buttons);debug('get title-start',buttons,value);return value;},set:function(value){debug('set title-start',value);value=parseInt(value,10);if(value===this._titleStart||isNaN(value)){return;}
|
||||
this.setAttr('title-start',value);this._titleStart=value;debug('set');}},titleEnd:{get:function(){debug('get title-end');if('_titleEnd'in this){return this._titleEnd;}
|
||||
var buttons=this.getButtonsAfterTitle();return this.sumButtonWidths(buttons);},set:function(value){debug('set title-end',value);value=parseInt(value,10);if(value===this._titleEnd||isNaN(value)){return;}
|
||||
this.setAttr('title-end',value);this._titleEnd=value;}},noFontFit:{get:function(){return this._noFontFit||false;},set:function(value){debug('set no-font-fit',value);value=!!(value||value==='');if(value===this.noFontFit){return;}
|
||||
this._noFontFit=value;if(value){this.setAttr('no-font-fit','');}
|
||||
else{this.removeAttr('no-font-fit');}}},ignoreDir:{get:function(){return this._ignoreDir||false;},set:function(value){debug('set ignore-dir',value);value=!!(value||value==='');if(value===this.ignoreDir){return;}
|
||||
this._ignoreDir=value;this.dirObserver=!value;if(value){this.setAttr('ignore-dir','');}
|
||||
else{this.removeAttr('ignore-dir');}}}},template:`<div class="inner">
|
||||
<button class="action-button">
|
||||
<content select="[l10n-action]"></content>
|
||||
</button>
|
||||
<content></content>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
-moz-user-select: none;
|
||||
|
||||
--gaia-header-button-color:
|
||||
var(--header-button-color,
|
||||
var(--header-color,
|
||||
var(--link-color,
|
||||
inherit)));
|
||||
}
|
||||
|
||||
/**
|
||||
* [hidden]
|
||||
*/
|
||||
|
||||
:host[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/** Reset
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::-moz-focus-inner { border: 0; }
|
||||
|
||||
/** Inner
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
min-height: 50px;
|
||||
-moz-user-select: none;
|
||||
|
||||
background:
|
||||
var(--header-background,
|
||||
var(--background,
|
||||
#fff));
|
||||
}
|
||||
|
||||
/** Action Button
|
||||
---------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 1. Hidden by default
|
||||
*/
|
||||
|
||||
.action-button {
|
||||
position: relative;
|
||||
|
||||
display: none; /* 1 */
|
||||
width: 50px;
|
||||
font-size: 30px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
|
||||
align-items: center;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
transition: opacity 200ms 280ms;
|
||||
color:
|
||||
var(--header-action-button-color,
|
||||
var(--header-icon-color,
|
||||
var(--gaia-header-button-color)));
|
||||
}
|
||||
|
||||
/**
|
||||
* [action=back]
|
||||
* [action=menu]
|
||||
* [action=close]
|
||||
*
|
||||
* 1. For icon vertical-alignment
|
||||
*/
|
||||
|
||||
[action=back] .action-button,
|
||||
[action=menu] .action-button,
|
||||
[action=close] .action-button {
|
||||
display: flex; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* :active
|
||||
*/
|
||||
|
||||
.action-button:active {
|
||||
transition: none;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
/** Action Button Icon
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.action-button:before {
|
||||
font-family: 'gaia-icons';
|
||||
font-style: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
[action=close] .action-button:before { content: 'close' }
|
||||
[action=menu] .action-button:before { content: 'menu' }
|
||||
|
||||
[action=back]:-moz-dir(ltr) .action-button:before { content: 'left' }
|
||||
[action=back]:-moz-dir(rtl) .action-button:before { content: 'right' }
|
||||
|
||||
/** Action Button Icon
|
||||
---------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 1. To enable vertical alignment.
|
||||
*/
|
||||
|
||||
.action-button:before {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/** Action Button Text
|
||||
---------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* To provide custom localized content for
|
||||
* the action-button, we allow the user
|
||||
* to provide an element with the class
|
||||
* .l10n-action. This node is then
|
||||
* pulled inside the real action-button.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <gaia-header action="back">
|
||||
* <span l10n-action aria-label="Back">Localized text</span>
|
||||
* <h1>title</h1>
|
||||
* </gaia-header>
|
||||
*/
|
||||
|
||||
::content [l10n-action] {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
/** Title
|
||||
---------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 1. Vertically center text. We can't use flexbox
|
||||
* here as it breaks text-overflow ellipsis
|
||||
* without an inner div.
|
||||
*/
|
||||
|
||||
::content h1 {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
line-height: 50px; /* 1 */
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
font-size: 24px;
|
||||
|
||||
color:
|
||||
var(--header-title-color,
|
||||
var(--header-color,
|
||||
var(--title-color,
|
||||
var(--text-color,
|
||||
inherit))));
|
||||
}
|
||||
|
||||
/**
|
||||
* [ignore-dir]
|
||||
*
|
||||
* When the <gaia-header> component has an [ignore-dir] attribute, header
|
||||
* direction is forced to LTR but we still want the <h1> text to be reversed
|
||||
* so that strings like '1 selected' become 'selected 1'.
|
||||
*
|
||||
* When we're happy for <gaia-header> to be fully RTL responsive we won't need
|
||||
* these rules anymore, but this depends on all Gaia apps being ready.
|
||||
*
|
||||
* This should be safe to remove when bug 1179459 lands.
|
||||
*/
|
||||
|
||||
:host[ignore-dir] {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
:host[ignore-dir]:-moz-dir(rtl) h1 {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
/** Buttons
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content a,
|
||||
::content button {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-width: 50px;
|
||||
margin: 0;
|
||||
padding: 0 10px;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
background: none;
|
||||
border-radius: 0;
|
||||
font-style: italic;
|
||||
cursor: pointer;
|
||||
transition: opacity 200ms 280ms;
|
||||
color: var(--gaia-header-button-color);
|
||||
}
|
||||
|
||||
/**
|
||||
* :active
|
||||
*/
|
||||
|
||||
::content a:active,
|
||||
::content button:active {
|
||||
transition: none;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
/**
|
||||
* [hidden]
|
||||
*/
|
||||
|
||||
::content a[hidden],
|
||||
::content button[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* [disabled]
|
||||
*/
|
||||
|
||||
::content a[disabled],
|
||||
::content button[disabled] {
|
||||
pointer-events: none;
|
||||
color: var(--header-disabled-button-color);
|
||||
}
|
||||
|
||||
/** Icon Buttons
|
||||
---------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Icons are a different color to text
|
||||
*/
|
||||
|
||||
::content .icon,
|
||||
::content [data-icon] {
|
||||
color:
|
||||
var(--header-icon-color,
|
||||
var(--gaia-header-button-color));
|
||||
}
|
||||
|
||||
/**
|
||||
* If users want their action button
|
||||
* to be in the component's light-dom
|
||||
* they can add an .action class
|
||||
* to make it look like the
|
||||
* shadow action button.
|
||||
*/
|
||||
|
||||
::content .action {
|
||||
color:
|
||||
var(--header-action-button-color,
|
||||
var(--header-icon-color,
|
||||
var(--gaia-header-button-color)));
|
||||
}
|
||||
|
||||
/**
|
||||
* [data-icon]:empty
|
||||
*
|
||||
* Icon buttons with no textContent,
|
||||
* should always be 50px.
|
||||
*
|
||||
* This is to prevent buttons being
|
||||
* larger than they should be before
|
||||
* icon-font has loaded.
|
||||
*/
|
||||
|
||||
::content [data-icon]:empty {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
</style>`,nextTick:nextTick});function contributesToLayout(el){return el.localName!=='style'&&!el.hasAttribute('l10n-action');}
|
||||
function setStyleId(el,id){el._styleId=id;}
|
||||
function getStyleId(el){return el._styleId;}
|
||||
function nextTick(fn){var cleared;Promise.resolve().then(()=>{if(!cleared){fn();}});return{clear:function(){cleared=true;}};}});})(typeof define=='function'&&define.amd?define:(function(n,w){'use strict';return typeof module=='object'?function(c){c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-header',this));},{"font-fit":1,"gaia-component":2,"gaia-icons":3}]},{},[4])(4)});
|
||||
Binary file not shown.
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
@font-face {
|
||||
font-family: "gaia-icons";
|
||||
src: url("fonts/gaia-icons.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
[data-icon]:before,
|
||||
.ligature-icons {
|
||||
font-family: "gaia-icons";
|
||||
content: attr(data-icon);
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
text-decoration: inherit;
|
||||
text-transform: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-size: 30px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):before {
|
||||
|
||||
background: rgba(255, 0, 0, 0.4);
|
||||
}
|
||||
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):after {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
width: 20rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
content: "Icon is not localized and is still visible to screen reader. Please add data-l10n-id or aria-label for localization or use aria-hidden to hide the icon from screen reader.";
|
||||
z-index: 9999;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 583 B |
Binary file not shown.
|
After Width: | Height: | Size: 583 B |
|
|
@ -0,0 +1,5 @@
|
|||
;window.GaiaCheckbox=(function(win){var proto=Object.create(HTMLElement.prototype);var baseurl=window.GaiaCheckboxBaseurl||'/shared/elements/gaia_checkbox/';proto.createdCallback=function(){this.lastClick=0;this.throttleTime=250;var shadow=this.createShadowRoot();this._template=template.content.cloneNode(true);this._wrapper=this._template.getElementById('checkbox');this._wrapper.addEventListener('click',this.handleClick.bind(this));this.configureClass();this.checked=this.hasAttribute('checked');this._wrapper.setAttribute('aria-checked',this.checked);this.setAttribute('role','presentation');shadow.appendChild(this._template);ComponentUtils.style.call(this,baseurl);};proto.handleClick=function(e){e.preventDefault();e.stopImmediatePropagation();if(this.lastClick+this.throttleTime>Date.now()){return;}
|
||||
this.lastClick=Date.now();var event=new MouseEvent('click',{view:window,bubbles:true,cancelable:true});this.dispatchEvent(event);if(!event.defaultPrevented){this.checked=!this.checked;this._wrapper.setAttribute('aria-checked',this.checked);}
|
||||
this.dispatchEvent(new CustomEvent('change',{bubbles:true,cancelable:false}));};proto.configureClass=function(){this._wrapper.className=this.className;};proto.attributeChangedCallback=function(name,from,to){if(name==='class'){this._wrapper.className=to;}};Object.defineProperty(proto,'checked',{get:function(){return this._checked||false;},set:function(value){this._wrapper.classList.toggle('checked',value);this._checked=value;}});Object.defineProperty(proto,'name',{get:function(){return this.getAttribute('name');},set:function(value){this.setAttribute('name',value);}});var template=document.createElement('template');template.innerHTML=`<span id="checkbox" role="checkbox">
|
||||
<span role="presentation"><content select="label"></content></span>
|
||||
</span>`;return document.registerElement('gaia-checkbox',{prototype:proto});})(window);
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
gaia-checkbox {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
#checkbox {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background: none;
|
||||
}
|
||||
#checkbox span {
|
||||
font-size: 1.9rem;
|
||||
font-weight: normal;
|
||||
color: var(--label-color);
|
||||
line-height: 3rem;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
min-width: 2.2rem;
|
||||
min-height: 2.2rem;
|
||||
width: 100%;
|
||||
padding-inline-end: 2.2rem;
|
||||
|
||||
white-space: normal;
|
||||
}
|
||||
#checkbox.inline {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#checkbox:not(.invisible) span:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
offset-inline-end: 0;
|
||||
top: 50%;
|
||||
margin: -1.2rem 0 0;
|
||||
margin-inline-start: -1.2rem;
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
}
|
||||
#checkbox.inline span {
|
||||
padding-inline-start: 3rem;
|
||||
padding-inline-end: 0;
|
||||
height: auto;
|
||||
}
|
||||
#checkbox.inline span:after {
|
||||
offset-inline-start: 1.2rem;
|
||||
offset-inline-end: unset;
|
||||
margin: -1.2rem 0 0;
|
||||
margin-inline-start: -1.2rem;
|
||||
}
|
||||
#checkbox:not(.invisible) span:after {
|
||||
background: url(images/default.png) no-repeat center top / 2.2rem auto;
|
||||
}
|
||||
#checkbox.checked span:after {
|
||||
background-position: center bottom;
|
||||
}
|
||||
#checkbox.danger span:after {
|
||||
background-image: url(images/danger.png);
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
;window.GaiaSubheader=(function(win){var proto=Object.create(HTMLElement.prototype);var baseurl=window.GaiaSubheaderBaseurl||'/shared/elements/gaia_subheader/';proto.createdCallback=function(){ComponentUtils.style.call(this,baseurl);};return document.registerElement('gaia-subheader',{prototype:proto});})(window);
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
gaia-subheader[hidden] {
|
||||
display: none;
|
||||
}
|
||||
gaia-subheader {
|
||||
display: block;
|
||||
padding: 0.8rem 3rem;
|
||||
border-bottom: solid 0.1rem #e6e6e6;
|
||||
color: #424242;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.8rem;
|
||||
text-align: start;
|
||||
}
|
||||
gaia-subheader[skin="organic"] {
|
||||
border: 0;
|
||||
background-color: #e7e7e7;
|
||||
color: #4d4d4d;
|
||||
}
|
||||
gaia-subheader[skin="dark"] {
|
||||
border: 0;
|
||||
background-color: #2c353b;
|
||||
color: #fff;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 755 B |
Binary file not shown.
|
After Width: | Height: | Size: 475 B |
Binary file not shown.
|
After Width: | Height: | Size: 748 B |
|
|
@ -0,0 +1,9 @@
|
|||
;window.GaiaSwitch=(function(win){var proto=Object.create(HTMLElement.prototype);var baseurl=window.GaiaSwitchBaseurl||'/shared/elements/gaia_switch/';proto.createdCallback=function(){var shadow=this.createShadowRoot();this._template=template.content.cloneNode(true);this._input=this._template.querySelector('input[type="checkbox"]');var checked=this.getAttribute('checked');if(checked!==null){this._input.checked=true;}
|
||||
var wrapper=this._template.getElementById('switch');wrapper.addEventListener('click',this.handleClick.bind(this));shadow.appendChild(this._template);ComponentUtils.style.call(this,baseurl);var dirObserver=new MutationObserver(this.updateInternalDir.bind(this));dirObserver.observe(document.documentElement,{attributeFilter:['dir'],attributes:true});this.updateInternalDir();};proto.updateInternalDir=function(){var internal=this.shadowRoot.firstElementChild;if(document.documentElement.dir==='rtl'){internal.setAttribute('dir','rtl');}else{internal.removeAttribute('dir');}};proto.handleClick=function(e){if(e&&e.target.tagName==='A'){return;}
|
||||
e&&e.preventDefault();e&&e.stopImmediatePropagation();var event=new MouseEvent('click',{view:window,bubbles:true,cancelable:true});this.dispatchEvent(event);if(!event.defaultPrevented){this.checked=!this.checked;}
|
||||
this.dispatchEvent(new CustomEvent('change',{bubbles:true,cancelable:false}));};proto.click=function(){this.handleClick();};Object.defineProperty(proto,'checked',{get:function(){return this._input.checked;},set:function(value){this._input.checked=value;}});Object.defineProperty(proto,'disabled',{get:function(){return this.hasAttribute('disabled');},set:function(value){if(value){this.setAttribute('disabled',true);}else{this.removeAttribute('disabled');}}});Object.defineProperty(proto,'name',{get:function(){return this.getAttribute('name');},set:function(value){this.setAttribute('name',value);}});Object.defineProperty(proto,'type',{get:function(){return'gaia-switch';}});var template=document.createElement('template');template.innerHTML=`<span id="switch">
|
||||
<input type="checkbox">
|
||||
<span><content select="label"></content></span>
|
||||
<div class="details"><content select="details"></content></div>
|
||||
<content select="a"></content>
|
||||
</span>`;return document.registerElement('gaia-switch',{prototype:proto});})(window);
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
gaia-switch {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
#switch {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background: none;
|
||||
}
|
||||
#switch span {
|
||||
font-size: 1.9rem;
|
||||
color: var(--label-color);
|
||||
padding: 1rem 0;
|
||||
line-height: 3rem;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
|
||||
white-space: normal;
|
||||
}
|
||||
#switch span:-moz-dir(ltr) { float: left; }
|
||||
#switch span:-moz-dir(rtl) { float: right; }
|
||||
#switch span:not(:empty) {
|
||||
padding-inline-end: 6rem;
|
||||
}
|
||||
#switch input {
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
offset-inline-start: 0;
|
||||
}
|
||||
#switch input:checked ~ span::after {
|
||||
background-position: center bottom;
|
||||
}
|
||||
#switch input ~ span::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
offset-inline-end: 0;
|
||||
top: 2.5rem;
|
||||
width: 6rem;
|
||||
margin: -1.4rem 0 0;
|
||||
height: 2.7rem;
|
||||
border-radius: 1.35rem;
|
||||
overflow: hidden;
|
||||
background-color: #e6e6e6;
|
||||
background-image: url(images/background_off.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 9.2rem 2.7rem;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
#switch input ~ span:-moz-dir(ltr)::after { background-position: -3.2rem 0; }
|
||||
#switch input ~ span:-moz-dir(rtl)::after { background-position: 0 0; }
|
||||
#switch input:checked ~ span:-moz-dir(ltr)::after {
|
||||
background-image: url(images/background.png);
|
||||
background-position: 0 0;
|
||||
}
|
||||
#switch input:checked ~ span:-moz-dir(rtl)::after {
|
||||
background-image: url(images/background_rtl.png);
|
||||
background-position: -3.2rem 0;
|
||||
}
|
||||
#switch input:disabled ~ span::after {
|
||||
opacity: 0.4;
|
||||
}
|
||||
#switch input.uninit ~ span::after {
|
||||
transition: none;
|
||||
}
|
||||
#switch div.details {
|
||||
clear: both;
|
||||
position: relative;
|
||||
top: -1.5rem;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding-top: 0.5rem;
|
||||
padding-inline-start: 0;
|
||||
padding-inline-end: 8rem;
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.6rem;
|
||||
color: #505859;
|
||||
font-style: normal;
|
||||
white-space: normal;
|
||||
}
|
||||
gaia-switch a {
|
||||
position: relative;
|
||||
top: -0.8rem;
|
||||
font-size: 1.7rem;
|
||||
line-height: 2.4rem;
|
||||
font-weight: 400;
|
||||
color: #35679a;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue