From 47a003ebaba4131525299bf678dace29a2f62cea Mon Sep 17 00:00:00 2001 From: zotlabs Date: Sat, 4 May 2019 14:06:44 -0700 Subject: [PATCH 01/43] page_site button had no value --- view/tpl/admin_site.tpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/view/tpl/admin_site.tpl b/view/tpl/admin_site.tpl index 7ea710c67..6b4b1ae55 100755 --- a/view/tpl/admin_site.tpl +++ b/view/tpl/admin_site.tpl @@ -71,7 +71,7 @@ {{include file="field_select.tpl" field=$directory_server}} {{/if}}
- +
@@ -96,7 +96,7 @@ {{include file="field_input.tpl" field=$sellpage}} {{include file="field_input.tpl" field=$first_page}}
- +
@@ -120,7 +120,7 @@ {{include file="field_textarea.tpl" field=$incl}} {{include file="field_textarea.tpl" field=$excl}}
- +
@@ -149,7 +149,7 @@ {{include file="field_input.tpl" field=$default_expire_days}} {{include file="field_input.tpl" field=$active_expire_days}}
- +
From b64858a0995a2b5525a22dda90028cf741667f9d Mon Sep 17 00:00:00 2001 From: zotlabs Date: Sun, 5 May 2019 13:06:15 -0700 Subject: [PATCH 02/43] directory: if safe mode is enabled, strip links from profile elements --- Zotlabs/Module/Directory.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Zotlabs/Module/Directory.php b/Zotlabs/Module/Directory.php index b7bece8f6..1604546d9 100644 --- a/Zotlabs/Module/Directory.php +++ b/Zotlabs/Module/Directory.php @@ -9,6 +9,7 @@ use Zotlabs\Lib\Libsync; require_once('include/socgraph.php'); require_once('include/bbcode.php'); +require_once('include/html2plain.php'); define( 'DIRECTORY_PAGESIZE', 60); @@ -298,12 +299,15 @@ class Directory extends Controller { $marital = ((x($profile,'marital') == 1) ? t('Status: ') . $profile['marital']: False); $homepage = ((x($profile,'homepage') == 1) ? t('Homepage: ') : False); - $homepageurl = ((x($profile,'homepage') == 1) ? $profile['homepage'] : ''); + $homepageurl = ((x($profile,'homepage') == 1) ? html2plain($profile['homepage']) : ''); - $hometown = ((x($profile,'hometown') == 1) ? $profile['hometown'] : False); + $hometown = ((x($profile,'hometown') == 1) ? html2plain($profile['hometown']) : False); $about = ((x($profile,'about') == 1) ? zidify_links(bbcode($profile['about'])) : False); - + if ($about && $safe_mode) { + $about = html2plain($about); + } + $keywords = ((x($profile,'keywords')) ? $profile['keywords'] : ''); @@ -360,7 +364,7 @@ class Directory extends Controller { 'pdesc_label' => t('Description:'), 'marital' => $marital, 'homepage' => $homepage, - 'homepageurl' => linkify($homepageurl), + 'homepageurl' => (($safe_mode) ? $homepageurl : linkify($homepageurl)), 'hometown' => $hometown, 'hometown_label' => t('Hometown:'), 'about' => $about, From c607876e36c568ca2ecd64f6b8aefd4d69985e0b Mon Sep 17 00:00:00 2001 From: zotlabs Date: Sun, 5 May 2019 16:13:51 -0700 Subject: [PATCH 03/43] logo update --- Zotlabs/Lib/System.php | 2 +- images/zaplogo-64.png | Bin 0 -> 7347 bytes images/zaplogo.xcf | Bin 0 -> 83971 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 images/zaplogo-64.png create mode 100644 images/zaplogo.xcf diff --git a/Zotlabs/Lib/System.php b/Zotlabs/Lib/System.php index c8e28689e..544e626b3 100644 --- a/Zotlabs/Lib/System.php +++ b/Zotlabs/Lib/System.php @@ -29,7 +29,7 @@ class System { static public function get_project_icon() { - return z_root() . '/images/zap4-64.png'; + return z_root() . '/images/zaplogo-64.png'; if(defined('NOMADIC')) { return '⚡'; diff --git a/images/zaplogo-64.png b/images/zaplogo-64.png new file mode 100644 index 0000000000000000000000000000000000000000..215ab617792a9a4dbd551601ef1625d111a05d4e GIT binary patch literal 7347 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 Z{{k`0Uws|YvycD)002ovPDHLkV1mWl`RM=v literal 0 HcmV?d00001 diff --git a/images/zaplogo.xcf b/images/zaplogo.xcf new file mode 100644 index 0000000000000000000000000000000000000000..e04a8141f781da0f551e05b34ba7cb37f82a9f31 GIT binary patch literal 83971 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 literal 0 HcmV?d00001 From 687eb93153438fd2f249f6932219ddc8dda71a18 Mon Sep 17 00:00:00 2001 From: zotlabs Date: Sun, 5 May 2019 18:44:33 -0700 Subject: [PATCH 04/43] directory entry reporting handler --- Zotlabs/Module/Dircensor.php | 54 ++++++++++++++++++++++++++++++++++ Zotlabs/Module/Directory.php | 20 +++++++++---- Zotlabs/Module/Dirsearch.php | 57 +++++++++++++++++++----------------- view/tpl/direntry.tpl | 3 ++ 4 files changed, 101 insertions(+), 33 deletions(-) create mode 100644 Zotlabs/Module/Dircensor.php diff --git a/Zotlabs/Module/Dircensor.php b/Zotlabs/Module/Dircensor.php new file mode 100644 index 000000000..b9f500022 --- /dev/null +++ b/Zotlabs/Module/Dircensor.php @@ -0,0 +1,54 @@ + (($rating_enabled && local_channel()) ? true : false), 'pdesc' => $pdesc, 'pdesc_label' => t('Description:'), + 'censor' => (($directory_admin) ? 'dircensor/' . $rr['hash'] : ''), + 'censor_label' => (($rr['censored']) ? t('Uncensor') : t('Censor')), 'marital' => $marital, 'homepage' => $homepage, 'homepageurl' => (($safe_mode) ? $homepageurl : linkify($homepageurl)), @@ -407,7 +415,7 @@ class Directory extends Controller { ksort($entries); // Sort array by key so that foreach-constructs work as expected if($j['keywords']) { - \App::$data['directory_keywords'] = $j['keywords']; + App::$data['directory_keywords'] = $j['keywords']; } // logger('mod_directory: entries: ' . print_r($entries,true), LOGGER_DATA); diff --git a/Zotlabs/Module/Dirsearch.php b/Zotlabs/Module/Dirsearch.php index 9b8a4f374..3e55e9ef6 100644 --- a/Zotlabs/Module/Dirsearch.php +++ b/Zotlabs/Module/Dirsearch.php @@ -1,14 +1,15 @@ {{$entry.ignore_label}} {{/if}} + {{if $entry.censor}} + {{$entry.censor_label}} + {{/if}} {{if $entry.connect}} {{$entry.conn_label}} {{/if}} From 6b31b648ebd017bb55d1ce0bf57379e50bb99c89 Mon Sep 17 00:00:00 2001 From: zotlabs Date: Sun, 5 May 2019 18:50:28 -0700 Subject: [PATCH 05/43] typo --- Zotlabs/Module/Dircensor.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Zotlabs/Module/Dircensor.php b/Zotlabs/Module/Dircensor.php index b9f500022..b7480479c 100644 --- a/Zotlabs/Module/Dircensor.php +++ b/Zotlabs/Module/Dircensor.php @@ -33,8 +33,6 @@ class Dircensor extends Controller { } $val = (($r[0]['xchan_censored']) ? 0 : 1); - $val = 0; - } q("update xchan set xchan_censored = $val where xchan_hash = '%s'", dbesc($xchan) From 642a1c2f2b079a37cb13626cf89444f118d1f64e Mon Sep 17 00:00:00 2001 From: zotlabs Date: Sun, 5 May 2019 21:06:02 -0700 Subject: [PATCH 06/43] cleanup default icon logic --- Zotlabs/Lib/System.php | 10 +++------- boot.php | 5 ++++- include/text.php | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Zotlabs/Lib/System.php b/Zotlabs/Lib/System.php index 544e626b3..2abf62875 100644 --- a/Zotlabs/Lib/System.php +++ b/Zotlabs/Lib/System.php @@ -28,15 +28,11 @@ class System { } static public function get_project_icon() { - + if(is_array(App::$config) && is_array(App::$config['system']) && array_key_exists('icon',App::$config['system'])) { + return App::$config['system']['icon']; + } return z_root() . '/images/zaplogo-64.png'; - if(defined('NOMADIC')) { - return '⚡'; - } - else { - return '☸'; - } } diff --git a/boot.php b/boot.php index e29173954..f44d94de8 100755 --- a/boot.php +++ b/boot.php @@ -1115,8 +1115,11 @@ class App { self::$meta->set('generator', System::get_platform_name()); $i = head_get_icon(); + if (! $i) { + $i = System::get_project_icon(); + } if($i) { - head_add_link(['rel' => 'shortcut icon', 'href' => head_get_icon()]); + head_add_link(['rel' => 'shortcut icon', 'href' => $i ]); } $x = [ 'header' => '' ]; diff --git a/include/text.php b/include/text.php index 9b1ddf7a9..30c4babcf 100644 --- a/include/text.php +++ b/include/text.php @@ -3467,10 +3467,10 @@ function array_path_exists($str,$arr) { function get_forum_channels($uid,$collections = 0) { - if(! $uid) + if (! $uid) return; - if($collections) { + if ($collections) { $pagetype = $collections; } else { From e7f5ad401cc0837ff6da19d446057c06326a47db Mon Sep 17 00:00:00 2001 From: zotlabs Date: Sun, 5 May 2019 23:38:29 -0700 Subject: [PATCH 07/43] cleanup --- Zotlabs/Module/Dirsearch.php | 258 +++++++++++++++++++---------------- Zotlabs/Module/Sitelist.php | 39 +++--- include/socgraph.php | 19 ++- 3 files changed, 179 insertions(+), 137 deletions(-) diff --git a/Zotlabs/Module/Dirsearch.php b/Zotlabs/Module/Dirsearch.php index 3e55e9ef6..d7eea5be0 100644 --- a/Zotlabs/Module/Dirsearch.php +++ b/Zotlabs/Module/Dirsearch.php @@ -5,6 +5,12 @@ namespace Zotlabs\Module; use App; use Zotlabs\Web\Controller; +// This is the primary endpoint for communicating with Zot directory services. +// Additionally the 'sitelist' endpoint may be used to query directory knowledge +// of discovered sites. That endpoint should be merged so that there is one +// definitive endpoint for directory services and since this endpoint already +// performs some site query functions. + class Dirsearch extends Controller { @@ -21,7 +27,7 @@ class Dirsearch extends Controller { $dirmode = intval(get_config('system','directory_mode')); - if($dirmode == DIRECTORY_MODE_NORMAL) { + if ($dirmode == DIRECTORY_MODE_NORMAL) { $ret['message'] = t('This site is not a directory server'); json_return_and_die($ret); } @@ -30,30 +36,32 @@ class Dirsearch extends Controller { $access_token = $_REQUEST['t']; $token = get_config('system','realm_token'); - if($token && $access_token != $token) { + if ($token && $access_token != $token) { $ret['message'] = t('This directory server requires an access token'); json_return_and_die($ret); } - if(argc() > 1 && argv(1) === 'sites') { + if (argc() > 1 && argv(1) === 'sites') { $ret = $this->list_public_sites(); json_return_and_die($ret); } $sql_extra = ''; - - $tables = array('name','address','locale','region','postcode','country','gender','marital','sexual','keywords'); - - if($_REQUEST['query']) { + $tables = [ 'name', 'address', 'locale', 'region', 'postcode', + 'country', 'gender', 'marital', 'sexual', 'keywords' ]; + + // parse advanced query if present + + if ($_REQUEST['query']) { $advanced = $this->dir_parse_query($_REQUEST['query']); - if($advanced) { - foreach($advanced as $adv) { - if(in_array($adv['field'],$tables)) { - if($adv['field'] === 'name') + if ($advanced) { + foreach ($advanced as $adv) { + if (in_array($adv['field'],$tables)) { + if ($adv['field'] === 'name') $sql_extra .= $this->dir_query_build($adv['logic'],'xchan_name',$adv['value']); - elseif($adv['field'] === 'address') + elseif ($adv['field'] === 'address') $sql_extra .= $this->dir_query_build($adv['logic'],'xchan_addr',$adv['value']); else $sql_extra .= $this->dir_query_build($adv['logic'],'xprof_' . $adv['field'],$adv['value']); @@ -63,7 +71,6 @@ class Dirsearch extends Controller { } $hash = ((x($_REQUEST['hash'])) ? $_REQUEST['hash'] : ''); - $name = ((x($_REQUEST,'name')) ? $_REQUEST['name'] : ''); $hub = ((x($_REQUEST,'hub')) ? $_REQUEST['hub'] : ''); $address = ((x($_REQUEST,'address')) ? $_REQUEST['address'] : ''); @@ -79,18 +86,22 @@ class Dirsearch extends Controller { $agele = ((x($_REQUEST,'agele')) ? intval($_REQUEST['agele']) : 0 ); $kw = ((x($_REQUEST,'kw')) ? intval($_REQUEST['kw']) : 0 ); $type = ((array_key_exists('type',$_REQUEST)) ? intval($_REQUEST['type']) : 0); - - if(get_config('system','disable_directory_keywords')) + + // allow a site to disable the directory's keyword list + if (get_config('system','disable_directory_keywords')) $kw = 0; - // by default use a safe search - $safe = ((x($_REQUEST,'safe'))); // ? intval($_REQUEST['safe']) : 1 ); - if ($safe === false) - $safe = 1; - - if(array_key_exists('sync',$_REQUEST)) { - if($_REQUEST['sync']) + $safe = ((x($_REQUEST,'safe'))); + if ($safe === false) { + $safe = 1; + } + + // Directory mirrors will request sync packets, which are lists + // of records that have changed since the sync datetime. + + if (array_key_exists('sync',$_REQUEST)) { + if ($_REQUEST['sync']) $sync = datetime_convert('UTC','UTC',$_REQUEST['sync']); else $sync = datetime_convert('UTC','UTC','2010-01-01 01:01:00'); @@ -98,43 +109,60 @@ class Dirsearch extends Controller { else $sync = false; - if(($dirmode == DIRECTORY_MODE_STANDALONE) && (! $hub)) { - $hub = \App::get_hostname(); + if (($dirmode == DIRECTORY_MODE_STANDALONE) && (! $hub)) { + $hub = App::get_hostname(); } - if($hub) + if ($hub) { $hub_query = " and xchan_hash in (select hubloc_hash from hubloc where hubloc_host = '" . protect_sprintf(dbesc($hub)) . "') "; - else + } + else { $hub_query = ''; - + } + + // The order identifier is validated further below + $sort_order = ((x($_REQUEST,'order')) ? $_REQUEST['order'] : ''); - + + + // parse and assemble the query for advanced searches + $joiner = ' OR '; + if($_REQUEST['and']) $joiner = ' AND '; - if($name) + if ($name) { $sql_extra .= $this->dir_query_build($joiner,'xchan_name',$name); - if($address) + } + if ($address) { $sql_extra .= $this->dir_query_build($joiner,'xchan_addr',$address); - if($locale) + } + if ($locale) { $sql_extra .= $this->dir_query_build($joiner,'xprof_locale',$locale); - if($region) + } + if ($region) { $sql_extra .= $this->dir_query_build($joiner,'xprof_region',$region); - if($postcode) + } + if ($postcode) { $sql_extra .= $this->dir_query_build($joiner,'xprof_postcode',$postcode); - if($country) + } + if ($country) { $sql_extra .= $this->dir_query_build($joiner,'xprof_country',$country); - if($gender) + } + if ($gender) { $sql_extra .= $this->dir_query_build($joiner,'xprof_gender',$gender); - if($marital) + } + if ($marital) { $sql_extra .= $this->dir_query_build($joiner,'xprof_marital',$marital); - if($sexual) + } + if ($sexual) { $sql_extra .= $this->dir_query_build($joiner,'xprof_sexual',$sexual); - if($keywords) + } + if ($keywords) { $sql_extra .= $this->dir_query_build($joiner,'xprof_keywords',$keywords); - - + } + // we only support an age range currently. You must set both agege // (greater than or equal) and agele (less than or equal) @@ -159,7 +187,6 @@ class Dirsearch extends Controller { $mtime = ((x($_REQUEST,'mtime')) ? datetime_convert('UTC','UTC',$_REQUEST['mtime']) : ''); - // ok a separate tag table won't work. // merge them into xprof $ret['success'] = true; @@ -170,33 +197,37 @@ class Dirsearch extends Controller { $logic = ((strlen($sql_extra)) ? 'false' : 'true'); - if($hash) + if ($hash) { $logic = 'true'; + } - if($dirmode == DIRECTORY_MODE_STANDALONE) { - $sql_extra .= " and xchan_addr like '%%" . \App::get_hostname() . "' "; + if ($dirmode == DIRECTORY_MODE_STANDALONE) { + $sql_extra .= " and xchan_addr like '%%" . App::get_hostname() . "' "; } $safesql = (($safe > 0) ? " and xchan_censored = 0 and xchan_selfcensored = 0 " : ''); - if($safe < 0) + if ($safe < 0) { $safesql = " and ( xchan_censored = 1 OR xchan_selfcensored = 1 ) "; - - if($type) + } + + if ($type) { $safesql .= " and xchan_type = " . intval($type); + } - if($limit) + if ($limit) { $qlimit = " LIMIT $limit "; + } else { $qlimit = " LIMIT " . intval($perpage) . " OFFSET " . intval($startrec); - if($return_total) { + if ($return_total) { $r = q("SELECT COUNT(xchan_hash) AS total FROM xchan left join xprof on xchan_hash = xprof_hash where $logic $sql_extra and xchan_network = 'zot6' and xchan_hidden = 0 and xchan_orphan = 0 and xchan_deleted = 0 $safesql "); - if($r) { + if ($r) { $ret['total_items'] = $r[0]['total']; } } } - if($sort_order == 'normal') { + if ($sort_order == 'normal') { $order = " order by xchan_name asc "; // Start the alphabetic search at 'A' @@ -205,58 +236,61 @@ class Dirsearch extends Controller { $safesql .= " and ascii(substring(xchan_name FROM 1 FOR 1)) > 64 "; } - elseif($sort_order == 'reverse') + elseif ($sort_order == 'reverse') $order = " order by xchan_name desc "; - elseif($sort_order == 'reversedate') + elseif ($sort_order == 'reversedate') $order = " order by xchan_name_date asc "; else $order = " order by xchan_name_date desc "; - if($sync) { - $spkt = array('transactions' => array()); + if ($sync) { + $spkt = array('transactions' => [] ); $r = q("select * from updates where ud_date >= '%s' and ud_guid != '' order by ud_date desc", dbesc($sync) ); - if($r) { - foreach($r as $rr) { + if ($r) { + foreach ($r as $rr) { $flags = array(); - if($rr['ud_flags'] & UPDATE_FLAGS_DELETED) + if ($rr['ud_flags'] & UPDATE_FLAGS_DELETED) $flags[] = 'deleted'; - if($rr['ud_flags'] & UPDATE_FLAGS_FORCED) + if ($rr['ud_flags'] & UPDATE_FLAGS_FORCED) $flags[] = 'forced'; - $spkt['transactions'][] = array( - 'hash' => $rr['ud_hash'], - 'address' => $rr['ud_addr'], + $spkt['transactions'][] = [ + 'hash' => $rr['ud_hash'], + 'address' => $rr['ud_addr'], 'transaction_id' => $rr['ud_guid'], - 'timestamp' => $rr['ud_date'], - 'flags' => $flags - ); + 'timestamp' => $rr['ud_date'], + 'flags' => $flags + ]; } } + + // sync ratings - not currently used + $r = q("select * from xlink where xlink_static = 1 and xlink_updated >= '%s' ", dbesc($sync) ); - if($r) { - $spkt['ratings'] = array(); - foreach($r as $rr) { - $spkt['ratings'][] = array( - 'type' => 'rating', - 'encoding' => 'zot', - 'channel' => $rr['xlink_xchan'], - 'target' => $rr['xlink_link'], - 'rating' => intval($rr['xlink_rating']), + if ($r) { + $spkt['ratings'] = []; + foreach ($r as $rr) { + $spkt['ratings'][] = [ + 'type' => 'rating', + 'encoding' => 'zot', + 'channel' => $rr['xlink_xchan'], + 'target' => $rr['xlink_link'], + 'rating' => intval($rr['xlink_rating']), 'rating_text' => $rr['xlink_rating_text'], - 'signature' => $rr['xlink_sig'], - 'edited' => $rr['xlink_updated'] - ); + 'signature' => $rr['xlink_sig'], + 'edited' => $rr['xlink_updated'] + ]; } } json_return_and_die($spkt); } else { - + // normal directory query $r = q("SELECT xchan.*, xprof.* from xchan left join xprof on xchan_hash = xprof_hash where ( $logic $sql_extra ) $hub_query and xchan_network = 'zot6' and xchan_system = 0 and xchan_hidden = 0 and xchan_orphan = 0 and xchan_deleted = 0 $safesql $order $qlimit " @@ -268,19 +302,19 @@ class Dirsearch extends Controller { - if($r) { + if ($r) { - $entries = array(); + $entries = []; - foreach($r as $rr) { + foreach ($r as $rr) { - $entry = array(); + $entry = []; $pc = q("select count(xlink_rating) as total_ratings from xlink where xlink_link = '%s' and xlink_rating != 0 and xlink_static = 1 group by xlink_rating", dbesc($rr['xchan_hash']) ); - if($pc) + if ($pc) $entry['total_ratings'] = intval($pc[0]['total_ratings']); else $entry['total_ratings'] = 0; @@ -314,23 +348,22 @@ class Dirsearch extends Controller { } $ret['results'] = $entries; - if($kw) { + if ($kw) { $k = dir_tagadelic($kw, $hub, $type); - if($k) { + if ($k) { $ret['keywords'] = array(); - foreach($k as $kv) { - $ret['keywords'][] = array('term' => $kv[0],'weight' => $kv[1], 'normalise' => $kv[2]); + foreach ($k as $kv) { + $ret['keywords'][] = [ 'term' => $kv[0], 'weight' => $kv[1], 'normalise' => $kv[2] ]; } } } } - json_return_and_die($ret); } function dir_query_build($joiner,$field,$s) { $ret = ''; - if(trim($s)) + if (trim($s)) $ret .= dbesc($joiner) . " " . dbesc($field) . " like '" . protect_sprintf( '%' . dbesc($s) . '%' ) . "' "; return $ret; } @@ -347,32 +380,32 @@ class Dirsearch extends Controller { $all = explode(' ',$s); $quoted_string = false; - if($all) { - foreach($all as $q) { - if($quoted_string === false) { - if($q === 'and') { + if ($all) { + foreach ($all as $q) { + if ($quoted_string === false) { + if ($q === 'and') { $curr['logic'] = 'and'; continue; } - if($q === 'or') { + if ($q === 'or') { $curr['logic'] = 'or'; continue; } - if($q === 'not') { + if ($q === 'not') { $curr['logic'] .= ' not'; continue; } - if(strpos($q,'=')) { - if(! isset($curr['logic'])) + if (strpos($q,'=')) { + if (! isset($curr['logic'])) $curr['logic'] = 'or'; $curr['field'] = trim(substr($q,0,strpos($q,'='))); $curr['value'] = trim(substr($q,strpos($q,'=')+1)); - if($curr['value'][0] == '"' && $curr['value'][strlen($curr['value'])-1] != '"') { + if ($curr['value'][0] == '"' && $curr['value'][strlen($curr['value'])-1] != '"') { $quoted_string = true; $curr['value'] = substr($curr['value'],1); continue; } - elseif($curr['value'][0] == '"' && $curr['value'][strlen($curr['value'])-1] == '"') { + elseif ($curr['value'][0] == '"' && $curr['value'][strlen($curr['value'])-1] == '"') { $curr['value'] = substr($curr['value'],1,strlen($curr['value'])-2); $ret[] = $curr; $curr = array(); @@ -386,7 +419,7 @@ class Dirsearch extends Controller { } } else { - if($q[strlen($q)-1] == '"') { + if ($q[strlen($q)-1] == '"') { $curr['value'] .= ' ' . str_replace('"','',trim($q)); $ret[] = $curr; $curr = array(); @@ -402,16 +435,11 @@ class Dirsearch extends Controller { } - - - - - function list_public_sites() { $rand = db_getfunc('rand'); $realm = get_directory_realm(); - if($realm == DIRECTORY_REALM) { + if ($realm == DIRECTORY_REALM) { $r = q("select * from site where site_access != 0 and site_register !=0 and ( site_realm = '%s' or site_realm = '') and site_type = %d and site_dead = 0 order by $rand", dbesc($realm), intval(SITE_TYPE_ZOT) @@ -426,35 +454,35 @@ class Dirsearch extends Controller { $ret = array('success' => false); - if($r) { + if ($r) { $ret['success'] = true; $ret['sites'] = array(); $insecure = array(); - foreach($r as $rr) { + foreach ($r as $rr) { - if($rr['site_access'] == ACCESS_FREE) + if ($rr['site_access'] == ACCESS_FREE) $access = 'free'; - elseif($rr['site_access'] == ACCESS_PAID) + elseif ($rr['site_access'] == ACCESS_PAID) $access = 'paid'; - elseif($rr['site_access'] == ACCESS_TIERED) + elseif ($rr['site_access'] == ACCESS_TIERED) $access = 'tiered'; else $access = 'private'; - if($rr['site_register'] == REGISTER_OPEN) + if ($rr['site_register'] == REGISTER_OPEN) $register = 'open'; - elseif($rr['site_register'] == REGISTER_APPROVE) + elseif ($rr['site_register'] == REGISTER_APPROVE) $register = 'approve'; else $register = 'closed'; - if(strpos($rr['site_url'],'https://') !== false) + if (strpos($rr['site_url'],'https://') !== false) $ret['sites'][] = array('url' => $rr['site_url'], 'access' => $access, 'register' => $register, 'sellpage' => $rr['site_sellpage'], 'location' => $rr['site_location'], 'project' => $rr['site_project'], 'version' => $rr['site_version']); else $insecure[] = array('url' => $rr['site_url'], 'access' => $access, 'register' => $register, 'sellpage' => $rr['site_sellpage'], 'location' => $rr['site_location'], 'project' => $rr['site_project'], 'version' => $rr['site_version']); } - if($insecure) { + if ($insecure) { $ret['sites'] = array_merge($ret['sites'],$insecure); } } diff --git a/Zotlabs/Module/Sitelist.php b/Zotlabs/Module/Sitelist.php index 2ac5ed1b8..69d6a7b5f 100644 --- a/Zotlabs/Module/Sitelist.php +++ b/Zotlabs/Module/Sitelist.php @@ -1,8 +1,11 @@ false); + $result = [ 'success' => false ]; $r = q("select count(site_url) as total from site where site_type = %d and site_dead = 0 $sql_extra ", intval(SITE_TYPE_ZOT) ); - if($r) + if ($r) { $result['total'] = intval($r[0]['total']); - + } + $result['start'] = $start; $result['limit'] = $limit; @@ -53,15 +63,12 @@ class Sitelist extends \Zotlabs\Web\Controller { $result['success'] = true; $result['results'] = count($r); - foreach($r as $rr) { - $result['entries'][] = array('url' => $rr['site_url']); + foreach ($r as $rv) { + $result['entries'][] = [ 'url' => $rv['site_url'] ]; } } - echo json_encode($result); - killme(); - - + json_return_and_die($result); } } diff --git a/include/socgraph.php b/include/socgraph.php index b3896df9c..bb54f679a 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -286,28 +286,35 @@ function suggestion_query($uid, $myxchan, $start = 0, $limit = 80) { return array(); } +// This function fetches a number of sitenames from the directory and searches them +// for channels that have opted-in to be "default" suggestions to new channels which +// have no connections. +// @TODO: We currently use a hardwired path of '/poco'. This should be configurable +// and based on discovered endpoint locations. + + function update_suggestions() { $dirmode = get_config('system', 'directory_mode', DIRECTORY_MODE_NORMAL); - if($dirmode == DIRECTORY_MODE_STANDALONE) { + if ($dirmode == DIRECTORY_MODE_STANDALONE) { poco_load('', z_root() . '/poco'); return; } - if($dirmode == DIRECTORY_MODE_PRIMARY) { + if ($dirmode == DIRECTORY_MODE_PRIMARY) { $url = z_root() . '/sitelist'; } else { $directory = Libzotdir::find_upstream_directory($dirmode); $url = $directory['url'] . '/sitelist'; } - if(! $url) + if (! $url) return; $ret = z_fetch_url($url); - if($ret['success']) { + if ($ret['success']) { // We will grab fresh data once a day via the poller. Remove anything over a week old because // the targets may have changed their preferences and don't want to be suggested - and they @@ -318,8 +325,8 @@ function update_suggestions() { ); $j = json_decode($ret['body'],true); - if($j && $j['success']) { - foreach($j['entries'] as $host) { + if ($j && $j['success']) { + foreach ($j['entries'] as $host) { poco_load('',$host['url'] . '/poco'); } } From 6634ad90853a93f018ef1efb77c2d100720eec32 Mon Sep 17 00:00:00 2001 From: zotlabs Date: Mon, 6 May 2019 20:22:08 -0700 Subject: [PATCH 08/43] cleanup - implement remote purge which has been missing for a long time --- Zotlabs/Daemon/Notifier.php | 19 ++++++--- Zotlabs/Lib/Libzotdir.php | 10 +++-- Zotlabs/Module/Connedit.php | 81 ++++++++++++++++++------------------ Zotlabs/Module/Dirsearch.php | 28 +++++++------ boot.php | 1 + include/connections.php | 4 +- 6 files changed, 81 insertions(+), 62 deletions(-) diff --git a/Zotlabs/Daemon/Notifier.php b/Zotlabs/Daemon/Notifier.php index 53cbab0d6..e8bf654fa 100644 --- a/Zotlabs/Daemon/Notifier.php +++ b/Zotlabs/Daemon/Notifier.php @@ -61,6 +61,7 @@ require_once('include/bbcode.php'); * permissions_reject abook_id * permissions_update abook_id * refresh_all channel_id + * purge xchan_hash * purge_all channel_id * expire channel_id * relay item_id (item was relayed to owner, we will deliver it as owner) @@ -228,13 +229,21 @@ class Notifier { self::$private = false; self::$packet_type = 'refresh'; } + elseif($cmd === 'purge') { + $xchan = argv(3); + logger('notifier: purge: ' . $item_id . ' => ' . $xchan); + if (! $xchan) { + return; + } + + self::$channel = channelx_by_n($item_id); + self::$recipients = [ $xchan ]; + self::$private = true; + self::$packet_type = 'purge'; + } elseif($cmd === 'purge_all') { logger('notifier: purge_all: ' . $item_id); - $s = q("select * from channel where channel_id = %d limit 1", - intval($item_id) - ); - if($s) - self::$channel = $s[0]; + self::$channel = channelx_by_n($item_id); self::$recipients = array(); $r = q("select abook_xchan from abook where abook_channel = %d and abook_self = 0", diff --git a/Zotlabs/Lib/Libzotdir.php b/Zotlabs/Lib/Libzotdir.php index 5b592b576..7dec1a2d5 100644 --- a/Zotlabs/Lib/Libzotdir.php +++ b/Zotlabs/Lib/Libzotdir.php @@ -3,6 +3,8 @@ namespace Zotlabs\Lib; use Zotlabs\Lib\Libzot; +use Zotlabs\Lib\Webfinger; +use Zotlabs\Lib\Zotfinger; require_once('include/permissions.php'); @@ -272,6 +274,8 @@ class Libzotdir { $ud_flags |= UPDATE_FLAGS_DELETED; if (is_array($t['flags']) && in_array('forced',$t['flags'])) $ud_flags |= UPDATE_FLAGS_FORCED; + if (is_array($t['flags']) && in_array('censored',$t['flags'])) + $ud_flags |= UPDATE_FLAGS_CENSORED; $z = q("insert into updates ( ud_hash, ud_guid, ud_date, ud_flags, ud_addr ) values ( '%s', '%s', '%s', %d, '%s' ) ", @@ -308,9 +312,9 @@ class Libzotdir { if ($ud['ud_addr'] && (! ($ud['ud_flags'] & UPDATE_FLAGS_DELETED))) { $success = false; - $href = \Zotlabs\Lib\Webfinger::zot_url(punify($ud['ud_addr'])); + $href = Webfinger::zot_url(punify($ud['ud_addr'])); if($href) { - $zf = \Zotlabs\Lib\Zotfinger::exec($href); + $zf = Zotfinger::exec($href); } if(is_array($zf) && array_path_exists('signature/signer',$zf) && $zf['signature']['signer'] === $href && intval($zf['signature']['header_valid'])) { $xc = Libzot::import_xchan($zf['data'], 0, $ud); @@ -639,7 +643,7 @@ class Libzotdir { ); } else { - q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d)>0 ", + q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and (ud_flags & %d) = 0 ", intval(UPDATE_FLAGS_UPDATED), dbesc($addr), intval(UPDATE_FLAGS_UPDATED) diff --git a/Zotlabs/Module/Connedit.php b/Zotlabs/Module/Connedit.php index 3f2e460e7..c803bbcce 100644 --- a/Zotlabs/Module/Connedit.php +++ b/Zotlabs/Module/Connedit.php @@ -1,11 +1,15 @@ function connectDefaultShare() { \$('.abook-edit-me').each(function() { @@ -365,7 +367,7 @@ class Connedit extends \Zotlabs\Web\Controller { if($cmd === 'update') { // pull feed and consume it, which should subscribe to the hub. - \Zotlabs\Daemon\Master::Summon(array('Poller',$contact_id)); + Master::Summon( [ 'Poller', $contact_id ]); goaway(z_root() . '/connedit/' . $contact_id); } @@ -412,7 +414,7 @@ class Connedit extends \Zotlabs\Web\Controller { else { // if you are on a different network we'll force a refresh of the connection basic info - \Zotlabs\Daemon\Master::Summon(array('Notifier','permissions_update',$contact_id)); + Master::Summon( [ 'Notifier', 'permissions_update', $contact_id ]); } goaway(z_root() . '/connedit/' . $contact_id); } @@ -470,18 +472,15 @@ class Connedit extends \Zotlabs\Web\Controller { if($cmd === 'drop') { - - // @FIXME - // We need to send either a purge or a refresh packet to the other side (the channel being unfriended). - // The issue is that the abook DB record _may_ get destroyed when we call contact_remove. As the notifier - // runs in the background there could be a race condition preventing this packet from being sent in all - // cases. - // PLACEHOLDER - if($orig_record[0]['xchan_network'] === 'activitypub') { ActivityPub::contact_remove(local_channel(), $orig_record[0]); } contact_remove(local_channel(), $orig_record[0]['abook_id']); + + // The purge notification is sent to the xchan_hash as the abook record will have just been removed + + Master::Summon( [ 'Notifier' , 'purge', $orig_record[0]['xchan_hash'] ] ); + Libsync::build_sync_packet(0 /* use the current local_channel */, array('abook' => array(array( 'abook_xchan' => $orig_record[0]['abook_xchan'], @@ -497,13 +496,13 @@ class Connedit extends \Zotlabs\Web\Controller { } } - if(\App::$poi) { + if(App::$poi) { $abook_prev = 0; $abook_next = 0; - $contact_id = \App::$poi['abook_id']; - $contact = \App::$poi; + $contact_id = App::$poi['abook_id']; + $contact = App::$poi; $cn = q("SELECT abook_id, xchan_name from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d and abook_self = 0 and xchan_deleted = 0 order by xchan_name", intval(local_channel()) @@ -700,9 +699,9 @@ class Connedit extends \Zotlabs\Web\Controller { $perms = array(); - $channel = \App::get_channel(); + $channel = App::get_channel(); - $global_perms = \Zotlabs\Access\Permissions::Perms(); + $global_perms = Permissions::Perms(); $existing = get_all_perms(local_channel(),$contact['abook_xchan'],false); @@ -722,7 +721,7 @@ class Connedit extends \Zotlabs\Web\Controller { $theirs = get_abconfig(local_channel(),$contact['abook_xchan'],'system','their_perms',EMPTY_STR); - $their_perms = \Zotlabs\Access\Permissions::FilledPerms(explode(',',$theirs)); + $their_perms = Permissions::FilledPerms(explode(',',$theirs)); foreach($global_perms as $k => $v) { if(! array_key_exists($k,$their_perms)) $their_perms[$k] = 1; diff --git a/Zotlabs/Module/Dirsearch.php b/Zotlabs/Module/Dirsearch.php index d7eea5be0..22a500d97 100644 --- a/Zotlabs/Module/Dirsearch.php +++ b/Zotlabs/Module/Dirsearch.php @@ -245,17 +245,22 @@ class Dirsearch extends Controller { if ($sync) { + + // generate sync packet for directory mirrors + $spkt = array('transactions' => [] ); $r = q("select * from updates where ud_date >= '%s' and ud_guid != '' order by ud_date desc", dbesc($sync) ); if ($r) { foreach ($r as $rr) { - $flags = array(); + $flags = []; if ($rr['ud_flags'] & UPDATE_FLAGS_DELETED) $flags[] = 'deleted'; if ($rr['ud_flags'] & UPDATE_FLAGS_FORCED) $flags[] = 'forced'; + if ($rr['ud_flags'] & UPDATE_FLAGS_CENSORED) + $flags[] = 'censored'; $spkt['transactions'][] = [ 'hash' => $rr['ud_hash'], @@ -289,18 +294,17 @@ class Dirsearch extends Controller { } json_return_and_die($spkt); } - else { - // normal directory query - $r = q("SELECT xchan.*, xprof.* from xchan left join xprof on xchan_hash = xprof_hash - where ( $logic $sql_extra ) $hub_query and xchan_network = 'zot6' and xchan_system = 0 and xchan_hidden = 0 and xchan_orphan = 0 and xchan_deleted = 0 - $safesql $order $qlimit " - ); + + + // normal directory query + + $r = q("SELECT xchan.*, xprof.* from xchan left join xprof on xchan_hash = xprof_hash + where ( $logic $sql_extra ) $hub_query and xchan_network = 'zot6' and xchan_system = 0 and xchan_hidden = 0 and xchan_orphan = 0 and xchan_deleted = 0 + $safesql $order $qlimit " + ); - $ret['page'] = $page + 1; - $ret['records'] = count($r); - } - - + $ret['page'] = $page + 1; + $ret['records'] = count($r); if ($r) { diff --git a/boot.php b/boot.php index f44d94de8..09e25d655 100755 --- a/boot.php +++ b/boot.php @@ -342,6 +342,7 @@ define ( 'POLL_OVERWRITE', 0x8000); // If you vote twice remove the prior define ( 'UPDATE_FLAGS_UPDATED', 0x0001); define ( 'UPDATE_FLAGS_FORCED', 0x0002); +define ( 'UPDATE_FLAGS_CENSORED', 0x0004); define ( 'UPDATE_FLAGS_DELETED', 0x1000); diff --git a/include/connections.php b/include/connections.php index c43d361bc..1422d3582 100644 --- a/include/connections.php +++ b/include/connections.php @@ -1,5 +1,7 @@ Date: Mon, 6 May 2019 22:22:58 -0700 Subject: [PATCH 09/43] cleanup --- Zotlabs/Lib/Permcat.php | 1 + Zotlabs/Module/Connedit.php | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Zotlabs/Lib/Permcat.php b/Zotlabs/Lib/Permcat.php index 5430ea063..92f69cdbd 100644 --- a/Zotlabs/Lib/Permcat.php +++ b/Zotlabs/Lib/Permcat.php @@ -20,6 +20,7 @@ use Zotlabs\Access\Permissions; * content ACLs are evaluated (@ref ::Zotlabs::Access::AccessList "AccessList"). * These answer the question "Can Joe view *this* album/photo?". */ + class Permcat { /** diff --git a/Zotlabs/Module/Connedit.php b/Zotlabs/Module/Connedit.php index c803bbcce..c0f62bd0d 100644 --- a/Zotlabs/Module/Connedit.php +++ b/Zotlabs/Module/Connedit.php @@ -9,7 +9,11 @@ use Zotlabs\Lib\ActivityPub; use Zotlabs\Lib\Apps; use Zotlabs\Lib\AccessList; use Zotlabs\Access\Permissions; +use Zotlabs\Access\PermissionLimits; +use Zotlabs\Lib\Permcat; use Zotlabs\Daemon\Master; +use Zotlabs\Web\HTTPHeaders; +use Sabre\VObject\Reader; /* @file connedit.php * @brief In this file the connection-editor form is generated and evaluated. @@ -93,7 +97,7 @@ class Connedit extends Controller { call_hooks('contact_edit_post', $_POST); $vc = get_abconfig(local_channel(),$orig_record['abook_xchan'],'system','vcard'); - $vcard = (($vc) ? \Sabre\VObject\Reader::read($vc) : null); + $vcard = (($vc) ? Reader::read($vc) : null); $serialised_vcard = update_vcard($_REQUEST,$vcard); if($serialised_vcard) set_abconfig(local_channel(),$orig_record[0]['abook_xchan'],'system','vcard',$serialised_vcard); @@ -213,7 +217,7 @@ class Connedit extends Controller { if($default_group) { $g = AccessList::rec_byhash(local_channel(),$default_group); if($g) - AccessList::member_add(local_channel(),'',\App::$poi['abook_xchan'],$g['id']); + AccessList::member_add(local_channel(),'',App::$poi['abook_xchan'],$g['id']); } // Check if settings permit ("post new friend activity" is allowed, and @@ -263,7 +267,7 @@ class Connedit extends Controller { } if($new_friend) { - $arr = array('channel_id' => local_channel(), 'abook' => \App::$poi); + $arr = array('channel_id' => local_channel(), 'abook' => App::$poi); call_hooks('accept_follow', $arr); } @@ -328,7 +332,7 @@ class Connedit extends Controller { } $section = ((array_key_exists('section',$_REQUEST)) ? $_REQUEST['section'] : ''); - $channel = \App::get_channel(); + $channel = App::get_channel(); $yes_no = [ t('No'), t('Yes') ]; @@ -377,14 +381,14 @@ class Connedit extends Controller { $recurse = 0; $x = z_fetch_url(zid($url),false,$recurse,['session' => true]); if($x['success']) { - $h = new \Zotlabs\Web\HTTPHeaders($x['header']); + $h = new HTTPHeaders($x['header']); $fields = $h->fetch(); if($fields) { foreach($fields as $y) { if(array_key_exists('content-type',$y)) { $type = explode(';',trim($y['content-type'])); if($type && $type[0] === 'text/vcard' && $x['body']) { - $vc = \Sabre\VObject\Reader::read($x['body']); + $vc = Reader::read($x['body']); $vcard = $vc->serialize(); if($vcard) { set_abconfig(local_channel(),$orig_record[0]['abook_xchan'],'system','vcard',$vcard); @@ -408,7 +412,7 @@ class Connedit extends Controller { if($cmd === 'refresh') { if($orig_record[0]['xchan_network'] === 'zot6') { - if(! Libzot::refresh($orig_record[0],\App::get_channel())) + if(! Libzot::refresh($orig_record[0], App::get_channel())) notice( t('Refresh failed - channel is currently unavailable.') ); } else { @@ -625,7 +629,7 @@ class Connedit extends Controller { $vc = get_abconfig(local_channel(),$contact['abook_xchan'],'system','vcard'); - $vctmp = (($vc) ? \Sabre\VObject\Reader::read($vc) : null); + $vctmp = (($vc) ? Reader::read($vc) : null); $vcard = (($vctmp) ? get_vcard_array($vctmp,$contact['abook_id']) : [] ); if(! $vcard) $vcard['fn'] = $contact['xchan_name']; @@ -732,7 +736,7 @@ class Connedit extends Controller { foreach($global_perms as $k => $v) { $thisperm = ((in_array($k,$my_perms)) ? 1 : 0); - $checkinherited = \Zotlabs\Access\PermissionLimits::Get(local_channel(),$k); + $checkinherited = PermissionLimits::Get(local_channel(),$k); // For auto permissions (when $self is true) we don't want to look at existing // permissions because they are enabled for the channel owner @@ -742,7 +746,7 @@ class Connedit extends Controller { $perms[] = array('perms_' . $k, $v, ((array_key_exists($k,$their_perms)) ? intval($their_perms[$k]) : ''),$thisperm, 1, (($checkinherited & PERMS_SPECIFIC) ? '' : '1'), '', $checkinherited); } - $pcat = new \Zotlabs\Lib\Permcat(local_channel()); + $pcat = new Permcat(local_channel()); $pcatlist = $pcat->listing(); $permcats = []; if($pcatlist) { From ff686b354b0cd9f59a28f2c002a6a282a88a3432 Mon Sep 17 00:00:00 2001 From: zotlabs Date: Tue, 7 May 2019 19:40:49 -0700 Subject: [PATCH 10/43] remove techlevels --- Zotlabs/Lib/Techlevels.php | 21 --------------------- Zotlabs/Module/Admin/Account_edit.php | 5 +---- Zotlabs/Module/New_channel.php | 2 -- Zotlabs/Module/Notes.php | 4 ++-- Zotlabs/Module/Register.php | 5 ----- Zotlabs/Module/Settings/Channel.php | 5 ----- Zotlabs/Module/Settings/Features.php | 23 +---------------------- Zotlabs/Render/SmartyTemplate.php | 2 -- include/account.php | 14 -------------- include/features.php | 5 ----- view/tpl/admin_account_edit.tpl | 1 - view/tpl/settings_features.tpl | 8 -------- 12 files changed, 4 insertions(+), 91 deletions(-) delete mode 100644 Zotlabs/Lib/Techlevels.php diff --git a/Zotlabs/Lib/Techlevels.php b/Zotlabs/Lib/Techlevels.php deleted file mode 100644 index 380901678..000000000 --- a/Zotlabs/Lib/Techlevels.php +++ /dev/null @@ -1,21 +0,0 @@ - t('0. Beginner/Basic'), - '1' => t('1. Novice - not skilled but willing to learn'), - '2' => t('2. Intermediate - somewhat comfortable'), - '3' => t('3. Advanced - very comfortable'), - '4' => t('4. Expert - I can write computer code'), - '5' => t('5. Wizard - I probably know more than you do') - ]; - return $techlevels; - } - -} - diff --git a/Zotlabs/Module/Admin/Account_edit.php b/Zotlabs/Module/Admin/Account_edit.php index 6dfadf183..d9156c744 100644 --- a/Zotlabs/Module/Admin/Account_edit.php +++ b/Zotlabs/Module/Admin/Account_edit.php @@ -31,13 +31,11 @@ class Account_edit { } $service_class = trim($_REQUEST['service_class']); - $account_level = intval(trim($_REQUEST['account_level'])); $account_language = trim($_REQUEST['account_language']); - $r = q("update account set account_service_class = '%s', account_level = %d, account_language = '%s' + $r = q("update account set account_service_class = '%s', account_language = '%s' where account_id = %d", dbesc($service_class), - intval($account_level), dbesc($account_language), intval($account_id) ); @@ -68,7 +66,6 @@ class Account_edit { '$title' => t('Account Edit'), '$pass1' => [ 'pass1', t('New Password'), ' ','' ], '$pass2' => [ 'pass2', t('New Password again'), ' ','' ], - '$account_level' => [ 'account_level', t('Technical skill level'), $x[0]['account_level'], '', \Zotlabs\Lib\Techlevels::levels() ], '$account_language' => [ 'account_language' , t('Account language (for emails)'), $x[0]['account_language'], '', language_list() ], '$service_class' => [ 'service_class', t('Service class'), $x[0]['account_service_class'], '' ], '$submit' => t('Submit'), diff --git a/Zotlabs/Module/New_channel.php b/Zotlabs/Module/New_channel.php index 9bbcc94f2..ae9f3937b 100644 --- a/Zotlabs/Module/New_channel.php +++ b/Zotlabs/Module/New_channel.php @@ -178,8 +178,6 @@ class New_channel extends Controller { $privacy_role = ((x($_REQUEST,'permissions_role')) ? $_REQUEST['permissions_role'] : "" ); $perm_roles = \Zotlabs\Access\PermissionRoles::roles(); - if((get_account_techlevel() < 4) && $privacy_role !== 'custom') - unset($perm_roles[t('Other')]); $name = array('name', t('Channel name'), ((x($_REQUEST,'name')) ? $_REQUEST['name'] : ''), $name_help, "*"); $nickhub = '@' . \App::get_hostname(); diff --git a/Zotlabs/Module/Notes.php b/Zotlabs/Module/Notes.php index 07859f315..a9fc38dbb 100644 --- a/Zotlabs/Module/Notes.php +++ b/Zotlabs/Module/Notes.php @@ -2,9 +2,10 @@ namespace Zotlabs\Module; /** @file */ use Zotlabs\Lib\Libsync; +use Zotlabs\Web\Controller; -class Notes extends \Zotlabs\Web\Controller { +class Notes extends Controller { function init() { @@ -30,7 +31,6 @@ class Notes extends \Zotlabs\Web\Controller { // push updates to channel clones if((argc() > 1) && (argv(1) === 'sync')) { - require_once('include/zot.php'); Libsync::build_sync_packet(); } diff --git a/Zotlabs/Module/Register.php b/Zotlabs/Module/Register.php index 3dded19c7..f9d81be0c 100644 --- a/Zotlabs/Module/Register.php +++ b/Zotlabs/Module/Register.php @@ -227,11 +227,6 @@ class Register extends \Zotlabs\Web\Controller { $perm_roles = \Zotlabs\Access\PermissionRoles::roles(); - // A new account will not have a techlevel, but accounts can also be created by the administrator. - - if((get_account_techlevel() < 4) && $privacy_role !== 'custom') - unset($perm_roles[t('Other')]); - // Configurable terms of service link $tosurl = get_config('system','tos_url'); diff --git a/Zotlabs/Module/Settings/Channel.php b/Zotlabs/Module/Settings/Channel.php index 6cfeb231e..fec994eb8 100644 --- a/Zotlabs/Module/Settings/Channel.php +++ b/Zotlabs/Module/Settings/Channel.php @@ -508,11 +508,6 @@ class Channel { $permissions_set = (($permissions_role != 'custom') ? true : false); $perm_roles = \Zotlabs\Access\PermissionRoles::roles(); - if((get_account_techlevel() < 4) && $permissions_role !== 'custom') - unset($perm_roles[t('Other')]); - - - $vnotify = get_pconfig(local_channel(),'system','vnotify'); $always_show_in_notices = get_pconfig(local_channel(),'system','always_show_in_notices'); diff --git a/Zotlabs/Module/Settings/Features.php b/Zotlabs/Module/Settings/Features.php index dedf72ac4..fd8ebab96 100644 --- a/Zotlabs/Module/Settings/Features.php +++ b/Zotlabs/Module/Settings/Features.php @@ -29,25 +29,6 @@ class Features { $arr = []; $harr = []; - if(intval($_REQUEST['techlevel'])) - $level = intval($_REQUEST['techlevel']); - else { - $level = get_account_techlevel(); - } - - if(! intval($level)) { - notice( t('Permission denied.') . EOL); - return; - } - - $techlevels = \Zotlabs\Lib\Techlevels::levels(); - - // This page isn't accessible at techlevel 0 - - unset($techlevels[0]); - - $def_techlevel = (($level > 0) ? $level : 1); - $techlock = get_config('system','techlevel_lock'); $all_features_raw = get_features(false); @@ -57,7 +38,7 @@ class Features { } } - $features = get_features(true,$level); + $features = get_features(true); foreach($features as $fname => $fdata) { $arr[$fname] = array(); @@ -72,8 +53,6 @@ class Features { $o .= replace_macros($tpl, array( '$form_security_token' => get_form_security_token("settings_features"), '$title' => t('Additional Features'), - '$techlevel' => [ 'techlevel', t('Your technical skill level'), $def_techlevel, t('Used to provide a member experience and additional features consistent with your comfort level'), $techlevels ], - '$techlock' => $techlock, '$features' => $arr, '$hiddens' => $harr, '$baseurl' => z_root(), diff --git a/Zotlabs/Render/SmartyTemplate.php b/Zotlabs/Render/SmartyTemplate.php index f14d63064..616cb10c3 100755 --- a/Zotlabs/Render/SmartyTemplate.php +++ b/Zotlabs/Render/SmartyTemplate.php @@ -34,8 +34,6 @@ class SmartyTemplate implements TemplateEngine { // these are available for use in all templates $r['$z_baseurl'] = z_root(); - $r['$z_server_role'] = \Zotlabs\Lib\System::get_server_role(); - $r['$z_techlevel'] = get_account_techlevel(); if(gettype($s) === 'string') { $template = $s; diff --git a/include/account.php b/include/account.php index 7009bd6f7..c3faab17a 100644 --- a/include/account.php +++ b/include/account.php @@ -148,7 +148,6 @@ function create_account($arr) { $flags = ((x($arr,'account_flags')) ? intval($arr['account_flags']) : ACCOUNT_OK); $roles = ((x($arr,'account_roles')) ? intval($arr['account_roles']) : 0 ); $expires = ((x($arr,'expires')) ? intval($arr['expires']) : NULL_DATE); - $techlevel = ((array_key_exists('techlevel',$arr)) ? intval($arr['techlevel']) : intval(get_config('system','techlevel'))); $default_service_class = get_config('system','default_service_class'); @@ -216,7 +215,6 @@ function create_account($arr) { 'account_created' => datetime_convert(), 'account_flags' => intval($flags), 'account_roles' => intval($roles), - 'account_level' => intval($techlevel), 'account_expires' => $expires, 'account_service_class' => $default_service_class ] @@ -820,15 +818,3 @@ function upgrade_bool_message($bbcode = false) { } -function get_account_techlevel($account_id = 0) { - - if(! $account_id) { - $x = \App::get_account(); - } - else { - $x = get_account_by_id($account_id); - } - - return (($x) ? intval($x['account_level']) : 0); - -} diff --git a/include/features.php b/include/features.php index 844d0eb03..9ce7813a9 100644 --- a/include/features.php +++ b/include/features.php @@ -530,8 +530,6 @@ function get_features($filtered = true, $level = (-1)) { $arr = $x['features']; - $techlevel = (($level >= 0) ? $level : get_account_techlevel()); - // removed any locked features and remove the entire category if this makes it empty if($filtered) { @@ -542,9 +540,6 @@ function get_features($filtered = true, $level = (-1)) { for($y = 0; $y < count($arr[$k]); $y ++) { $disabled = false; if(is_array($arr[$k][$y])) { - if($arr[$k][$y][5] > $techlevel) { - $disabled = true; - } if($arr[$k][$y][4] !== false) { $disabled = true; } diff --git a/view/tpl/admin_account_edit.tpl b/view/tpl/admin_account_edit.tpl index 239d9084a..5f44d0cb0 100644 --- a/view/tpl/admin_account_edit.tpl +++ b/view/tpl/admin_account_edit.tpl @@ -8,7 +8,6 @@ {{include file="field_password.tpl" field=$pass1}} {{include file="field_password.tpl" field=$pass2}} -{{include file="field_select.tpl" field=$account_level}} {{include file="field_select.tpl" field=$account_language}} {{include file="field_input.tpl" field=$service_class}} diff --git a/view/tpl/settings_features.tpl b/view/tpl/settings_features.tpl index 398d96a4f..0d3be426f 100755 --- a/view/tpl/settings_features.tpl +++ b/view/tpl/settings_features.tpl @@ -4,14 +4,6 @@
- {{if ! $techlock}} -
- {{include file="field_select.tpl" field=$techlevel}} -
- {{else}} - - {{/if}} - {{if $hiddens}} {{foreach $hiddens as $k => $v}} From d747d9b7d3e24b9108fe8cdbad738c2ca570a606 Mon Sep 17 00:00:00 2001 From: zotlabs Date: Tue, 7 May 2019 21:50:02 -0700 Subject: [PATCH 11/43] get_rpost_path() moved to a static class method and was still being referenced as a global --- Zotlabs/Module/Rpost.php | 4 ++-- include/bbcode.php | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Zotlabs/Module/Rpost.php b/Zotlabs/Module/Rpost.php index b17e15ca6..35e9c0a0b 100644 --- a/Zotlabs/Module/Rpost.php +++ b/Zotlabs/Module/Rpost.php @@ -6,7 +6,7 @@ use App; use Zotlabs\Web\Controller; use Zotlabs\Access\AccessControl; use Zotlabs\Lib\PermissionDescription; - +use Zotlabs\Lib\Libzot; require_once('include/acl_selectors.php'); require_once('include/items.php'); @@ -46,7 +46,7 @@ class Rpost extends Controller { // by the wretched beast called 'suhosin'. All the browsers now allow long GET requests, but suhosin // blocks them. - $url = get_rpost_path(App::get_observer()); + $url = Libzot::get_rpost_path(App::get_observer()); // make sure we're not looping to our own hub if(($url) && (! stristr($url, App::get_hostname()))) { foreach($_GET as $key => $arg) { diff --git a/include/bbcode.php b/include/bbcode.php index 343dd8ca8..6f0b3b5d0 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -1,5 +1,8 @@ Date: Fri, 10 May 2019 14:39:55 -0700 Subject: [PATCH 12/43] don't do oembed processing on naked links --- Zotlabs/Render/SmartyInterface.php | 16 +- Zotlabs/Render/SmartyTemplate.php | 63 ++--- images/zapr-64.png | Bin 0 -> 5132 bytes images/zapr.svg | 392 +++++++++++++++++++++++++++++ images/zapr.xcf | Bin 0 -> 66247 bytes include/bbcode.php | 11 +- 6 files changed, 441 insertions(+), 41 deletions(-) create mode 100644 images/zapr-64.png create mode 100644 images/zapr.svg create mode 100644 images/zapr.xcf diff --git a/Zotlabs/Render/SmartyInterface.php b/Zotlabs/Render/SmartyInterface.php index 9c9a501c0..1dab7880e 100755 --- a/Zotlabs/Render/SmartyInterface.php +++ b/Zotlabs/Render/SmartyInterface.php @@ -2,7 +2,10 @@ namespace Zotlabs\Render; -class SmartyInterface extends \Smarty { +use Smarty; +use App; + +class SmartyInterface extends Smarty { public $filename; @@ -16,26 +19,27 @@ class SmartyInterface extends \Smarty { // The order is thus very important here $template_dirs = array('theme' => "view/theme/$thname/tpl/"); - if( x(\App::$theme_info,"extends") ) + if ( x(App::$theme_info,"extends") ) { $template_dirs = $template_dirs + array('extends' => "view/theme/" . \App::$theme_info["extends"] . "/tpl/"); + } $template_dirs = $template_dirs + array('base' => 'view/tpl/'); $this->setTemplateDir($template_dirs); - $basecompiledir = \App::$config['system']['smarty3_folder']; + $basecompiledir = App::$config['system']['smarty3_folder']; $this->setCompileDir($basecompiledir.'/compiled/'); $this->setConfigDir($basecompiledir.'/config/'); $this->setCacheDir($basecompiledir.'/cache/'); - $this->left_delimiter = \App::get_template_ldelim('smarty3'); - $this->right_delimiter = \App::get_template_rdelim('smarty3'); + $this->left_delimiter = App::get_template_ldelim('smarty3'); + $this->right_delimiter = App::get_template_rdelim('smarty3'); // Don't report errors so verbosely $this->error_reporting = E_ALL & (~E_NOTICE); } function parsed($template = '') { - if($template) { + if ($template) { return $this->fetch('string:' . $template); } return $this->fetch('file:' . $this->filename); diff --git a/Zotlabs/Render/SmartyTemplate.php b/Zotlabs/Render/SmartyTemplate.php index 616cb10c3..9bc5b8aab 100755 --- a/Zotlabs/Render/SmartyTemplate.php +++ b/Zotlabs/Render/SmartyTemplate.php @@ -2,28 +2,33 @@ namespace Zotlabs\Render; +use App; + + class SmartyTemplate implements TemplateEngine { static $name ="smarty3"; - public function __construct(){ + public function __construct() { // Cannot use get_config() here because it is called during installation when there is no DB. // FIXME: this may leak private information such as system pathnames. - $basecompiledir = ((array_key_exists('smarty3_folder',\App::$config['system'])) - ? \App::$config['system']['smarty3_folder'] : ''); - if (!$basecompiledir) $basecompiledir = str_replace('Zotlabs','',dirname(__dir__)) . "/" . TEMPLATE_BUILD_PATH; - if (!is_dir($basecompiledir)) { + $basecompiledir = ((array_key_exists('smarty3_folder', App::$config['system'])) + ? App::$config['system']['smarty3_folder'] : ''); + if (! $basecompiledir) { + $basecompiledir = str_replace('Zotlabs','',dirname(__dir__)) . "/" . TEMPLATE_BUILD_PATH; + } + if (! is_dir($basecompiledir)) { @os_mkdir(TEMPLATE_BUILD_PATH, STORAGE_DEFAULT_PERMISSIONS, true); - if (!is_dir($basecompiledir)) { + if (! is_dir($basecompiledir)) { echo "ERROR: folder $basecompiledir does not exist."; killme(); } } - if(!is_writable($basecompiledir)){ + if (! is_writable($basecompiledir)) { echo "ERROR: folder $basecompiledir must be writable by webserver."; killme(); } - \App::$config['system']['smarty3_folder'] = $basecompiledir; + App::$config['system']['smarty3_folder'] = $basecompiledir; } // TemplateEngine interface @@ -31,16 +36,16 @@ class SmartyTemplate implements TemplateEngine { public function replace_macros($s, $r) { $template = ''; - // these are available for use in all templates + // macro or macros available for use in all templates $r['$z_baseurl'] = z_root(); - if(gettype($s) === 'string') { + if (gettype($s) === 'string') { $template = $s; $s = new SmartyInterface(); } - foreach($r as $key=>$value) { - if($key[0] === '$') { + foreach ($r as $key => $value) { + if ($key[0] === '$') { $key = substr($key, 1); } $s->assign($key, $value); @@ -48,32 +53,32 @@ class SmartyTemplate implements TemplateEngine { return $s->parsed($template); } - public function get_markup_template($file, $root=''){ + public function get_markup_template($file, $root = '') { $template_file = theme_include($file, $root); - if($template_file) { + if ($template_file) { $template = new SmartyInterface(); $template->filename = $template_file; return $template; } - return ""; + return EMPTY_STR; } - public function get_intltext_template($file, $root='') { + public function get_intltext_template($file, $root = '') { - $lang = \App::$language; - if ($root != '' && substr($root,-1) != '/' ) { - $root .= '/'; - } - foreach (Array( - $root."view/$lang/$file", - $root."view/en/$file", - '' - ) as $template_file) { - if (is_file($template_file)) { break; } - } - if ($template_file=='') {$template_file = theme_include($file,$root);} - if($template_file) { + $lang = App::$language; + if ($root != '' && substr($root,-1) != '/' ) { + $root .= '/'; + } + foreach ( [ $root . "view/$lang/$file", $root . "view/en/$file", '' ] as $template_file) { + if (is_file($template_file)) { + break; + } + } + if ($template_file == '') { + $template_file = theme_include($file,$root); + } + if ($template_file) { $template = new SmartyInterface(); $template->filename = $template_file; return $template; diff --git a/images/zapr-64.png b/images/zapr-64.png new file mode 100644 index 0000000000000000000000000000000000000000..f45c82408f613e1082ea57a2b676831a422d3fde GIT binary patch literal 5132 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 uXvoDtz(z;Em7}dzUOQ$w@6E6IjQ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/zapr.xcf b/images/zapr.xcf new file mode 100644 index 0000000000000000000000000000000000000000..b7509bc4e84473db7684af2a6e0853ebee5b25c5 GIT binary patch literal 66247 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 E7uFCPR{#J2 literal 0 HcmV?d00001 diff --git a/include/bbcode.php b/include/bbcode.php index 6f0b3b5d0..364be1d32 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -90,12 +90,11 @@ function nakedoembed($match) { $strip_url = strip_escaped_zids($url); - $o = oembed_fetch_url($strip_url); - - if ($o['type'] == 'error') - return str_replace($url,$strip_url,$match[0]); - - return '[embed]' . $strip_url . '[/embed]'; + // this function no longer performs oembed on naked links + // because they author may have created naked links intentionally. + // Now it just strips zids on naked links. + + return str_replace($url,$strip_url,$match[0]); } function tryzrlaudio($match) { From 0cea34721ebea5acb99d318bc10336458002f5f8 Mon Sep 17 00:00:00 2001 From: zotlabs Date: Fri, 10 May 2019 15:29:05 -0700 Subject: [PATCH 13/43] optimise oembed pdf processing so we don't actually load the content, which could cause performance issues. --- include/oembed.php | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/include/oembed.php b/include/oembed.php index 2dbc46c4e..b1b9502ca 100644 --- a/include/oembed.php +++ b/include/oembed.php @@ -1,6 +1,6 @@ '', + 'title' => t('View PDF'), + 'type' => 'pdf' + ]; + // set $txt to something so that we don't attempt to fetch what could be a lengthy pdf. + $txt = EMPTY_STR; + } + if(is_null($txt)) { $txt = ""; @@ -215,18 +226,10 @@ function oembed_fetch_url($embedurl){ // save in cache if(! get_config('system','oembed_cache_disable')) - Zlib\Cache::set('[' . App::$videowidth . '] ' . $furl, $txt); + Cache::set('[' . App::$videowidth . '] ' . $furl, $txt); } - if(strpos(strtolower($embedurl),'.pdf') !== false) { - $action = 'allow'; - $j = [ - 'html' => '', - 'title' => t('View PDF'), - 'type' => 'pdf' - ]; - } if(! $j) { $j = json_decode($txt,true); From 23e8c52beaa708cbedc136fd49087230878e77f2 Mon Sep 17 00:00:00 2001 From: zotlabs Date: Fri, 10 May 2019 18:34:27 -0700 Subject: [PATCH 14/43] register page was missing form token --- Zotlabs/Module/Register.php | 12 ++++++++---- view/tpl/register.tpl | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Zotlabs/Module/Register.php b/Zotlabs/Module/Register.php index f9d81be0c..bc813f8e1 100644 --- a/Zotlabs/Module/Register.php +++ b/Zotlabs/Module/Register.php @@ -1,10 +1,11 @@ %s - INTERVAL %s", @@ -269,7 +272,8 @@ class Register extends \Zotlabs\Web\Controller { require_once('include/bbcode.php'); $o = replace_macros(get_markup_template('register.tpl'), array( - + + '$form_security_token' => get_form_security_token("register"), '$title' => t('Registration'), '$reg_is' => $registration_is, '$registertext' => bbcode(get_config('system','register_text')), diff --git a/view/tpl/register.tpl b/view/tpl/register.tpl index 33ca46ba1..1054c7567 100755 --- a/view/tpl/register.tpl +++ b/view/tpl/register.tpl @@ -4,6 +4,7 @@
+ {{if $reg_is}}
{{$reg_is}}
From fe1517022cb47a9f9a8afad78476b389b5aa4798 Mon Sep 17 00:00:00 2001 From: zotlabs Date: Sun, 12 May 2019 18:20:37 -0700 Subject: [PATCH 15/43] port calendar merge from hz --- Zotlabs/Lib/Apps.php | 3 +- Zotlabs/Lib/Libzotdir.php | 25 +- Zotlabs/Module/Calendar.php | 567 ++ Zotlabs/Module/Cdav.php | 223 +- Zotlabs/Photo/PhotoDriver.php | 2 +- Zotlabs/Widget/Cdav.php | 23 +- app/{caldav.apx => calendar.apd} | 4 +- app/{events.apd => events.apx} | 0 include/event.php | 30 +- include/photos.php | 25 +- .../CHANGELOG.txt | 0 .../CONTRIBUTING.txt | 0 library/fullcalendar.old/LICENSE.txt | 20 + .../fullcalendar.css | 0 .../fullcalendar.js | 0 .../fullcalendar.min.css | 0 .../fullcalendar.min.js | 0 .../fullcalendar.print.css | 0 .../fullcalendar.print.min.css | 0 .../gcal.js | 0 .../gcal.min.js | 0 .../locale-all.js | 0 library/fullcalendar/CHANGELOG.md | 1377 +++ library/fullcalendar/LICENSE.txt | 2 +- .../fullcalendar/demos/background-events.html | 109 + library/fullcalendar/demos/daygrid-views.html | 109 + library/fullcalendar/demos/default.html | 103 + .../demos/external-dragging-2cals.html | 75 + .../demos/external-dragging-builtin.html | 149 + library/fullcalendar/demos/full-height.html | 129 + .../fullcalendar/demos/google-calendar.html | 86 + .../fullcalendar/demos/js/theme-chooser.js | 141 + library/fullcalendar/demos/json.html | 93 + library/fullcalendar/demos/json/events.json | 56 + library/fullcalendar/demos/list-views.html | 118 + library/fullcalendar/demos/locales.html | 152 + library/fullcalendar/demos/php/get-events.php | 50 + .../fullcalendar/demos/php/get-time-zones.php | 9 + library/fullcalendar/demos/php/utils.php | 130 + library/fullcalendar/demos/rrule.html | 73 + library/fullcalendar/demos/selectable.html | 125 + library/fullcalendar/demos/themes.html | 215 + library/fullcalendar/demos/time-zones.html | 145 + .../fullcalendar/demos/timegrid-views.html | 113 + library/fullcalendar/demos/week-numbers.html | 118 + .../fullcalendar/packages/bootstrap/main.css | 33 + .../fullcalendar/packages/bootstrap/main.js | 90 + .../packages/bootstrap/main.min.css | 5 + .../packages/bootstrap/main.min.js | 20 + .../fullcalendar/packages/core/locales-all.js | 1353 +++ .../packages/core/locales-all.min.js | 6 + .../fullcalendar/packages/core/locales/af.js | 30 + .../packages/core/locales/ar-dz.js | 31 + .../packages/core/locales/ar-kw.js | 31 + .../packages/core/locales/ar-ly.js | 31 + .../packages/core/locales/ar-ma.js | 31 + .../packages/core/locales/ar-sa.js | 31 + .../packages/core/locales/ar-tn.js | 31 + .../fullcalendar/packages/core/locales/ar.js | 31 + .../fullcalendar/packages/core/locales/bg.js | 31 + .../fullcalendar/packages/core/locales/bs.js | 32 + .../fullcalendar/packages/core/locales/ca.js | 30 + .../fullcalendar/packages/core/locales/cs.js | 32 + .../fullcalendar/packages/core/locales/da.js | 30 + .../fullcalendar/packages/core/locales/de.js | 33 + .../fullcalendar/packages/core/locales/el.js | 30 + .../packages/core/locales/en-au.js | 17 + .../packages/core/locales/en-gb.js | 17 + .../packages/core/locales/en-nz.js | 17 + .../packages/core/locales/es-us.js | 30 + .../fullcalendar/packages/core/locales/es.js | 30 + .../fullcalendar/packages/core/locales/et.js | 32 + .../fullcalendar/packages/core/locales/eu.js | 30 + .../fullcalendar/packages/core/locales/fa.js | 33 + .../fullcalendar/packages/core/locales/fi.js | 30 + .../packages/core/locales/fr-ca.js | 27 + .../packages/core/locales/fr-ch.js | 31 + .../fullcalendar/packages/core/locales/fr.js | 31 + .../fullcalendar/packages/core/locales/gl.js | 30 + .../fullcalendar/packages/core/locales/he.js | 27 + .../fullcalendar/packages/core/locales/hi.js | 32 + .../fullcalendar/packages/core/locales/hr.js | 32 + .../fullcalendar/packages/core/locales/hu.js | 30 + .../fullcalendar/packages/core/locales/id.js | 30 + .../fullcalendar/packages/core/locales/is.js | 30 + .../fullcalendar/packages/core/locales/it.js | 32 + .../fullcalendar/packages/core/locales/ja.js | 28 + .../fullcalendar/packages/core/locales/ka.js | 32 + .../fullcalendar/packages/core/locales/kk.js | 32 + .../fullcalendar/packages/core/locales/ko.js | 26 + .../fullcalendar/packages/core/locales/lb.js | 30 + .../fullcalendar/packages/core/locales/lt.js | 30 + .../fullcalendar/packages/core/locales/lv.js | 32 + .../fullcalendar/packages/core/locales/mk.js | 28 + .../fullcalendar/packages/core/locales/ms.js | 32 + .../fullcalendar/packages/core/locales/nb.js | 30 + .../fullcalendar/packages/core/locales/nl.js | 30 + .../fullcalendar/packages/core/locales/nn.js | 30 + .../fullcalendar/packages/core/locales/pl.js | 30 + .../packages/core/locales/pt-br.js | 28 + .../fullcalendar/packages/core/locales/pt.js | 30 + .../fullcalendar/packages/core/locales/ro.js | 32 + .../fullcalendar/packages/core/locales/ru.js | 32 + .../fullcalendar/packages/core/locales/sk.js | 32 + .../fullcalendar/packages/core/locales/sl.js | 30 + .../fullcalendar/packages/core/locales/sq.js | 32 + .../packages/core/locales/sr-cyrl.js | 32 + .../fullcalendar/packages/core/locales/sr.js | 32 + .../fullcalendar/packages/core/locales/sv.js | 30 + .../fullcalendar/packages/core/locales/th.js | 25 + .../fullcalendar/packages/core/locales/tr.js | 30 + .../fullcalendar/packages/core/locales/uk.js | 32 + .../fullcalendar/packages/core/locales/vi.js | 32 + .../packages/core/locales/zh-cn.js | 33 + .../packages/core/locales/zh-tw.js | 26 + library/fullcalendar/packages/core/main.css | 900 ++ library/fullcalendar/packages/core/main.js | 8791 +++++++++++++++++ .../fullcalendar/packages/core/main.min.css | 5 + .../fullcalendar/packages/core/main.min.js | 9 + .../fullcalendar/packages/daygrid/main.css | 69 + library/fullcalendar/packages/daygrid/main.js | 1630 +++ .../packages/daygrid/main.min.css | 5 + .../fullcalendar/packages/daygrid/main.min.js | 20 + .../packages/google-calendar/main.js | 169 + .../packages/google-calendar/main.min.js | 20 + .../fullcalendar/packages/interaction/main.js | 2155 ++++ .../packages/interaction/main.min.js | 21 + library/fullcalendar/packages/list/main.css | 101 + library/fullcalendar/packages/list/main.js | 341 + .../fullcalendar/packages/list/main.min.css | 5 + .../fullcalendar/packages/list/main.min.js | 20 + library/fullcalendar/packages/luxon/main.js | 162 + .../fullcalendar/packages/luxon/main.min.js | 20 + .../packages/moment-timezone/main.js | 64 + .../packages/moment-timezone/main.min.js | 20 + library/fullcalendar/packages/moment/main.js | 103 + .../fullcalendar/packages/moment/main.min.js | 6 + library/fullcalendar/packages/rrule/main.js | 127 + .../fullcalendar/packages/rrule/main.min.js | 20 + .../fullcalendar/packages/timegrid/main.css | 266 + .../fullcalendar/packages/timegrid/main.js | 1339 +++ .../packages/timegrid/main.min.css | 5 + .../packages/timegrid/main.min.js | 20 + library/fullcalendar/vendor/rrule.js | 3617 +++++++ util/storageconv | 2 +- view/tpl/cdav_calendar.tpl | 561 +- view/tpl/cdav_widget_calendar.tpl | 27 +- view/tpl/event_head.tpl | 6 +- view/tpl/jot-header.tpl | 2 +- 149 files changed, 28385 insertions(+), 260 deletions(-) create mode 100644 Zotlabs/Module/Calendar.php rename app/{caldav.apx => calendar.apd} (59%) rename app/{events.apd => events.apx} (100%) rename library/{fullcalendar => fullcalendar.old}/CHANGELOG.txt (100%) rename library/{fullcalendar => fullcalendar.old}/CONTRIBUTING.txt (100%) create mode 100644 library/fullcalendar.old/LICENSE.txt rename library/{fullcalendar => fullcalendar.old}/fullcalendar.css (100%) rename library/{fullcalendar => fullcalendar.old}/fullcalendar.js (100%) rename library/{fullcalendar => fullcalendar.old}/fullcalendar.min.css (100%) rename library/{fullcalendar => fullcalendar.old}/fullcalendar.min.js (100%) rename library/{fullcalendar => fullcalendar.old}/fullcalendar.print.css (100%) rename library/{fullcalendar => fullcalendar.old}/fullcalendar.print.min.css (100%) rename library/{fullcalendar => fullcalendar.old}/gcal.js (100%) rename library/{fullcalendar => fullcalendar.old}/gcal.min.js (100%) rename library/{fullcalendar => fullcalendar.old}/locale-all.js (100%) create mode 100644 library/fullcalendar/CHANGELOG.md create mode 100644 library/fullcalendar/demos/background-events.html create mode 100644 library/fullcalendar/demos/daygrid-views.html create mode 100644 library/fullcalendar/demos/default.html create mode 100644 library/fullcalendar/demos/external-dragging-2cals.html create mode 100644 library/fullcalendar/demos/external-dragging-builtin.html create mode 100644 library/fullcalendar/demos/full-height.html create mode 100644 library/fullcalendar/demos/google-calendar.html create mode 100644 library/fullcalendar/demos/js/theme-chooser.js create mode 100644 library/fullcalendar/demos/json.html create mode 100644 library/fullcalendar/demos/json/events.json create mode 100644 library/fullcalendar/demos/list-views.html create mode 100644 library/fullcalendar/demos/locales.html create mode 100644 library/fullcalendar/demos/php/get-events.php create mode 100644 library/fullcalendar/demos/php/get-time-zones.php create mode 100644 library/fullcalendar/demos/php/utils.php create mode 100644 library/fullcalendar/demos/rrule.html create mode 100644 library/fullcalendar/demos/selectable.html create mode 100644 library/fullcalendar/demos/themes.html create mode 100644 library/fullcalendar/demos/time-zones.html create mode 100644 library/fullcalendar/demos/timegrid-views.html create mode 100644 library/fullcalendar/demos/week-numbers.html create mode 100644 library/fullcalendar/packages/bootstrap/main.css create mode 100644 library/fullcalendar/packages/bootstrap/main.js create mode 100644 library/fullcalendar/packages/bootstrap/main.min.css create mode 100644 library/fullcalendar/packages/bootstrap/main.min.js create mode 100644 library/fullcalendar/packages/core/locales-all.js create mode 100644 library/fullcalendar/packages/core/locales-all.min.js create mode 100644 library/fullcalendar/packages/core/locales/af.js create mode 100644 library/fullcalendar/packages/core/locales/ar-dz.js create mode 100644 library/fullcalendar/packages/core/locales/ar-kw.js create mode 100644 library/fullcalendar/packages/core/locales/ar-ly.js create mode 100644 library/fullcalendar/packages/core/locales/ar-ma.js create mode 100644 library/fullcalendar/packages/core/locales/ar-sa.js create mode 100644 library/fullcalendar/packages/core/locales/ar-tn.js create mode 100644 library/fullcalendar/packages/core/locales/ar.js create mode 100644 library/fullcalendar/packages/core/locales/bg.js create mode 100644 library/fullcalendar/packages/core/locales/bs.js create mode 100644 library/fullcalendar/packages/core/locales/ca.js create mode 100644 library/fullcalendar/packages/core/locales/cs.js create mode 100644 library/fullcalendar/packages/core/locales/da.js create mode 100644 library/fullcalendar/packages/core/locales/de.js create mode 100644 library/fullcalendar/packages/core/locales/el.js create mode 100644 library/fullcalendar/packages/core/locales/en-au.js create mode 100644 library/fullcalendar/packages/core/locales/en-gb.js create mode 100644 library/fullcalendar/packages/core/locales/en-nz.js create mode 100644 library/fullcalendar/packages/core/locales/es-us.js create mode 100644 library/fullcalendar/packages/core/locales/es.js create mode 100644 library/fullcalendar/packages/core/locales/et.js create mode 100644 library/fullcalendar/packages/core/locales/eu.js create mode 100644 library/fullcalendar/packages/core/locales/fa.js create mode 100644 library/fullcalendar/packages/core/locales/fi.js create mode 100644 library/fullcalendar/packages/core/locales/fr-ca.js create mode 100644 library/fullcalendar/packages/core/locales/fr-ch.js create mode 100644 library/fullcalendar/packages/core/locales/fr.js create mode 100644 library/fullcalendar/packages/core/locales/gl.js create mode 100644 library/fullcalendar/packages/core/locales/he.js create mode 100644 library/fullcalendar/packages/core/locales/hi.js create mode 100644 library/fullcalendar/packages/core/locales/hr.js create mode 100644 library/fullcalendar/packages/core/locales/hu.js create mode 100644 library/fullcalendar/packages/core/locales/id.js create mode 100644 library/fullcalendar/packages/core/locales/is.js create mode 100644 library/fullcalendar/packages/core/locales/it.js create mode 100644 library/fullcalendar/packages/core/locales/ja.js create mode 100644 library/fullcalendar/packages/core/locales/ka.js create mode 100644 library/fullcalendar/packages/core/locales/kk.js create mode 100644 library/fullcalendar/packages/core/locales/ko.js create mode 100644 library/fullcalendar/packages/core/locales/lb.js create mode 100644 library/fullcalendar/packages/core/locales/lt.js create mode 100644 library/fullcalendar/packages/core/locales/lv.js create mode 100644 library/fullcalendar/packages/core/locales/mk.js create mode 100644 library/fullcalendar/packages/core/locales/ms.js create mode 100644 library/fullcalendar/packages/core/locales/nb.js create mode 100644 library/fullcalendar/packages/core/locales/nl.js create mode 100644 library/fullcalendar/packages/core/locales/nn.js create mode 100644 library/fullcalendar/packages/core/locales/pl.js create mode 100644 library/fullcalendar/packages/core/locales/pt-br.js create mode 100644 library/fullcalendar/packages/core/locales/pt.js create mode 100644 library/fullcalendar/packages/core/locales/ro.js create mode 100644 library/fullcalendar/packages/core/locales/ru.js create mode 100644 library/fullcalendar/packages/core/locales/sk.js create mode 100644 library/fullcalendar/packages/core/locales/sl.js create mode 100644 library/fullcalendar/packages/core/locales/sq.js create mode 100644 library/fullcalendar/packages/core/locales/sr-cyrl.js create mode 100644 library/fullcalendar/packages/core/locales/sr.js create mode 100644 library/fullcalendar/packages/core/locales/sv.js create mode 100644 library/fullcalendar/packages/core/locales/th.js create mode 100644 library/fullcalendar/packages/core/locales/tr.js create mode 100644 library/fullcalendar/packages/core/locales/uk.js create mode 100644 library/fullcalendar/packages/core/locales/vi.js create mode 100644 library/fullcalendar/packages/core/locales/zh-cn.js create mode 100644 library/fullcalendar/packages/core/locales/zh-tw.js create mode 100644 library/fullcalendar/packages/core/main.css create mode 100644 library/fullcalendar/packages/core/main.js create mode 100644 library/fullcalendar/packages/core/main.min.css create mode 100644 library/fullcalendar/packages/core/main.min.js create mode 100644 library/fullcalendar/packages/daygrid/main.css create mode 100644 library/fullcalendar/packages/daygrid/main.js create mode 100644 library/fullcalendar/packages/daygrid/main.min.css create mode 100644 library/fullcalendar/packages/daygrid/main.min.js create mode 100644 library/fullcalendar/packages/google-calendar/main.js create mode 100644 library/fullcalendar/packages/google-calendar/main.min.js create mode 100644 library/fullcalendar/packages/interaction/main.js create mode 100644 library/fullcalendar/packages/interaction/main.min.js create mode 100644 library/fullcalendar/packages/list/main.css create mode 100644 library/fullcalendar/packages/list/main.js create mode 100644 library/fullcalendar/packages/list/main.min.css create mode 100644 library/fullcalendar/packages/list/main.min.js create mode 100644 library/fullcalendar/packages/luxon/main.js create mode 100644 library/fullcalendar/packages/luxon/main.min.js create mode 100644 library/fullcalendar/packages/moment-timezone/main.js create mode 100644 library/fullcalendar/packages/moment-timezone/main.min.js create mode 100644 library/fullcalendar/packages/moment/main.js create mode 100644 library/fullcalendar/packages/moment/main.min.js create mode 100644 library/fullcalendar/packages/rrule/main.js create mode 100644 library/fullcalendar/packages/rrule/main.min.js create mode 100644 library/fullcalendar/packages/timegrid/main.css create mode 100644 library/fullcalendar/packages/timegrid/main.js create mode 100644 library/fullcalendar/packages/timegrid/main.min.css create mode 100644 library/fullcalendar/packages/timegrid/main.min.js create mode 100644 library/fullcalendar/vendor/rrule.js diff --git a/Zotlabs/Lib/Apps.php b/Zotlabs/Lib/Apps.php index b74de3a0e..50b40039a 100644 --- a/Zotlabs/Lib/Apps.php +++ b/Zotlabs/Lib/Apps.php @@ -66,7 +66,7 @@ class Apps { 'Channel Home', 'View Profile', 'Photos', - 'Events', + 'Calendar', 'Directory', 'Search', 'Profile Photo' @@ -303,6 +303,7 @@ class Apps { 'Affinity Tool' => t('Affinity Tool'), 'Articles' => t('Articles'), 'Cards' => t('Cards'), + 'Calendar' => t('Calendar'), 'Categories' => t('Categories'), 'Admin' => t('Site Admin'), 'Content Filter' => t('Content Filter'), diff --git a/Zotlabs/Lib/Libzotdir.php b/Zotlabs/Lib/Libzotdir.php index 7dec1a2d5..d87a11d36 100644 --- a/Zotlabs/Lib/Libzotdir.php +++ b/Zotlabs/Lib/Libzotdir.php @@ -85,7 +85,7 @@ class Libzotdir { if ($directory) { $j = Zotfinger::exec($directory); - if(array_path_exists('data/directory_mode',$j)) { + if (array_path_exists('data/directory_mode',$j)) { if ($j['data']['directory_mode'] === 'normal') { $isadir = false; } @@ -240,7 +240,6 @@ class Libzotdir { // for brand new directory servers, only load the last couple of days. // It will take about a month for a new directory to obtain the full current repertoire of channels. - /** @FIXME Go back and pick up earlier ratings if this is a new directory server. These do not get refreshed. */ $token = get_config('system','realm_token'); @@ -266,9 +265,10 @@ class Libzotdir { $r = q("select * from updates where ud_guid = '%s' limit 1", dbesc($t['transaction_id']) ); - if($r) + if ($r) { continue; - + } + $ud_flags = 0; if (is_array($t['flags']) && in_array('deleted',$t['flags'])) $ud_flags |= UPDATE_FLAGS_DELETED; @@ -344,7 +344,7 @@ class Libzotdir { logger('local_dir_update: uid: ' . $uid, LOGGER_DEBUG); - $p = q("select channel.channel_hash, channel_address, channel_timezone, profile.* from profile left join channel on channel_id = uid where uid = %d and is_default = 1", + $p = q("select channel_hash, channel_address, channel_timezone, profile.* from profile left join channel on channel_id = uid where uid = %d and is_default = 1", intval($uid) ); @@ -384,7 +384,7 @@ class Libzotdir { $hidden = (1 - intval($p[0]['publish'])); - logger('hidden: ' . $hidden); + // logger('hidden: ' . $hidden); $r = q("select xchan_hidden from xchan where xchan_hash = '%s' limit 1", dbesc($p[0]['channel_hash']) @@ -554,10 +554,11 @@ class Libzotdir { } $d = [ - 'xprof' => $arr, + 'xprof' => $arr, 'profile' => $profile, - 'update' => $update + 'update' => $update ]; + /** * @hooks import_directory_profile * Called when processing delivery of a profile structure from an external source (usually for directory storage). @@ -565,11 +566,13 @@ class Libzotdir { * * \e array \b profile * * \e boolean \b update */ + call_hooks('import_directory_profile', $d); - if (($d['update']) && (! $suppress_update)) - self::update_modtime($arr['xprof_hash'],random_string() . '@' . \App::get_hostname(), $addr, $ud_flags); - + if (($d['update']) && (! $suppress_update)) { + self::update_modtime($arr['xprof_hash'], new_uuid(), $addr, $ud_flags); + } + return $d['update']; } diff --git a/Zotlabs/Module/Calendar.php b/Zotlabs/Module/Calendar.php new file mode 100644 index 000000000..b50dce1db --- /dev/null +++ b/Zotlabs/Module/Calendar.php @@ -0,0 +1,567 @@ +set($x[0]); + + $created = $x[0]['created']; + $edited = datetime_convert(); + } + else { + $created = $edited = datetime_convert(); + $acl->set_from_array($_POST); + } + + $post_tags = array(); + $channel = \App::get_channel(); + $ac = $acl->get(); + + if(strlen($categories)) { + $cats = explode(',',$categories); + foreach($cats as $cat) { + $post_tags[] = array( + 'uid' => $profile_uid, + 'ttype' => TERM_CATEGORY, + 'otype' => TERM_OBJ_POST, + 'term' => trim($cat), + 'url' => $channel['xchan_url'] . '?f=&cat=' . urlencode(trim($cat)) + ); + } + } + + $datarray = array(); + $datarray['dtstart'] = $start; + $datarray['dtend'] = $finish; + $datarray['summary'] = $summary; + $datarray['description'] = $desc; + $datarray['location'] = $location; + $datarray['etype'] = $type; + $datarray['adjust'] = $adjust; + $datarray['nofinish'] = $nofinish; + $datarray['uid'] = local_channel(); + $datarray['account'] = get_account_id(); + $datarray['event_xchan'] = $channel['channel_hash']; + $datarray['allow_cid'] = $ac['allow_cid']; + $datarray['allow_gid'] = $ac['allow_gid']; + $datarray['deny_cid'] = $ac['deny_cid']; + $datarray['deny_gid'] = $ac['deny_gid']; + $datarray['private'] = (($acl->is_private()) ? 1 : 0); + $datarray['id'] = $event_id; + $datarray['created'] = $created; + $datarray['edited'] = $edited; + + if(intval($_REQUEST['preview'])) { + $html = format_event_html($datarray); + echo $html; + killme(); + } + + $event = event_store_event($datarray); + + if($post_tags) + $datarray['term'] = $post_tags; + + $item_id = event_store_item($datarray,$event); + + if($item_id) { + $r = q("select * from item where id = %d", + intval($item_id) + ); + if($r) { + xchan_query($r); + $sync_item = fetch_post_tags($r); + $z = q("select * from event where event_hash = '%s' and uid = %d limit 1", + dbesc($r[0]['resource_id']), + intval($channel['channel_id']) + ); + if($z) { + build_sync_packet($channel['channel_id'],array('event_item' => array(encode_item($sync_item[0],true)),'event' => $z)); + } + } + } + + \Zotlabs\Daemon\Master::Summon(array('Notifier','event',$item_id)); + + killme(); + + } + + + + function get() { + + if(argc() > 2 && argv(1) == 'ical') { + $event_id = argv(2); + + require_once('include/security.php'); + $sql_extra = permissions_sql(local_channel()); + + $r = q("select * from event where event_hash = '%s' $sql_extra limit 1", + dbesc($event_id) + ); + if($r) { + header('Content-type: text/calendar'); + header('content-disposition: attachment; filename="' . t('event') . '-' . $event_id . '.ics"' ); + echo ical_wrapper($r); + killme(); + } + else { + notice( t('Event not found.') . EOL ); + return; + } + } + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + return; + } + + if((argc() > 2) && (argv(1) === 'ignore') && intval(argv(2))) { + $r = q("update event set dismissed = 1 where id = %d and uid = %d", + intval(argv(2)), + intval(local_channel()) + ); + } + + if((argc() > 2) && (argv(1) === 'unignore') && intval(argv(2))) { + $r = q("update event set dismissed = 0 where id = %d and uid = %d", + intval(argv(2)), + intval(local_channel()) + ); + } + + $channel = \App::get_channel(); + + $mode = 'view'; + $export = false; + //$y = 0; + //$m = 0; + $ignored = ((x($_REQUEST,'ignored')) ? " and dismissed = " . intval($_REQUEST['ignored']) . " " : ''); + + if(argc() > 1) { + if(argc() > 2 && argv(1) === 'add') { + $mode = 'add'; + $item_id = intval(argv(2)); + } + if(argc() > 2 && argv(1) === 'drop') { + $mode = 'drop'; + $event_id = argv(2); + } + if(argc() <= 2 && argv(1) === 'export') { + $export = true; + } + if(argc() > 2 && intval(argv(1)) && intval(argv(2))) { + $mode = 'view'; + //$y = intval(argv(1)); + //$m = intval(argv(2)); + } + if(argc() <= 2) { + $mode = 'view'; + $event_id = argv(1); + } + } + + if($mode === 'add') { + event_addtocal($item_id,local_channel()); + killme(); + } + + if($mode == 'view') { + + /* edit/create form */ + if($event_id) { + $r = q("SELECT * FROM event WHERE event_hash = '%s' AND uid = %d LIMIT 1", + dbesc($event_id), + intval(local_channel()) + ); + if(count($r)) + $orig_event = $r[0]; + } + + $channel = \App::get_channel(); + +/* + // Passed parameters overrides anything found in the DB + if(!x($orig_event)) + $orig_event = array(); + + $n_checked = ((x($orig_event) && $orig_event['nofinish']) ? ' checked="checked" ' : ''); + $a_checked = ((x($orig_event) && $orig_event['adjust']) ? ' checked="checked" ' : ''); + $t_orig = ((x($orig_event)) ? $orig_event['summary'] : ''); + $d_orig = ((x($orig_event)) ? $orig_event['description'] : ''); + $l_orig = ((x($orig_event)) ? $orig_event['location'] : ''); + $eid = ((x($orig_event)) ? $orig_event['id'] : 0); + $event_xchan = ((x($orig_event)) ? $orig_event['event_xchan'] : $channel['channel_hash']); + $mid = ((x($orig_event)) ? $orig_event['mid'] : ''); + + $sdt = ((x($orig_event)) ? $orig_event['dtstart'] : 'now'); + + $fdt = ((x($orig_event)) ? $orig_event['dtend'] : '+1 hour'); + + $tz = date_default_timezone_get(); + if(x($orig_event)) + $tz = (($orig_event['adjust']) ? date_default_timezone_get() : 'UTC'); + + $syear = datetime_convert('UTC', $tz, $sdt, 'Y'); + $smonth = datetime_convert('UTC', $tz, $sdt, 'm'); + $sday = datetime_convert('UTC', $tz, $sdt, 'd'); + $shour = datetime_convert('UTC', $tz, $sdt, 'H'); + $sminute = datetime_convert('UTC', $tz, $sdt, 'i'); + + $stext = datetime_convert('UTC',$tz,$sdt); + $stext = substr($stext,0,14) . "00:00"; + + $fyear = datetime_convert('UTC', $tz, $fdt, 'Y'); + $fmonth = datetime_convert('UTC', $tz, $fdt, 'm'); + $fday = datetime_convert('UTC', $tz, $fdt, 'd'); + $fhour = datetime_convert('UTC', $tz, $fdt, 'H'); + $fminute = datetime_convert('UTC', $tz, $fdt, 'i'); + + $ftext = datetime_convert('UTC',$tz,$fdt); + $ftext = substr($ftext,0,14) . "00:00"; + + $type = ((x($orig_event)) ? $orig_event['etype'] : 'event'); + + $f = get_config('system','event_input_format'); + if(! $f) + $f = 'ymd'; + + $thisyear = datetime_convert('UTC',date_default_timezone_get(),'now','Y'); + $thismonth = datetime_convert('UTC',date_default_timezone_get(),'now','m'); + if(! $y) + $y = intval($thisyear); + if(! $m) + $m = intval($thismonth); + + + // Put some limits on dates. The PHP date functions don't seem to do so well before 1900. + // An upper limit was chosen to keep search engines from exploring links millions of years in the future. + + if($y < 1901) + $y = 1900; + if($y > 2099) + $y = 2100; + + $nextyear = $y; + $nextmonth = $m + 1; + if($nextmonth > 12) { + $nextmonth = 1; + $nextyear ++; + } + + $prevyear = $y; + if($m > 1) + $prevmonth = $m - 1; + else { + $prevmonth = 12; + $prevyear --; + } + + $dim = get_dim($y,$m); + $start = sprintf('%d-%d-%d %d:%d:%d',$y,$m,1,0,0,0); + $finish = sprintf('%d-%d-%d %d:%d:%d',$y,$m,$dim,23,59,59); +*/ + + if (argv(1) === 'json'){ + if (x($_GET,'start')) $start = $_GET['start']; + if (x($_GET,'end')) $finish = $_GET['end']; + } + + $start = datetime_convert('UTC','UTC',$start); + $finish = datetime_convert('UTC','UTC',$finish); + + $adjust_start = datetime_convert('UTC', date_default_timezone_get(), $start); + $adjust_finish = datetime_convert('UTC', date_default_timezone_get(), $finish); + + if (x($_GET,'id')){ + $r = q("SELECT event.*, item.plink, item.item_flags, item.author_xchan, item.owner_xchan, item.id as item_id + from event left join item on item.resource_id = event.event_hash + where item.resource_type = 'event' and event.uid = %d and event.id = %d limit 1", + intval(local_channel()), + intval($_GET['id']) + ); + } + elseif($export) { + $r = q("SELECT * from event where uid = %d", + intval(local_channel()) + ); + } + else { + // fixed an issue with "nofinish" events not showing up in the calendar. + // There's still an issue if the finish date crosses the end of month. + // Noting this for now - it will need to be fixed here and in Friendica. + // Ultimately the finish date shouldn't be involved in the query. + + $r = q("SELECT event.*, item.plink, item.item_flags, item.author_xchan, item.owner_xchan, item.id as item_id + from event left join item on event.event_hash = item.resource_id + where item.resource_type = 'event' and event.uid = %d and event.uid = item.uid $ignored + AND (( event.adjust = 0 AND ( event.dtend >= '%s' or event.nofinish = 1 ) AND event.dtstart <= '%s' ) + OR ( event.adjust = 1 AND ( event.dtend >= '%s' or event.nofinish = 1 ) AND event.dtstart <= '%s' )) ", + intval(local_channel()), + dbesc($start), + dbesc($finish), + dbesc($adjust_start), + dbesc($adjust_finish) + ); + + } + + //$links = []; + + if($r && ! $export) { + xchan_query($r); + $r = fetch_post_tags($r,true); + + $r = sort_by_date($r); + } + +/* + if($r) { + foreach($r as $rr) { + $j = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], 'j') : datetime_convert('UTC','UTC',$rr['dtstart'],'j')); + if(! x($links,$j)) + $links[$j] = z_root() . '/' . \App::$cmd . '#link-' . $j; + } + } +*/ + + $events = []; + + //$last_date = ''; + //$fmt = t('l, F j'); + + if($r) { + + foreach($r as $rr) { + //$j = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], 'j') : datetime_convert('UTC','UTC',$rr['dtstart'],'j')); + //$d = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], $fmt) : datetime_convert('UTC','UTC',$rr['dtstart'],$fmt)); + //$d = day_translate($d); + + $start = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], 'c') : datetime_convert('UTC','UTC',$rr['dtstart'],'c')); + if ($rr['nofinish']){ + $end = null; + } else { + $end = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtend'], 'c') : datetime_convert('UTC','UTC',$rr['dtend'],'c')); + + // give a fake end to birthdays so they get crammed into a + // single day on the calendar + + if($rr['etype'] === 'birthday') + $end = null; + } + + $catsenabled = feature_enabled(local_channel(),'categories'); + $categories = ''; + if($catsenabled){ + if($rr['term']) { + $cats = get_terms_oftype($rr['term'], TERM_CATEGORY); + foreach ($cats as $cat) { + if(strlen($categories)) + $categories .= ', '; + $categories .= $cat['term']; + } + } + } + + $allDay = false; + + // allDay event rules + if(!strpos($start, 'T') && !strpos($end, 'T')) + $allDay = true; + if(strpos($start, 'T00:00:00') && strpos($end, 'T00:00:00')) + $allDay = true; + + //$is_first = ($d !== $last_date); + + //$last_date = $d; + + $edit = ((local_channel() && $rr['author_xchan'] == get_observer_hash()) ? array(z_root().'/events/'.$rr['event_hash'].'?expandform=1',t('Edit event'),'','') : false); + + $drop = array(z_root().'/events/drop/'.$rr['event_hash'],t('Delete event'),'',''); + + + $events[] = [ + 'calendar_id' => 'calendar', + 'rw' => true, + 'id' => $rr['id'], + 'uri' => $rr['event_hash'], + 'start' => $start, + 'end' => $end, + 'drop' => $drop, + 'allDay' => $allDay, + 'title' => htmlentities($rr['summary'], ENT_COMPAT, 'UTF-8'), + 'editable' => $edit ? true : false, + 'item' => $rr, + 'plink' => [ $rr['plink'], t('Link to source') ], + 'description' => htmlentities($rr['description'], ENT_COMPAT, 'UTF-8'), + 'location' => htmlentities($rr['location'], ENT_COMPAT, 'UTF-8'), + 'allow_cid' => expand_acl($rr['allow_cid']), + 'allow_gid' => expand_acl($rr['allow_gid']), + 'deny_cid' => expand_acl($rr['deny_cid']), + 'deny_gid' => expand_acl($rr['deny_gid']), + 'categories' => $categories + ]; + } + } + + if($export) { + header('Content-type: text/calendar'); + header('content-disposition: attachment; filename="' . t('calendar') . '-' . $channel['channel_address'] . '.ics"' ); + echo ical_wrapper($r); + killme(); + } + + if (\App::$argv[1] === 'json'){ + json_return_and_die($events); + } + } + + + if($mode === 'drop' && $event_id) { + $r = q("SELECT * FROM event WHERE event_hash = '%s' AND uid = %d LIMIT 1", + dbesc($event_id), + intval(local_channel()) + ); + + $sync_event = $r[0]; + + if($r) { + $r = q("delete from event where event_hash = '%s' and uid = %d", + dbesc($event_id), + intval(local_channel()) + ); + if($r) { + $r = q("update item set resource_type = '', resource_id = '' where resource_type = 'event' and resource_id = '%s' and uid = %d", + dbesc($event_id), + intval(local_channel()) + ); + $sync_event['event_deleted'] = 1; + build_sync_packet(0,array('event' => array($sync_event))); + killme(); + } + notice( t('Failed to remove event' ) . EOL); + killme(); + } + } + + } + +} diff --git a/Zotlabs/Module/Cdav.php b/Zotlabs/Module/Cdav.php index bff308dfa..c579fe045 100644 --- a/Zotlabs/Module/Cdav.php +++ b/Zotlabs/Module/Cdav.php @@ -133,10 +133,6 @@ class Cdav extends Controller { logger('loggedin'); - if((argv(1) == 'calendars') && (!Apps::system_app_installed(local_channel(), 'CalDAV'))) { - killme(); - } - if((argv(1) == 'addressbooks') && (!Apps::system_app_installed(local_channel(), 'CardDAV'))) { killme(); } @@ -221,10 +217,6 @@ class Cdav extends Controller { if(! local_channel()) return; - if((argv(1) === 'calendar') && (! Apps::system_app_installed(local_channel(), 'CalDAV'))) { - return; - } - if((argv(1) === 'addressbook') && (! Apps::system_app_installed(local_channel(), 'CardDAV'))) { return; } @@ -280,9 +272,12 @@ class Cdav extends Controller { return; $title = $_REQUEST['title']; - $dtstart = new \DateTime($_REQUEST['dtstart']); - if($_REQUEST['dtend']) - $dtend = new \DateTime($_REQUEST['dtend']); + $start = datetime_convert(App::$timezone, 'UTC', $_REQUEST['dtstart']); + $dtstart = new \DateTime($start); + if($_REQUEST['dtend']) { + $end = datetime_convert(App::$timezone, 'UTC', $_REQUEST['dtend']); + $dtend = new \DateTime($end); + } $description = $_REQUEST['description']; $location = $_REQUEST['location']; @@ -306,13 +301,17 @@ class Cdav extends Controller { 'DTSTART' => $dtstart ] ]); - if($dtend) + if($dtend) { $vcalendar->VEVENT->add('DTEND', $dtend); + $vcalendar->VEVENT->DTEND['TZID'] = App::$timezone; + } if($description) $vcalendar->VEVENT->add('DESCRIPTION', $description); if($location) $vcalendar->VEVENT->add('LOCATION', $location); + $vcalendar->VEVENT->DTSTART['TZID'] = App::$timezone; + $calendarData = $vcalendar->serialize(); $caldavBackend->createCalendarObject($id, $objectUri, $calendarData); @@ -351,8 +350,12 @@ class Cdav extends Controller { $uri = $_REQUEST['uri']; $title = $_REQUEST['title']; - $dtstart = new \DateTime($_REQUEST['dtstart']); - $dtend = $_REQUEST['dtend'] ? new \DateTime($_REQUEST['dtend']) : ''; + $start = datetime_convert(App::$timezone, 'UTC', $_REQUEST['dtstart']); + $dtstart = new \DateTime($start); + if($_REQUEST['dtend']) { + $end = datetime_convert(App::$timezone, 'UTC', $_REQUEST['dtend']); + $dtend = new \DateTime($end); + } $description = $_REQUEST['description']; $location = $_REQUEST['location']; @@ -404,8 +407,12 @@ class Cdav extends Controller { return; $uri = $_REQUEST['uri']; - $dtstart = new \DateTime($_REQUEST['dtstart']); - $dtend = $_REQUEST['dtend'] ? new \DateTime($_REQUEST['dtend']) : ''; + $start = datetime_convert(App::$timezone, 'UTC', $_REQUEST['dtstart']); + $dtstart = new \DateTime($start); + if($_REQUEST['dtend']) { + $end = datetime_convert(App::$timezone, 'UTC', $_REQUEST['dtend']); + $dtend = new \DateTime($end); + } $object = $caldavBackend->getCalendarObject($id, $uri); @@ -747,16 +754,27 @@ class Cdav extends Controller { //Import calendar or addressbook if(($_FILES) && array_key_exists('userfile',$_FILES) && intval($_FILES['userfile']['size']) && $_REQUEST['target']) { - $src = @file_get_contents($_FILES['userfile']['tmp_name']); + $src = $_FILES['userfile']['tmp_name']; if($src) { if($_REQUEST['c_upload']) { + if($_REQUEST['target'] == 'calendar') { + $result = parse_ical_file($src,local_channel()); + if($result) + info( t('Calendar entries imported.') . EOL); + else + notice( t('No calendar entries found.') . EOL); + + @unlink($src); + return; + } + $id = explode(':', $_REQUEST['target']); $ext = 'ics'; $table = 'calendarobjects'; $column = 'calendarid'; - $objects = new \Sabre\VObject\Splitter\ICalendar($src); + $objects = new \Sabre\VObject\Splitter\ICalendar(@file_get_contents($src)); $profile = \Sabre\VObject\Node::PROFILE_CALDAV; $backend = new \Sabre\CalDAV\Backend\PDO($pdo); } @@ -766,7 +784,7 @@ class Cdav extends Controller { $ext = 'vcf'; $table = 'cards'; $column = 'addressbookid'; - $objects = new \Sabre\VObject\Splitter\VCard($src); + $objects = new \Sabre\VObject\Splitter\VCard(@file_get_contents($src)); $profile = \Sabre\VObject\Node::PROFILE_CARDDAV; $backend = new \Sabre\CardDAV\Backend\PDO($pdo); } @@ -832,24 +850,17 @@ class Cdav extends Controller { if(!local_channel()) return; - if((argv(1) === 'calendar') && (! Apps::system_app_installed(local_channel(), 'CalDAV'))) { - //Do not display any associated widgets at this point - App::$pdl = ''; - - $o = 'CalDAV App (Not Installed):
'; - $o .= t('CalDAV capable calendar'); - return $o; - } - if((argv(1) === 'addressbook') && (! Apps::system_app_installed(local_channel(), 'CardDAV'))) { //Do not display any associated widgets at this point App::$pdl = ''; - $o = 'CardDAV App (Not Installed):
'; + $o = '' . t('CardDAV App') . ' (' . t('Not Installed') . '):
'; $o .= t('CalDAV capable addressbook'); return $o; } + App::$profile_uid = local_channel(); + $channel = App::get_channel(); $principalUri = 'principals/' . $channel['channel_address']; @@ -867,28 +878,93 @@ class Cdav extends Controller { } if(argv(1) === 'calendar') { - nav_set_selected('CalDAV'); + nav_set_selected('Calendar'); $caldavBackend = new \Sabre\CalDAV\Backend\PDO($pdo); $calendars = $caldavBackend->getCalendarsForUser($principalUri); } //Display calendar(s) here - if(argc() == 2 && argv(1) === 'calendar') { + if(argc() <= 3 && argv(1) === 'calendar') { - head_add_css('/library/fullcalendar/fullcalendar.css'); + head_add_css('/library/fullcalendar/packages/core/main.min.css'); + head_add_css('/library/fullcalendar/packages/daygrid/main.min.css'); + head_add_css('/library/fullcalendar/packages/timegrid/main.min.css'); + head_add_css('/library/fullcalendar/packages/list/main.min.css'); head_add_css('cdav_calendar.css'); - head_add_js('/library/moment/moment.min.js', 1); - head_add_js('/library/fullcalendar/fullcalendar.min.js', 1); - head_add_js('/library/fullcalendar/locale-all.js', 1); + head_add_js('/library/fullcalendar/packages/core/main.min.js'); + head_add_js('/library/fullcalendar/packages/interaction/main.min.js'); + head_add_js('/library/fullcalendar/packages/daygrid/main.min.js'); + head_add_js('/library/fullcalendar/packages/timegrid/main.min.js'); + head_add_js('/library/fullcalendar/packages/list/main.min.js'); + + $sources = ''; + $resource_id = ''; + $resource = null; + + if(argc() == 3) + $resource_id = argv(2); + + if($resource_id) { + $r = q("SELECT event.*, item.author_xchan, item.owner_xchan, item.plink, item.id as item_id FROM event LEFT JOIN item ON event.event_hash = item.resource_id + WHERE event.uid = %d AND event.event_hash = '%s' LIMIT 1", + intval(local_channel()), + dbesc($resource_id) + ); + if($r) { + xchan_query($r); + $r = fetch_post_tags($r,true); + + $r[0]['dtstart'] = (($r[0]['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$r[0]['dtstart'], 'c') : datetime_convert('UTC','UTC',$r[0]['dtstart'],'c')); + $r[0]['dtend'] = (($r[0]['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$r[0]['dtend'], 'c') : datetime_convert('UTC','UTC',$r[0]['dtend'],'c')); + + $r[0]['plink'] = [$r[0]['plink'], t('Link to source')]; + + $resource = $r[0]; + + $catsenabled = feature_enabled(local_channel(),'categories'); + $categories = ''; + if($catsenabled){ + if($r[0]['term']) { + $cats = get_terms_oftype($r[0]['term'], TERM_CATEGORY); + foreach ($cats as $cat) { + if(strlen($categories)) + $categories .= ', '; + $categories .= $cat['term']; + } + } + } + + if($r[0]['dismissed'] == 0) { + q("UPDATE event SET dismissed = 1 WHERE event.uid = %d AND event.event_hash = '%s'", + intval(local_channel()), + dbesc($resource_id) + ); + } + } + } + + if(get_pconfig(local_channel(), 'cdav_calendar', 'calendar')) { + $sources .= '{ + id: \'calendar\', + url: \'/calendar/json/\', + color: \'#3a87ad\' + }, '; + } + + $calendars[] = [ + 'displayname' => $channel['channel_name'], + 'id' => 'calendar' + ]; foreach($calendars as $calendar) { $editable = (($calendar['share-access'] == 2) ? 'false' : 'true'); // false/true must be string since we're passing it to javascript - $color = (($calendar['{http://apple.com/ns/ical/}calendar-color']) ? $calendar['{http://apple.com/ns/ical/}calendar-color'] : '#3a87ad'); + $color = (($calendar['{http://apple.com/ns/ical/}calendar-color']) ? $calendar['{http://apple.com/ns/ical/}calendar-color'] : '#6cad39'); $sharer = (($calendar['share-access'] == 3) ? $calendar['{urn:ietf:params:xml:ns:caldav}calendar-description'] : ''); $switch = get_pconfig(local_channel(), 'cdav_calendar', $calendar['id'][0]); if($switch) { $sources .= '{ + id: ' . $calendar['id'][0] . ', url: \'/cdav/calendar/json/' . $calendar['id'][0] . '/' . $calendar['id'][1] . '\', color: \'' . $color . '\' }, '; @@ -905,19 +981,33 @@ class Cdav extends Controller { $sources = rtrim($sources, ', '); - $first_day = get_pconfig(local_channel(),'system','cal_first_day'); + $first_day = feature_enabled(local_channel(), 'cal_first_day'); $first_day = (($first_day) ? $first_day : 0); $title = ['title', t('Event title')]; - $dtstart = ['dtstart', t('Start date and time'), '', t('Example: YYYY-MM-DD HH:mm')]; - $dtend = ['dtend', t('End date and time'), '', t('Example: YYYY-MM-DD HH:mm')]; + $dtstart = ['dtstart', t('Start date and time')]; + $dtend = ['dtend', t('End date and time')]; $description = ['description', t('Description')]; $location = ['location', t('Location')]; + $catsenabled = feature_enabled(local_channel(), 'categories'); + + require_once('include/acl_selectors.php'); + + $accesslist = new \Zotlabs\Access\AccessControl($channel); + $perm_defaults = $accesslist->get(); + + //$acl = (($orig_event['event_xchan']) ? '' : populate_acl(((x($orig_event)) ? $orig_event : $perm_defaults), false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'))); + $acl = populate_acl($perm_defaults, false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream')); + + //$permissions = ((x($orig_event)) ? $orig_event : $perm_defaults); + $permissions = $perm_defaults; + $o .= replace_macros(get_markup_template('cdav_calendar.tpl'), [ '$sources' => $sources, '$color' => $color, '$lang' => App::$language, + '$timezone' => App::$timezone, '$first_day' => $first_day, '$prev' => t('Previous'), '$next' => t('Next'), @@ -929,6 +1019,7 @@ class Cdav extends Controller { '$list_week' => t('List week'), '$list_day' => t('List day'), '$title' => $title, + '$calendars' => $calendars, '$writable_calendars' => $writable_calendars, '$dtstart' => $dtstart, '$dtend' => $dtend, @@ -936,11 +1027,27 @@ class Cdav extends Controller { '$location' => $location, '$more' => t('More'), '$less' => t('Less'), + '$update' => t('Update'), '$calendar_select_label' => t('Select calendar'), + '$calendar_optiopns_label' => [t('Channel Calendars'), t('CalDAV Calendars')], '$delete' => t('Delete'), '$delete_all' => t('Delete all'), '$cancel' => t('Cancel'), - '$recurrence_warning' => t('Sorry! Editing of recurrent events is not yet implemented.') + '$create' => t('Create'), + '$recurrence_warning' => t('Sorry! Editing of recurrent events is not yet implemented.'), + + '$channel_hash' => $channel['channel_hash'], + '$acl' => $acl, + '$lockstate' => (($accesslist->is_private()) ? 'lock' : 'unlock'), + '$allow_cid' => acl2json($permissions['allow_cid']), + '$allow_gid' => acl2json($permissions['allow_gid']), + '$deny_cid' => acl2json($permissions['deny_cid']), + '$deny_gid' => acl2json($permissions['deny_gid']), + '$catsenabled' => $catsenabled, + '$categories_label' => t('Categories'), + + '$resource' => json_encode($resource), + '$categories' => $categories ]); return $o; @@ -950,10 +1057,12 @@ class Cdav extends Controller { //Provide json data for calendar if(argc() == 5 && argv(1) === 'calendar' && argv(2) === 'json' && intval(argv(3)) && intval(argv(4))) { + $events = []; + $id = [argv(3), argv(4)]; if(! cdav_perms($id[0],$calendars)) - killme(); + json_return_and_die($events); if (x($_GET,'start')) $start = new \DateTime($_GET['start']); @@ -967,16 +1076,19 @@ class Cdav extends Controller { $filters['comp-filters'][0]['time-range']['end'] = $end; $uris = $caldavBackend->calendarQuery($id, $filters); + if($uris) { - $objects = $caldavBackend->getMultipleCalendarObjects($id, $uris); - foreach($objects as $object) { $vcalendar = \Sabre\VObject\Reader::read($object['calendardata']); - if(isset($vcalendar->VEVENT->RRULE)) + if(isset($vcalendar->VEVENT->RRULE)) { + // expanding recurrent events seems to loose timezone info + // save it here so we can add it later + $recurrent_timezone = (string)$vcalendar->VEVENT->DTSTART['TZID']; $vcalendar = $vcalendar->expand($start, $end); + } foreach($vcalendar->VEVENT as $vevent) { $title = (string)$vevent->SUMMARY; @@ -984,14 +1096,15 @@ class Cdav extends Controller { $dtend = (string)$vevent->DTEND; $description = (string)$vevent->DESCRIPTION; $location = (string)$vevent->LOCATION; - + $timezone = (string)$vevent->DTSTART['TZID']; $rw = ((cdav_perms($id[0],$calendars,true)) ? true : false); + $editable = $rw ? true : false; $recurrent = ((isset($vevent->{'RECURRENCE-ID'})) ? true : false); - $editable = $rw ? true : false; - - if($recurrent) + if($recurrent) { $editable = false; + $timezone = $recurrent_timezone; + } $allDay = false; @@ -1005,8 +1118,8 @@ class Cdav extends Controller { 'calendar_id' => $id, 'uri' => $object['uri'], 'title' => $title, - 'start' => $dtstart, - 'end' => $dtend, + 'start' => datetime_convert($timezone, $timezone, $dtstart, 'c'), + 'end' => (($dtend) ? datetime_convert($timezone, $timezone, $dtend, 'c') : ''), 'description' => $description, 'location' => $location, 'allDay' => $allDay, @@ -1016,15 +1129,12 @@ class Cdav extends Controller { ]; } } - json_return_and_die($events); - } - else { - killme(); } + json_return_and_die($events); } //enable/disable calendars - if(argc() == 5 && argv(1) === 'calendar' && argv(2) === 'switch' && intval(argv(3)) && (argv(4) == 1 || argv(4) == 0)) { + if(argc() == 5 && argv(1) === 'calendar' && argv(2) === 'switch' && argv(3) && (argv(4) == 1 || argv(4) == 0)) { $id = argv(3); if(! cdav_perms($id,$calendars)) @@ -1283,12 +1393,13 @@ class Cdav extends Controller { $caldavBackend = new \Sabre\CalDAV\Backend\PDO($pdo); $properties = [ '{DAV:}displayname' => t('Default Calendar'), - '{http://apple.com/ns/ical/}calendar-color' => '#3a87ad', + '{http://apple.com/ns/ical/}calendar-color' => '#6cad39', '{urn:ietf:params:xml:ns:caldav}calendar-description' => $channel['channel_name'] ]; $id = $caldavBackend->createCalendar($uri, 'default', $properties); set_pconfig(local_channel(), 'cdav_calendar' , $id[0], 1); + set_pconfig(local_channel(), 'cdav_calendar' , 'calendar', 1); //create default addressbook $carddavBackend = new \Sabre\CardDAV\Backend\PDO($pdo); diff --git a/Zotlabs/Photo/PhotoDriver.php b/Zotlabs/Photo/PhotoDriver.php index c1993ac20..91d228155 100644 --- a/Zotlabs/Photo/PhotoDriver.php +++ b/Zotlabs/Photo/PhotoDriver.php @@ -508,7 +508,7 @@ abstract class PhotoDriver { $arr['imgscale'] = $scale; - if(boolval(get_config('system','filesystem_storage_thumbnails', 0)) && $scale > 0) { + if(boolval(get_config('system','filesystem_storage_thumbnails', 1)) && $scale > 0) { $channel = \App::get_channel(); $arr['os_storage'] = 1; $arr['os_syspath'] = 'store/' . $channel['channel_address'] . '/' . $arr['os_path'] . '-' . $scale; diff --git a/Zotlabs/Widget/Cdav.php b/Zotlabs/Widget/Cdav.php index 589f915c5..2a2e0855f 100644 --- a/Zotlabs/Widget/Cdav.php +++ b/Zotlabs/Widget/Cdav.php @@ -22,7 +22,7 @@ class Cdav { $o = ''; - if(argc() == 2 && argv(1) === 'calendar') { + if(argc() <= 3 && argv(1) === 'calendar') { $caldavBackend = new \Sabre\CalDAV\Backend\PDO($pdo); @@ -57,7 +57,7 @@ class Cdav { $switch = get_pconfig(local_channel(), 'cdav_calendar', $sabrecal['id'][0]); - $color = (($sabrecal['{http://apple.com/ns/ical/}calendar-color']) ? $sabrecal['{http://apple.com/ns/ical/}calendar-color'] : '#3a87ad'); + $color = (($sabrecal['{http://apple.com/ns/ical/}calendar-color']) ? $sabrecal['{http://apple.com/ns/ical/}calendar-color'] : '#6cad39'); $editable = (($sabrecal['share-access'] == 2) ? 'false' : 'true'); // false/true must be string since we're passing it to javascript @@ -113,10 +113,22 @@ class Cdav { } } + $calendars[] = [ + 'ownernick' => $channel['channel_address'], + 'displayname' => $channel['channel_name'], + 'calendarid' => 'calendar', + 'json_source' => '/calendar/json', + 'color' => '#3a87ad', + 'editable' => true, + 'switch' => get_pconfig(local_channel(), 'cdav_calendar', 'calendar') + ]; + $o .= replace_macros(get_markup_template('cdav_widget_calendar.tpl'), [ - '$my_calendars_label' => t('My Calendars'), + '$calendars_label' => t('Channel Calendar'), + '$calendars' => $calendars, + '$my_calendars_label' => t('CalDAV Calendars'), '$my_calendars' => $my_calendars, - '$shared_calendars_label' => t('Shared Calendars'), + '$shared_calendars_label' => t('Shared CalDAV Calendars'), '$shared_calendars' => $shared_calendars, '$sharee_options' => $sharee_options, '$access_options' => $access_options, @@ -124,10 +136,11 @@ class Cdav { '$share' => t('Share'), '$edit_label' => t('Calendar name and color'), '$edit' => t('Edit'), - '$create_label' => t('Create new calendar'), + '$create_label' => t('Create new CalDAV calendar'), '$create' => t('Create'), '$create_placeholder' => t('Calendar Name'), '$tools_label' => t('Calendar Tools'), + '$tools_options_label' => [t('Channel Calendars'), t('CalDAV Calendars')], '$import_label' => t('Import calendar'), '$import_placeholder' => t('Select a calendar to import to'), '$upload' => t('Upload'), diff --git a/app/caldav.apx b/app/calendar.apd similarity index 59% rename from app/caldav.apx rename to app/calendar.apd index a5839fd6e..7d859d65d 100644 --- a/app/caldav.apx +++ b/app/calendar.apd @@ -1,6 +1,6 @@ version: 1 url: $baseurl/cdav/calendar requires: local_channel -name: CalDAV +name: Calendar photo: icon:calendar -categories: Productivity, Personal +categories: Productivity, nav_featured_app diff --git a/app/events.apd b/app/events.apx similarity index 100% rename from app/events.apd rename to app/events.apx diff --git a/include/event.php b/include/event.php index 0514b7001..0043da760 100644 --- a/include/event.php +++ b/include/event.php @@ -1188,13 +1188,16 @@ function event_store_item($arr, $event) { $item_arr['item_origin'] = $item_origin; $item_arr['item_thread_top'] = $item_thread_top; - $attach = array(array( - 'href' => z_root() . '/events/ical/' . urlencode($event['event_hash']), - 'length' => 0, - 'type' => 'text/calendar', - 'title' => t('event') . '-' . $event['event_hash'], - 'revision' => '' - )); + $attach = [ + [ + 'href' => z_root() . '/calendar/ical/' . urlencode($event['event_hash']), + 'length' => 0, + 'type' => 'text/calendar', + 'title' => t('event') . '-' . $event['event_hash'], + 'revision' => '' + ] + ]; + $item_arr['attach'] = $attach; @@ -1211,7 +1214,7 @@ function event_store_item($arr, $event) { // otherwise we'll fallback to /display/$message_id if($wall) - $item_arr['plink'] = z_root() . '/channel/' . $z['channel_address'] . '/?f=&mid=' . urlencode($item_arr['mid']); + $item_arr['plink'] = z_root() . '/channel/' . $z['channel_address'] . '/?f=&mid=' . gen_link_id($item_arr['mid']); else $item_arr['plink'] = z_root() . '/display/' . gen_link_id($item_arr['mid']); @@ -1311,9 +1314,14 @@ function cdav_principal($uri) { } function cdav_perms($needle, $haystack, $check_rw = false) { + + if ($needle === 'calendar') { + return true; + } + foreach ($haystack as $item) { - if($check_rw) { - if(is_array($item['id'])) { + if ($check_rw) { + if (is_array($item['id'])) { if ($item['id'][0] == $needle && $item['share-access'] != 2) { return $item['{DAV:}displayname']; } @@ -1325,7 +1333,7 @@ function cdav_perms($needle, $haystack, $check_rw = false) { } } else { - if(is_array($item['id'])) { + if (is_array($item['id'])) { if ($item['id'][0] == $needle) { return $item['{DAV:}displayname']; } diff --git a/include/photos.php b/include/photos.php index f6aebba83..d926b61f1 100644 --- a/include/photos.php +++ b/include/photos.php @@ -22,11 +22,11 @@ require_once('include/text.php'); function photo_upload($channel, $observer, $args) { - $ret = array('success' => false); + $ret = [ 'success' => false ]; $channel_id = $channel['channel_id']; $account_id = $channel['channel_account_id']; - if(! perm_is_allowed($channel_id, $observer['xchan_hash'], 'write_storage')) { + if (! perm_is_allowed($channel_id, $observer['xchan_hash'], 'write_storage')) { $ret['message'] = t('Permission denied.'); return $ret; } @@ -37,14 +37,15 @@ function photo_upload($channel, $observer, $args) { $album = $args['album']; - if(intval($args['visible']) || $args['visible'] === 'true') + if (intval($args['visible']) || $args['visible'] === 'true') { $visible = 1; - else + } + else { $visible = 0; + } + + $deliver = ((array_key_exists('deliver', $args)) ? intval($args['deliver']) : 1 ); - $deliver = true; - if(array_key_exists('deliver',$args)) - $deliver = intval($args['deliver']); // Set to default channel permissions. If the parent directory (album) has permissions set, // use those instead. If we have specific permissions supplied, they take precedence over @@ -52,11 +53,13 @@ function photo_upload($channel, $observer, $args) { // ...messy... needs re-factoring once the photos/files integration stabilises $acl = new AccessControl($channel); - if(array_key_exists('directory',$args) && $args['directory']) + if (array_key_exists('directory',$args) && $args['directory']) { $acl->set($args['directory']); - if(array_key_exists('allow_cid',$args)) + } + if (array_key_exists('allow_cid',$args)) { $acl->set($args); - if( (array_key_exists('group_allow',$args)) + } + if ((array_key_exists('group_allow',$args)) || (array_key_exists('contact_allow',$args)) || (array_key_exists('group_deny',$args)) || (array_key_exists('contact_deny',$args))) { @@ -68,7 +71,7 @@ function photo_upload($channel, $observer, $args) { $width = $height = 0; if($args['getimagesize']) { - $width = $args['getimagesize'][0]; + $width = $args['getimagesize'][0]; $height = $args['getimagesize'][1]; } diff --git a/library/fullcalendar/CHANGELOG.txt b/library/fullcalendar.old/CHANGELOG.txt similarity index 100% rename from library/fullcalendar/CHANGELOG.txt rename to library/fullcalendar.old/CHANGELOG.txt diff --git a/library/fullcalendar/CONTRIBUTING.txt b/library/fullcalendar.old/CONTRIBUTING.txt similarity index 100% rename from library/fullcalendar/CONTRIBUTING.txt rename to library/fullcalendar.old/CONTRIBUTING.txt diff --git a/library/fullcalendar.old/LICENSE.txt b/library/fullcalendar.old/LICENSE.txt new file mode 100644 index 000000000..eafaf97ec --- /dev/null +++ b/library/fullcalendar.old/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2015 Adam Shaw + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/library/fullcalendar/fullcalendar.css b/library/fullcalendar.old/fullcalendar.css similarity index 100% rename from library/fullcalendar/fullcalendar.css rename to library/fullcalendar.old/fullcalendar.css diff --git a/library/fullcalendar/fullcalendar.js b/library/fullcalendar.old/fullcalendar.js similarity index 100% rename from library/fullcalendar/fullcalendar.js rename to library/fullcalendar.old/fullcalendar.js diff --git a/library/fullcalendar/fullcalendar.min.css b/library/fullcalendar.old/fullcalendar.min.css similarity index 100% rename from library/fullcalendar/fullcalendar.min.css rename to library/fullcalendar.old/fullcalendar.min.css diff --git a/library/fullcalendar/fullcalendar.min.js b/library/fullcalendar.old/fullcalendar.min.js similarity index 100% rename from library/fullcalendar/fullcalendar.min.js rename to library/fullcalendar.old/fullcalendar.min.js diff --git a/library/fullcalendar/fullcalendar.print.css b/library/fullcalendar.old/fullcalendar.print.css similarity index 100% rename from library/fullcalendar/fullcalendar.print.css rename to library/fullcalendar.old/fullcalendar.print.css diff --git a/library/fullcalendar/fullcalendar.print.min.css b/library/fullcalendar.old/fullcalendar.print.min.css similarity index 100% rename from library/fullcalendar/fullcalendar.print.min.css rename to library/fullcalendar.old/fullcalendar.print.min.css diff --git a/library/fullcalendar/gcal.js b/library/fullcalendar.old/gcal.js similarity index 100% rename from library/fullcalendar/gcal.js rename to library/fullcalendar.old/gcal.js diff --git a/library/fullcalendar/gcal.min.js b/library/fullcalendar.old/gcal.min.js similarity index 100% rename from library/fullcalendar/gcal.min.js rename to library/fullcalendar.old/gcal.min.js diff --git a/library/fullcalendar/locale-all.js b/library/fullcalendar.old/locale-all.js similarity index 100% rename from library/fullcalendar/locale-all.js rename to library/fullcalendar.old/locale-all.js diff --git a/library/fullcalendar/CHANGELOG.md b/library/fullcalendar/CHANGELOG.md new file mode 100644 index 000000000..2687cd8e7 --- /dev/null +++ b/library/fullcalendar/CHANGELOG.md @@ -0,0 +1,1377 @@ + +v4.0.2 (2019-04-03) +------------------- + +Bugfixes: +- eventAllow and constraints not respected when dragging event between calendars +- viewSkeletonRender now in typedefs (#4589) +- invalid draggedEvent properties in eventAllow for external dnd (#4575) +- forceEventDuration not working with external dnd (#4597) +- rrule displaying time when allDay is true (#4576) +- rrule events not displaying at interval start (#4596) +- prev button not initially working when starting on 31st of a month (#4595) +- clicking X in popover generating a dayClick (#4584) +- locale file used as single script tag not affecting calendar locale (#4581) +- header "today" button not translated for pt and pt-br (#4591) +- fa locale typo (#4582) + + +v4.0.1 (2019-03-18) +------------------- + +Read about all the changes in v4: +https://fullcalendar.io/docs/upgrading-from-v3 + +Obscure breaking changes from v3->v4 not mentioned elsewhere: +- `touchMouseIgnoreWait` moved to `(packageRoot).config.touchMouseIgnoreWait` +- `dataAttrPrefix` moved to `(packageRoot).config.dataAttrPrefix` + +Advancements since latest prerelease: +- New styling for buttons and icons in header. New styling for events. +- Bugfixes: #4539, #4503, #4534, #4505, #4477, #4467, #4454, #4458, #4483, + #4517, #4506, #4435, #4498, #4497, #4446, #4432, #4530 + +NOTE: version "4.0.0" was skipped because of an NPM publishing error + + +v3.10.0 (2019-01-10) +-------------------- + +POTENTIALLY BREAKING CHANGE: +The jquery and moment packages have been moved to peerDependencies. If you are using +NPM to install fullcalendar, you'll need to explicitly add jquery and moment as +dependencies of your project. NPM will not install them automatically. (#4136, #4233) + +New Features: +- events from a Google Calendar event source will receive extended props (#4123) +- export more classes and util functions (#4124) +- new locales: zh-hk (#4266), be (#4274) + +Bugfixes: +- not accepting dayClicks/selects because of overflow-x:hidden on html/body (#3615) +- event end time not displayed when duration is one slot, in agenda view (#3049) +- switching views before event fetch resolves, JS error (#3689) +- single-day allDay event not showing when time is specified (#3854) +- prev button doesn't work when previous days are hidden by hiddenDays and dayCount + is greater than dateIncrement (#4202) +- calendar locale not used in all moments objects (#4174) +- background event background color does not completely fill cells in Chrome (#4145) +- provide a delta for eventResize when resizing from start (#4135) +- IE11 memory leak from not removing handler correctly (#4311) +- make touchstart handlers passive (#4087) +- fixed typescript definition for: eventAllow (#4243), selectAllow (#4319) +- fixed locales: de (#4197, #4371), hu (#4203), tr (#4312), ja (#4329) + + +v3.9.0 (2018-03-04) +------------------- + +- Bootstrap 4 support (#4032, #4065, thx @GeekJosh) +- add OptionsInput to the fullcalendar.d.ts exports (#4040, #4006) +- columnHeaderFormat/columnHeaderHtml/columnHeaderText in .d.ts file (#4061, #4085) +- list-view auto-height not working (#3346, #4071, thx @WhatTheBuild) +- bump momentjs minimum version to 2.20.1, for locale fixes (#4014) +- swedish week header translation fix (#4082) +- dutch year translation (#4069) + + +v3.8.2 (2018-01-30) +------------------- + +Bugfixes: +- Fix TypeScript definitions file with strictNullChecks (#4035) + + +v3.8.1 (2018-01-28) +------------------- + +Bugfixes: +- TypeScript definition file not compatible with noImplicitAny (#4017) +- ES6 classes are not supported for grid class (#3437) +- day numbers in month view should be localized (#3339) +- select helper is resizable, causes js error (#3764) +- selecting over existing select helper causes js error (#4031) +- eventOrder doesn't work on custom fields (#3950) +- aria label on button icons (#4023) +- dynamic option changes to select/overlap/allow doesn't cause rerender + +Locales: +- added Georgian (#3994) +- added Bosnian (#4029) + + +v3.8.0 (2017-12-18) +------------------- + +- new settings for month/agenda/basic views (#3078): + - `columnHeaderFormat` (renamed from `columnFormat`) + - `columnHeaderText` + - `columnHeaderHtml` +- TypeScript definition file (fullcalendar.d.ts) included in npm package (#3889) +- codebase using SASS, though not taking advantage of it yet (#3463) +- codebase fully ported to TypeScript / Webpack +- Afrikaans locale fix (#3862) + + +v3.7.0 (2017-11-13) +------------------- + +Bugfixes: +- `render` method does not re-adjust calendar dimension (#3893) +- when custom view navigates completely into hidden weekends, JS error ([scheduler-375]) + +Other: +- in themes.html demo, fixed broken Bootswatch themes (#3917) +- moved JavaScript codebase over to TypeScript + (same external API; embedded typedefs coming soon) + +[scheduler-375]: https://github.com/fullcalendar/fullcalendar-scheduler/issues/375 + + +v3.6.2 (2017-10-23) +------------------- + +Bugfixes: +- Google Calendar event sources not calling `loading` callback (#3884) +- `eventDataTransform` w/ eventConstraint shouldn't be called during event resizing (#3859) +- `navLinks` would go to the previously navigated date (#3869) +- `nowIndicator` arrow would repeatedly render (#3872) +- fc-content-skeleton DOM element would repeatedly render on navigation in agenda view + + +v3.6.1 (2017-10-11) +------------------- + +Bugfixes: +- JSON feed event sources always requesting current page (#3865) +- multi-day events appearing multiple times in more+ popover (#3856) + + +v3.6.0 (2017-10-10) +------------------- + +Features: +- `agendaEventMinHeight` for guaranteeing height (#961, #3788) thx @Stafie +- `columnHeader` can be set to `false` to hide headings (#3438, #3787) thx @caseyjhol +- export all View classes (#2851, #3831) +- `updateEvent`, update complex attributes (#2864) +- Albanian locale (#3847) thx @alensaqe + +Bugfixes: +- objects used as non-standard Event properties ignored by `updateEvent` (#3839) +- listDay error if event goes over period (#3843) +- `validDays` with `hiddenDays`, js error when no days active (#3846) +- json feed Event Source object no longer has `url` property (#3845) +- `updateEvent`, allDay to timed, when no end, wrong end date (#3144) +- `removeEvents` by `_id` stopped working (#3828) +- correct `this` context in FuncEventSource (#3848) thx @declspec +- js event not received in unselect callback when selecting another cell (#3832) + +Incompatibilities: +- The `viewRender` callback might now be fired AFTER events have been rendered + to the DOM. However, the eventRender/eventAfterRender/eventAfterAllRender callbacks + will always be fired after `viewRender`, just as before. +- The internal `Grid` class (accessed via `$.fullCalendar.Grid`) has been removed. + For monkeypatching, use DayGrid/TimeGrid directly. + + +v3.5.1 (2017-09-06) +------------------- + +- fixed loading trigger not firing (#3810) +- fixed overaggressively fetching events, on option changes (#3820) +- fixed event object `date` property being discarded (tho still parsed) (#3819) +- fixed event object `_id` property being discarded (#3811) + + +v3.5.0 (2017-08-30) +------------------- + +Features: +- Bootstrap 3 theme support (#2334, #3566) + - via `themeSystem: 'bootstrap3'` (the `theme` option is deprecated) + - new `bootstrapGlyphicons` option + - jQuery UI "Cupertino" theme no longer included in zip archive + - improved theme switcher on demo page (#1436) + (big thanks to @joankaradimov) +- 25% event rendering performance improvement across the board (#2524) +- console message for unknown method/calendar (#3253) +- Serbian cyrilic/latin (#3656) +- available via Packagist (#2999, #3617) + +Bugfixes: +- slot time label invisible when minTime starts out of alignment (#2786) +- bug with inverse-background event rendering when out of range (#3652) +- wrongly disabled prev/next when current date outside of validRange (#3686, #3651) +- updateEvent, error when changing allDay from false to true (#3518) +- updateEvent doesn't support ID changes (#2928) +- Promise then method doesn't forward result (#3744) +- Korean typo (#3693) +- fixed switching from any view to listview, eventAfterRender isn't called (#3751) + +Incompatibilities: +- Event Objects obtained from clientEvents or various callbacks are no longer + references to internally used objects. Rather, they are static object copies. +- `clientEvents` method no longer returns events in same order as received. + Do not depend on order. + + +v3.4.0 (2017-04-27) +------------------- + +- composer.json for Composer (PHP package manager) (#3617) +- fix toISOString for locales with non-trivial postformatting (#3619) +- fix for nested inverse-background events (#3609) +- Estonian locale (#3600) +- fixed Latvian localization (#3525) +- internal refactor of async systems + + +v3.3.1 (2017-04-01) +------------------- + +Bugfixes: +- stale calendar title when navigate away from then back to the a view (#3604) +- js error when gotoDate immediately after calendar initialization (#3598) +- agenda view scrollbars causes misalignment in jquery 3.2.1 (#3612) +- navigation bug when trying to navigate to a day of another week (#3610) +- dateIncrement not working when duration and dateIncrement have different units + + +v3.3.0 (2017-03-23) +------------------- + +Features: +- `visibleRange` - complete control over view's date range (#2847, #3105, #3245) +- `validRange` - restrict date range (#429) +- `changeView` - pass in a date or visibleRange as second param (#3366) +- `dateIncrement` - customize prev/next jump (#2710) +- `dateAlignment` - custom view alignment, like start-of-week (#3113) +- `dayCount` - force a fixed number-of-days, even with hiddenDays (#2753) +- `showNonCurrentDates` - option to hide day cells for prev/next months (#437) +- can define a defaultView with a duration/visibleRange/dayCount with needing + to create a custom view in the `views` object. Known as a "Generic View". + +Behavior Changes: +- when custom view is specified with duration `{days:7}`, + it will no longer align with the start of the week. (#2847) +- when `gotoDate` is called on a custom view with a duration of multiple days, + the view will always shift to begin with the given date. (#3515) + +Bugfixes: +- event rendering when excessive `minTime`/`maxTime` (#2530) +- event dragging not shown when excessive `minTime`/`maxTime` (#3055) +- excessive `minTime`/`maxTime` not reflected in event fetching (#3514) + - when minTime is negative, or maxTime beyond 24 hours, when event data is requested + via a function or a feed, the given data params will have time parts. +- external event dragging via touchpunch broken (#3544) +- can't make an immediate new selection after existing selection, with mouse. + introduced in v3.2.0 (#3558) + + +v3.2.0 (2017-02-14) +------------------- + +Features: +- `selectMinDistance`, threshold before a mouse selection begins (#2428) + +Bugfixes: +- iOS 10, unwanted scrolling while dragging events/selection (#3403) +- dayClick triggered when swiping on touch devices (#3332) +- dayClick not functioning on Firefix mobile (#3450) +- title computed incorrectly for views with no weekends (#2884) +- unwanted scrollbars in month-view when non-integer width (#3453, #3444) +- incorrect date formatting for locales with non-standlone month/day names (#3478) +- date formatting, incorrect omission of trailing period for certain locales (#2504, #3486) +- formatRange should collapse same week numbers (#3467) +- Taiwanese locale updated (#3426) +- Finnish noEventsMessage updated (#3476) +- Croatian (hr) buttonText is blank (#3270) +- JSON feed PHP example, date range math bug (#3485) + + +v3.1.0 (2016-12-05) +------------------- + +- experimental support for implicitly batched ("debounced") event rendering (#2938) + - `eventRenderWait` (off by default) +- new `footer` option, similar to header toolbar (#654, #3299) +- event rendering batch methods (#3351): + - `renderEvents` + - `updateEvents` +- more granular touch settings (#3377): + - `eventLongPressDelay` + - `selectLongPressDelay` +- eventDestroy not called when removing the popover (#3416, #3419) +- print stylesheet and gcal extension now offered as minified (#3415) +- fc-today in agenda header cells (#3361, #3365) +- height-related options in tandem with other options (#3327, #3384) +- Kazakh locale (#3394) +- Afrikaans locale (#3390) +- internal refactor related to timing of rendering and firing handlers. + calls to rerender the current date-range and events from within handlers + might not execute immediately. instead, will execute after handler finishes. + + +v3.0.1 (2016-09-26) +------------------- + +Bugfixes: +- list view rendering event times incorrectly (#3334) +- list view rendering events/days out of order (#3347) +- events with no title rendering as "undefined" +- add .fc scope to table print styles (#3343) +- "display no events" text fix for German (#3354) + + +v3.0.0 (2016-09-04) +------------------- + +Features: +- List View (#560) + - new views: `listDay`, `listWeek`, `listMonth`, `listYear`, and simply `list` + - `listDayFormat` + - `listDayAltFormat` + - `noEventsMessage` +- Clickable day/week numbers for easier navigation (#424) + - `navLinks` + - `navLinkDayClick` + - `navLinkWeekClick` +- Programmatically allow/disallow user interactions: + - `eventAllow` (#2740) + - `selectAllow` (#2511) +- Option to display week numbers in cells (#3024) + - `weekNumbersWithinDays` (set to `true` to activate) +- When week calc is ISO, default first day-of-week to Monday (#3255) +- Macedonian locale (#2739) +- Malay locale + +Breaking Changes: +- IE8 support dropped +- jQuery: minimum support raised to v2.0.0 +- MomentJS: minimum support raised to v2.9.0 +- `lang` option renamed to `locale` +- dist files have been renamed to be more consistent with MomentJS: + - `lang/` -> `locale/` + - `lang-all.js` -> `locale-all.js` +- behavior of moment methods no longer affected by ambiguousness: + - `isSame` + - `isBefore` + - `isAfter` +- View-Option-Hashes no longer supported (deprecated in 2.2.4) +- removed `weekMode` setting +- removed `axisFormat` setting +- DOM structure of month/basic-view day cell numbers changed + +Bugfixes: +- `$.fullCalendar.version` incorrect (#3292) + +Build System: +- using gulp instead of grunt (faster) +- using npm internally for dependencies instead of bower +- changed repo directory structure + + +v2.9.1 (2016-07-31) +------------------- + +- multiple definitions for businessHours (#2686) +- businessHours for single day doesn't display weekends (#2944) +- height/contentHeight can accept a function or 'parent' for dynamic value (#3271) +- fix +more popover clipped by overflow (#3232) +- fix +more popover positioned incorrectly when scrolled (#3137) +- Norwegian Nynorsk translation (#3246) +- fix isAnimating JS error (#3285) + + +v2.9.0 (2016-07-10) +------------------- + +- Setters for (almost) all options (#564). + See [docs](http://fullcalendar.io/docs/utilities/dynamic_options/) for more info. +- Travis CI improvements (#3266) + + +v2.8.0 (2016-06-19) +------------------- + +- getEventSources method (#3103, #2433) +- getEventSourceById method (#3223) +- refetchEventSources method (#3103, #1328, #254) +- removeEventSources method (#3165, #948) +- prevent flicker when refetchEvents is called (#3123, #2558) +- fix for removing event sources that share same URL (#3209) +- jQuery 3 support (#3197, #3124) +- Travis CI integration (#3218) +- EditorConfig for promoting consistent code style (#141) +- use en dash when formatting ranges (#3077) +- height:auto always shows scrollbars in month view on FF (#3202) +- new languages: + - Basque (#2992) + - Galician (#194) + - Luxembourgish (#2979) + + +v2.7.3 (2016-06-02) +------------------- + +internal enhancements that plugins can benefit from: +- EventEmitter not correctly working with stopListeningTo +- normalizeEvent hook for manipulating event data + + +v2.7.2 (2016-05-20) +------------------- + +- fixed desktops/laptops with touch support not accepting mouse events for + dayClick/dragging/resizing (#3154, #3149) +- fixed dayClick incorrectly triggered on touch scroll (#3152) +- fixed touch event dragging wrongfully beginning upon scrolling document (#3160) +- fixed minified JS still contained comments +- UI change: mouse users must hover over an event to reveal its resizers + + +v2.7.1 (2016-05-01) +------------------- + +- dayClick not firing on touch devices (#3138) +- icons for prev/next not working in MS Edge (#2852) +- fix bad languages troubles with firewalls (#3133, #3132) +- update all dev dependencies (#3145, #3010, #2901, #251) +- git-ignore npm debug logs (#3011) +- misc automated test updates (#3139, #3147) +- Google Calendar htmlLink not always defined (#2844) + + +v2.7.0 (2016-04-23) +------------------- + +touch device support (#994): + - smoother scrolling + - interactions initiated via "long press": + - event drag-n-drop + - event resize + - time-range selecting + - `longPressDelay` + + +v2.6.1 (2016-02-17) +------------------- + +- make `nowIndicator` positioning refresh on window resize + + +v2.6.0 (2016-01-07) +------------------- + +- current time indicator (#414) +- bundled with most recent version of moment (2.11.0) +- UMD wrapper around lang files now handles commonjs (#2918) +- fix bug where external event dragging would not respect eventOverlap +- fix bug where external event dropping would not render the whole-day highlight + + +v2.5.0 (2015-11-30) +------------------- + +- internal timezone refactor. fixes #2396, #2900, #2945, #2711 +- internal "grid" system refactor. improved API for plugins. + + +v2.4.0 (2015-08-16) +------------------- + +- add new buttons to the header via `customButtons` ([225]) +- control stacking order of events via `eventOrder` ([364]) +- control frequency of slot text via `slotLabelInterval` ([946]) +- `displayEventTime` ([1904]) +- `on` and `off` methods ([1910]) +- renamed `axisFormat` to `slotLabelFormat` + +[225]: https://code.google.com/p/fullcalendar/issues/detail?id=225 +[364]: https://code.google.com/p/fullcalendar/issues/detail?id=364 +[946]: https://code.google.com/p/fullcalendar/issues/detail?id=946 +[1904]: https://code.google.com/p/fullcalendar/issues/detail?id=1904 +[1910]: https://code.google.com/p/fullcalendar/issues/detail?id=1910 + + +v2.3.2 (2015-06-14) +------------------- + +- minor code adjustment in preparation for plugins + + +v2.3.1 (2015-03-08) +------------------- + +- Fix week view column title for en-gb ([PR220]) +- Publish to NPM ([2447]) +- Detangle bower from npm package ([PR179]) + +[PR220]: https://github.com/arshaw/fullcalendar/pull/220 +[2447]: https://code.google.com/p/fullcalendar/issues/detail?id=2447 +[PR179]: https://github.com/arshaw/fullcalendar/pull/179 + + +v2.3.0 (2015-02-21) +------------------- + +- internal refactoring in preparation for other views +- businessHours now renders on whole-days in addition to timed areas +- events in "more" popover not sorted by time ([2385]) +- avoid using moment's deprecated zone method ([2443]) +- destroying the calendar sometimes causes all window resize handlers to be unbound ([2432]) +- multiple calendars on one page, can't accept external elements after navigating ([2433]) +- accept external events from jqui sortable ([1698]) +- external jqui drop processed before reverting ([1661]) +- IE8 fix: month view renders incorrectly ([2428]) +- IE8 fix: eventLimit:true wouldn't activate "more" link ([2330]) +- IE8 fix: dragging an event with an href +- IE8 fix: invisible element while dragging agenda view events +- IE8 fix: erratic external element dragging + +[2385]: https://code.google.com/p/fullcalendar/issues/detail?id=2385 +[2443]: https://code.google.com/p/fullcalendar/issues/detail?id=2443 +[2432]: https://code.google.com/p/fullcalendar/issues/detail?id=2432 +[2433]: https://code.google.com/p/fullcalendar/issues/detail?id=2433 +[1698]: https://code.google.com/p/fullcalendar/issues/detail?id=1698 +[1661]: https://code.google.com/p/fullcalendar/issues/detail?id=1661 +[2428]: https://code.google.com/p/fullcalendar/issues/detail?id=2428 +[2330]: https://code.google.com/p/fullcalendar/issues/detail?id=2330 + + +v2.2.7 (2015-02-10) +------------------- + +- view.title wasn't defined in viewRender callback ([2407]) +- FullCalendar versions >= 2.2.5 brokenness with Moment versions <= 2.8.3 ([2417]) +- Support Bokmal Norwegian language specifically ([2427]) + +[2407]: https://code.google.com/p/fullcalendar/issues/detail?id=2407 +[2417]: https://code.google.com/p/fullcalendar/issues/detail?id=2417 +[2427]: https://code.google.com/p/fullcalendar/issues/detail?id=2427 + + +v2.2.6 (2015-01-11) +------------------- + +- Compatibility with Moment v2.9. Was breaking GCal plugin ([2408]) +- View object's `title` property mistakenly omitted ([2407]) +- Single-day views with hiddens days could cause prev/next misbehavior ([2406]) +- Don't let the current date ever be a hidden day (solves [2395]) +- Hebrew locale ([2157]) + +[2408]: https://code.google.com/p/fullcalendar/issues/detail?id=2408 +[2407]: https://code.google.com/p/fullcalendar/issues/detail?id=2407 +[2406]: https://code.google.com/p/fullcalendar/issues/detail?id=2406 +[2395]: https://code.google.com/p/fullcalendar/issues/detail?id=2395 +[2157]: https://code.google.com/p/fullcalendar/issues/detail?id=2157 + + +v2.2.5 (2014-12-30) +------------------- + +- `buttonText` specified for custom views via the `views` option + - bugfix: wrong default value, couldn't override default + - feature: default value taken from locale + + +v2.2.4 (2014-12-29) +------------------- + +- Arbitrary durations for basic/agenda views with the `views` option ([692]) +- Specify view-specific options using the `views` option. fixes [2283] +- Deprecate view-option-hashes +- Formalize and expose View API ([1055]) +- updateEvent method, more intuitive behavior. fixes [2194] + +[692]: https://code.google.com/p/fullcalendar/issues/detail?id=692 +[2283]: https://code.google.com/p/fullcalendar/issues/detail?id=2283 +[1055]: https://code.google.com/p/fullcalendar/issues/detail?id=1055 +[2194]: https://code.google.com/p/fullcalendar/issues/detail?id=2194 + + +v2.2.3 (2014-11-26) +------------------- + +- removeEventSource with Google Calendar object source, would not remove ([2368]) +- Events with invalid end dates are still accepted and rendered ([2350], [2237], [2296]) +- Bug when rendering business hours and navigating away from original view ([2365]) +- Links to Google Calendar events will use current timezone ([2122]) +- Google Calendar plugin works with timezone names that have spaces +- Google Calendar plugin accepts person email addresses as calendar IDs +- Internally use numeric sort instead of alphanumeric sort ([2370]) + +[2368]: https://code.google.com/p/fullcalendar/issues/detail?id=2368 +[2350]: https://code.google.com/p/fullcalendar/issues/detail?id=2350 +[2237]: https://code.google.com/p/fullcalendar/issues/detail?id=2237 +[2296]: https://code.google.com/p/fullcalendar/issues/detail?id=2296 +[2365]: https://code.google.com/p/fullcalendar/issues/detail?id=2365 +[2122]: https://code.google.com/p/fullcalendar/issues/detail?id=2122 +[2370]: https://code.google.com/p/fullcalendar/issues/detail?id=2370 + + +v2.2.2 (2014-11-19) +------------------- + +- Fixes to Google Calendar API V3 code + - wouldn't recognize a lone-string Google Calendar ID if periods before the @ symbol + - removeEventSource wouldn't work when given a Google Calendar ID + + +v2.2.1 (2014-11-19) +------------------- + +- Migrate Google Calendar plugin to use V3 of the API ([1526]) + +[1526]: https://code.google.com/p/fullcalendar/issues/detail?id=1526 + + +v2.2.0 (2014-11-14) +------------------- + +- Background events. Event object's `rendering` property ([144], [1286]) +- `businessHours` option ([144]) +- Controlling where events can be dragged/resized and selections can go ([396], [1286], [2253]) + - `eventOverlap`, `selectOverlap`, and similar + - `eventConstraint`, `selectConstraint`, and similar +- Improvements to dragging and dropping external events ([2004]) + - Associating with real event data. used with `eventReceive` + - Associating a `duration` +- Performance boost for moment creation + - Be aware, FullCalendar-specific methods now attached directly to global moment.fn + - Helps with [issue 2259][2259] +- Reintroduced forgotten `dropAccept` option ([2312]) + +[144]: https://code.google.com/p/fullcalendar/issues/detail?id=144 +[396]: https://code.google.com/p/fullcalendar/issues/detail?id=396 +[1286]: https://code.google.com/p/fullcalendar/issues/detail?id=1286 +[2004]: https://code.google.com/p/fullcalendar/issues/detail?id=2004 +[2253]: https://code.google.com/p/fullcalendar/issues/detail?id=2253 +[2259]: https://code.google.com/p/fullcalendar/issues/detail?id=2259 +[2312]: https://code.google.com/p/fullcalendar/issues/detail?id=2312 + + +v2.1.1 (2014-08-29) +------------------- + +- removeEventSource not working with array ([2203]) +- mouseout not triggered after mouseover+updateEvent ([829]) +- agenda event's render with no href, not clickable ([2263]) + +[2203]: https://code.google.com/p/fullcalendar/issues/detail?id=2203 +[829]: https://code.google.com/p/fullcalendar/issues/detail?id=829 +[2263]: https://code.google.com/p/fullcalendar/issues/detail?id=2263 + + +v2.1.0 (2014-08-25) +------------------- + +Large code refactor with better OOP, better code reuse, and more comments. +**No more reliance on jQuery UI** for event dragging, resizing, or anything else. + +Significant changes to HTML/CSS skeleton: +- Leverages tables for liquid rendering of days and events. No costly manual repositioning ([809]) +- **Backwards-incompatibilities**: + - **Many classNames have changed. Custom CSS will likely need to be adjusted.** + - IE7 definitely not supported anymore + - In `eventRender` callback, `element` will not be attached to DOM yet + - Events are styled to be one line by default ([1992]). Can be undone through custom CSS, + but not recommended (might get gaps [like this][111] in certain situations). + +A "more..." link when there are too many events on a day ([304]). Works with month and basic views +as well as the all-day section of the agenda views. New options: +- `eventLimit`. a number or `true` +- `eventLimitClick`. the `"popover`" value will reveal all events in a raised panel (the default) +- `eventLimitText` +- `dayPopoverFormat` + +Changes related to height and scrollbars: +- `aspectRatio`/`height`/`contentHeight` values will be honored *no matter what* + - If too many events causing too much vertical space, scrollbars will be used ([728]). + This is default behavior for month view (**backwards-incompatibility**) + - If too few slots in agenda view, view will stretch to be the correct height ([2196]) +- `'auto'` value for `height`/`contentHeight` options. If content is too tall, the view will + vertically stretch to accomodate and no scrollbars will be used ([521]). +- Tall weeks in month view will borrow height from other weeks ([243]) +- Automatically scroll the view then dragging/resizing an event ([1025], [2078]) +- New `fixedWeekCount` option to determines the number of weeks in month view + - Supersedes `weekMode` (**deprecated**). Instead, use a combination of `fixedWeekCount` and + one of the height options, possibly with an `'auto'` value + +Much nicer, glitch-free rendering of calendar *for printers* ([35]). Things you might not expect: +- Buttons will become hidden +- Agenda views display a flat list of events where the time slots would be + +Other issues resolved along the way: +- Space on right side of agenda events configurable through CSS ([204]) +- Problem with window resize ([259]) +- Events sorting stays consistent across weeks ([510]) +- Agenda's columns misaligned on wide screens ([511]) +- Run `selectHelper` through `eventRender` callbacks ([629]) +- Keyboard access, tabbing ([637]) +- Run resizing events through `eventRender` ([714]) +- Resize an event to a different day in agenda views ([736]) +- Allow selection across days in agenda views ([778]) +- Mouseenter delegated event not working on event elements ([936]) +- Agenda event dragging, snapping to different columns is erratic ([1101]) +- Android browser cuts off Day view at 8 PM with no scroll bar ([1203]) +- Don't fire `eventMouseover`/`eventMouseout` while dragging/resizing ([1297]) +- Customize the resize handle text ("=") ([1326]) +- If agenda event is too short, don't overwrite `.fc-event-time` ([1700]) +- Zooming calendar causes events to misalign ([1996]) +- Event destroy callback on event removal ([2017]) +- Agenda views, when RTL, should have axis on right ([2132]) +- Make header buttons more accessibile ([2151]) +- daySelectionMousedown should interpret OSX ctrl+click as a right mouse click ([2169]) +- Best way to display time text on multi-day events *with times* ([2172]) +- Eliminate table use for header layout ([2186]) +- Event delegation used for event-related callbacks (like `eventClick`). Speedier. + +[35]: https://code.google.com/p/fullcalendar/issues/detail?id=35 +[204]: https://code.google.com/p/fullcalendar/issues/detail?id=204 +[243]: https://code.google.com/p/fullcalendar/issues/detail?id=243 +[259]: https://code.google.com/p/fullcalendar/issues/detail?id=259 +[304]: https://code.google.com/p/fullcalendar/issues/detail?id=304 +[510]: https://code.google.com/p/fullcalendar/issues/detail?id=510 +[511]: https://code.google.com/p/fullcalendar/issues/detail?id=511 +[521]: https://code.google.com/p/fullcalendar/issues/detail?id=521 +[629]: https://code.google.com/p/fullcalendar/issues/detail?id=629 +[637]: https://code.google.com/p/fullcalendar/issues/detail?id=637 +[714]: https://code.google.com/p/fullcalendar/issues/detail?id=714 +[728]: https://code.google.com/p/fullcalendar/issues/detail?id=728 +[736]: https://code.google.com/p/fullcalendar/issues/detail?id=736 +[778]: https://code.google.com/p/fullcalendar/issues/detail?id=778 +[809]: https://code.google.com/p/fullcalendar/issues/detail?id=809 +[936]: https://code.google.com/p/fullcalendar/issues/detail?id=936 +[1025]: https://code.google.com/p/fullcalendar/issues/detail?id=1025 +[1101]: https://code.google.com/p/fullcalendar/issues/detail?id=1101 +[1203]: https://code.google.com/p/fullcalendar/issues/detail?id=1203 +[1297]: https://code.google.com/p/fullcalendar/issues/detail?id=1297 +[1326]: https://code.google.com/p/fullcalendar/issues/detail?id=1326 +[1700]: https://code.google.com/p/fullcalendar/issues/detail?id=1700 +[1992]: https://code.google.com/p/fullcalendar/issues/detail?id=1992 +[1996]: https://code.google.com/p/fullcalendar/issues/detail?id=1996 +[2017]: https://code.google.com/p/fullcalendar/issues/detail?id=2017 +[2078]: https://code.google.com/p/fullcalendar/issues/detail?id=2078 +[2132]: https://code.google.com/p/fullcalendar/issues/detail?id=2132 +[2151]: https://code.google.com/p/fullcalendar/issues/detail?id=2151 +[2169]: https://code.google.com/p/fullcalendar/issues/detail?id=2169 +[2172]: https://code.google.com/p/fullcalendar/issues/detail?id=2172 +[2186]: https://code.google.com/p/fullcalendar/issues/detail?id=2186 +[2196]: https://code.google.com/p/fullcalendar/issues/detail?id=2196 +[111]: https://code.google.com/p/fullcalendar/issues/detail?id=111 + + +v2.0.3 (2014-08-15) +------------------- + +- moment-2.8.1 compatibility ([2221]) +- relative path in bower.json ([PR 117]) +- upgraded jquery-ui and misc dev dependencies + +[2221]: https://code.google.com/p/fullcalendar/issues/detail?id=2221 +[PR 117]: https://github.com/arshaw/fullcalendar/pull/177 + + +v2.0.2 (2014-06-24) +------------------- + +- bug with persisting addEventSource calls ([2191]) +- bug with persisting removeEvents calls with an array source ([2187]) +- bug with removeEvents method when called with 0 removes all events ([2082]) + +[2191]: https://code.google.com/p/fullcalendar/issues/detail?id=2191 +[2187]: https://code.google.com/p/fullcalendar/issues/detail?id=2187 +[2082]: https://code.google.com/p/fullcalendar/issues/detail?id=2082 + + +v2.0.1 (2014-06-15) +------------------- + +- `delta` parameters reintroduced in `eventDrop` and `eventResize` handlers ([2156]) + - **Note**: this changes the argument order for `revertFunc` +- wrongfully triggering a windowResize when resizing an agenda view event ([1116]) +- `this` values in event drag-n-drop/resize handlers consistently the DOM node ([1177]) +- `displayEventEnd` - v2 workaround to force display of an end time ([2090]) +- don't modify passed-in eventSource items ([954]) +- destroy method now removes fc-ltr class ([2033]) +- weeks of last/next month still visible when weekends are hidden ([2095]) +- fixed memory leak when destroying calendar with selectable/droppable ([2137]) +- Icelandic language ([2180]) +- Bahasa Indonesia language ([PR 172]) + +[1116]: https://code.google.com/p/fullcalendar/issues/detail?id=1116 +[1177]: https://code.google.com/p/fullcalendar/issues/detail?id=1177 +[2090]: https://code.google.com/p/fullcalendar/issues/detail?id=2090 +[954]: https://code.google.com/p/fullcalendar/issues/detail?id=954 +[2033]: https://code.google.com/p/fullcalendar/issues/detail?id=2033 +[2095]: https://code.google.com/p/fullcalendar/issues/detail?id=2095 +[2137]: https://code.google.com/p/fullcalendar/issues/detail?id=2137 +[2156]: https://code.google.com/p/fullcalendar/issues/detail?id=2156 +[2180]: https://code.google.com/p/fullcalendar/issues/detail?id=2180 +[PR 172]: https://github.com/arshaw/fullcalendar/pull/172 + + +v2.0.0 (2014-06-01) +------------------- + +Internationalization support, timezone support, and [MomentJS] integration. Extensive changes, many +of which are backwards incompatible. + +[Full list of changes][Upgrading-to-v2] | [Affected Issues][Date-Milestone] + +An automated testing framework has been set up ([Karma] + [Jasmine]) and tests have been written +which cover about half of FullCalendar's functionality. Special thanks to @incre-d, @vidbina, and +@sirrocco for the help. + +In addition, the main development repo has been repurposed to also include the built distributable +JS/CSS for the project and will serve as the new [Bower] endpoint. + +[MomentJS]: http://momentjs.com/ +[Upgrading-to-v2]: http://arshaw.com/fullcalendar/wiki/Upgrading-to-v2/ +[Date-Milestone]: https://code.google.com/p/fullcalendar/issues/list?can=1&q=milestone%3Ddate +[Karma]: http://karma-runner.github.io/ +[Jasmine]: http://jasmine.github.io/ +[Bower]: http://bower.io/ + + +v1.6.4 (2013-09-01) +------------------- + +- better algorithm for positioning timed agenda events ([1115]) +- `slotEventOverlap` option to tweak timed agenda event overlapping ([218]) +- selection bug when slot height is customized ([1035]) +- supply view argument in `loading` callback ([1018]) +- fixed week number not displaying in agenda views ([1951]) +- fixed fullCalendar not initializing with no options ([1356]) +- NPM's `package.json`, no more warnings or errors ([1762]) +- building the bower component should output `bower.json` instead of `component.json` ([PR 125]) +- use bower internally for fetching new versions of jQuery and jQuery UI + +[1115]: https://code.google.com/p/fullcalendar/issues/detail?id=1115 +[218]: https://code.google.com/p/fullcalendar/issues/detail?id=218 +[1035]: https://code.google.com/p/fullcalendar/issues/detail?id=1035 +[1018]: https://code.google.com/p/fullcalendar/issues/detail?id=1018 +[1951]: https://code.google.com/p/fullcalendar/issues/detail?id=1951 +[1356]: https://code.google.com/p/fullcalendar/issues/detail?id=1356 +[1762]: https://code.google.com/p/fullcalendar/issues/detail?id=1762 +[PR 125]: https://github.com/arshaw/fullcalendar/pull/125 + + +v1.6.3 (2013-08-10) +------------------- + +- `viewRender` callback ([PR 15]) +- `viewDestroy` callback ([PR 15]) +- `eventDestroy` callback ([PR 111]) +- `handleWindowResize` option ([PR 54]) +- `eventStartEditable`/`startEditable` options ([PR 49]) +- `eventDurationEditable`/`durationEditable` options ([PR 49]) +- specify function for `$.ajax` `data` parameter for JSON event sources ([PR 59]) +- fixed bug with agenda event dropping in wrong column ([PR 55]) +- easier event element z-index customization ([PR 58]) +- classNames on past/future days ([PR 88]) +- allow `null`/`undefined` event titles ([PR 84]) +- small optimize for agenda event rendering ([PR 56]) +- deprecated: + - `viewDisplay` + - `disableDragging` + - `disableResizing` +- bundled with latest jQuery (1.10.2) and jQuery UI (1.10.3) + +[PR 15]: https://github.com/arshaw/fullcalendar/pull/15 +[PR 111]: https://github.com/arshaw/fullcalendar/pull/111 +[PR 54]: https://github.com/arshaw/fullcalendar/pull/54 +[PR 49]: https://github.com/arshaw/fullcalendar/pull/49 +[PR 59]: https://github.com/arshaw/fullcalendar/pull/59 +[PR 55]: https://github.com/arshaw/fullcalendar/pull/55 +[PR 58]: https://github.com/arshaw/fullcalendar/pull/58 +[PR 88]: https://github.com/arshaw/fullcalendar/pull/88 +[PR 84]: https://github.com/arshaw/fullcalendar/pull/84 +[PR 56]: https://github.com/arshaw/fullcalendar/pull/56 + + +v1.6.2 (2013-07-18) +------------------- + +- `hiddenDays` option ([686]) +- bugfix: when `eventRender` returns `false`, incorrect stacking of events ([762]) +- bugfix: couldn't change `event.backgroundImage` when calling `updateEvent` (thx @stephenharris) + +[686]: https://code.google.com/p/fullcalendar/issues/detail?id=686 +[762]: https://code.google.com/p/fullcalendar/issues/detail?id=762 + + +v1.6.1 (2013-04-14) +------------------- + +- fixed event inner content overflow bug ([1783]) +- fixed table header className bug [1772] +- removed text-shadow on events (better for general use, thx @tkrotoff) + +[1783]: https://code.google.com/p/fullcalendar/issues/detail?id=1783 +[1772]: https://code.google.com/p/fullcalendar/issues/detail?id=1772 + + +v1.6.0 (2013-03-18) +------------------- + +- visual facelift, with bootstrap-inspired buttons and colors +- simplified HTML/CSS for events and buttons +- `dayRender`, for modifying a day cell ([191], thx @althaus) +- week numbers on side of calendar ([295]) + - `weekNumber` + - `weekNumberCalculation` + - `weekNumberTitle` + - `W` formatting variable +- finer snapping granularity for agenda view events ([495], thx @ms-doodle-com) +- `eventAfterAllRender` ([753], thx @pdrakeweb) +- `eventDataTransform` (thx @joeyspo) +- `data-date` attributes on cells (thx @Jae) +- expose `$.fullCalendar.dateFormatters` +- when clicking fast on buttons, prevent text selection +- bundled with latest jQuery (1.9.1) and jQuery UI (1.10.2) +- Grunt/Lumbar build system for internal development +- build for Bower package manager +- build for jQuery plugin site + +[191]: https://code.google.com/p/fullcalendar/issues/detail?id=191 +[295]: https://code.google.com/p/fullcalendar/issues/detail?id=295 +[495]: https://code.google.com/p/fullcalendar/issues/detail?id=495 +[753]: https://code.google.com/p/fullcalendar/issues/detail?id=753 + + +v1.5.4 (2012-09-05) +------------------- + +- made compatible with jQuery 1.8.* (thx @archaeron) +- bundled with jQuery 1.8.1 and jQuery UI 1.8.23 + + +v1.5.3 (2012-02-06) +------------------- + +- fixed dragging issue with jQuery UI 1.8.16 ([1168]) +- bundled with jQuery 1.7.1 and jQuery UI 1.8.17 + +[1168]: https://code.google.com/p/fullcalendar/issues/detail?id=1168 + + +v1.5.2 (2011-08-21) +------------------- + +- correctly process UTC "Z" ISO8601 date strings ([750]) + +[750]: https://code.google.com/p/fullcalendar/issues/detail?id=750 + + +v1.5.1 (2011-04-09) +------------------- + +- more flexible ISO8601 date parsing ([814]) +- more flexible parsing of UNIX timestamps ([826]) +- FullCalendar now buildable from source on a Mac ([795]) +- FullCalendar QA'd in FF4 ([883]) +- upgraded to jQuery 1.5.2 (which supports IE9) and jQuery UI 1.8.11 + +[814]: https://code.google.com/p/fullcalendar/issues/detail?id=814 +[826]: https://code.google.com/p/fullcalendar/issues/detail?id=826 +[795]: https://code.google.com/p/fullcalendar/issues/detail?id=795 +[883]: https://code.google.com/p/fullcalendar/issues/detail?id=883 + + +v1.5 (2011-03-19) +----------------- + +- slicker default styling for buttons +- reworked a lot of the calendar's HTML and accompanying CSS (solves [327] and [395]) +- more printer-friendly (fullcalendar-print.css) +- fullcalendar now inherits styles from jquery-ui themes differently. + styles for buttons are distinct from styles for calendar cells. + (solves [299]) +- can now color events through FullCalendar options and Event-Object properties ([117]) + THIS IS NOW THE PREFERRED METHOD OF COLORING EVENTS (as opposed to using className and CSS) + - FullCalendar options: + - eventColor (changes both background and border) + - eventBackgroundColor + - eventBorderColor + - eventTextColor + - Event-Object options: + - color (changes both background and border) + - backgroundColor + - borderColor + - textColor +- can now specify an event source as an *object* with a `url` property (json feed) or + an `events` property (function or array) with additional properties that will + be applied to the entire event source: + - color (changes both background and border) + - backgroudColor + - borderColor + - textColor + - className + - editable + - allDayDefault + - ignoreTimezone + - startParam (for a feed) + - endParam (for a feed) + - ANY OF THE JQUERY $.ajax OPTIONS + allows for easily changing from GET to POST and sending additional parameters ([386]) + allows for easily attaching ajax handlers such as `error` ([754]) + allows for turning caching on ([355]) +- Google Calendar feeds are now specified differently: + - specify a simple string of your feed's URL + - specify an *object* with a `url` property of your feed's URL. + you can include any of the new Event-Source options in this object. + - the old `$.fullCalendar.gcalFeed` method still works +- no more IE7 SSL popup ([504]) +- remove `cacheParam` - use json event source `cache` option instead +- latest jquery/jquery-ui + +[327]: https://code.google.com/p/fullcalendar/issues/detail?id=327 +[395]: https://code.google.com/p/fullcalendar/issues/detail?id=395 +[299]: https://code.google.com/p/fullcalendar/issues/detail?id=299 +[117]: https://code.google.com/p/fullcalendar/issues/detail?id=117 +[386]: https://code.google.com/p/fullcalendar/issues/detail?id=386 +[754]: https://code.google.com/p/fullcalendar/issues/detail?id=754 +[355]: https://code.google.com/p/fullcalendar/issues/detail?id=355 +[504]: https://code.google.com/p/fullcalendar/issues/detail?id=504 + + +v1.4.11 (2011-02-22) +-------------------- + +- fixed rerenderEvents bug ([790]) +- fixed bug with faulty dragging of events from all-day slot in agenda views +- bundled with jquery 1.5 and jquery-ui 1.8.9 + +[790]: https://code.google.com/p/fullcalendar/issues/detail?id=790 + + +v1.4.10 (2011-01-02) +-------------------- + +- fixed bug with resizing event to different week in 5-day month view ([740]) +- fixed bug with events not sticking after a removeEvents call ([757]) +- fixed bug with underlying parseTime method, and other uses of parseInt ([688]) + +[740]: https://code.google.com/p/fullcalendar/issues/detail?id=740 +[757]: https://code.google.com/p/fullcalendar/issues/detail?id=757 +[688]: https://code.google.com/p/fullcalendar/issues/detail?id=688 + + +v1.4.9 (2010-11-16) +------------------- + +- new algorithm for vertically stacking events ([111]) +- resizing an event to a different week ([306]) +- bug: some events not rendered with consecutive calls to addEventSource ([679]) + +[111]: https://code.google.com/p/fullcalendar/issues/detail?id=111 +[306]: https://code.google.com/p/fullcalendar/issues/detail?id=306 +[679]: https://code.google.com/p/fullcalendar/issues/detail?id=679 + + +v1.4.8 (2010-10-16) +------------------- + +- ignoreTimezone option (set to `false` to process UTC offsets in ISO8601 dates) +- bugfixes + - event refetching not being called under certain conditions ([417], [554]) + - event refetching being called multiple times under certain conditions ([586], [616]) + - selection cannot be triggered by right mouse button ([558]) + - agenda view left axis sized incorrectly ([465]) + - IE js error when calendar is too narrow ([517]) + - agenda view looks strange when no scrollbars ([235]) + - improved parsing of ISO8601 dates with UTC offsets +- $.fullCalendar.version +- an internal refactor of the code, for easier future development and modularity + +[417]: https://code.google.com/p/fullcalendar/issues/detail?id=417 +[554]: https://code.google.com/p/fullcalendar/issues/detail?id=554 +[586]: https://code.google.com/p/fullcalendar/issues/detail?id=586 +[616]: https://code.google.com/p/fullcalendar/issues/detail?id=616 +[558]: https://code.google.com/p/fullcalendar/issues/detail?id=558 +[465]: https://code.google.com/p/fullcalendar/issues/detail?id=465 +[517]: https://code.google.com/p/fullcalendar/issues/detail?id=517 +[235]: https://code.google.com/p/fullcalendar/issues/detail?id=235 + + +v1.4.7 (2010-07-05) +------------------- + +- "dropping" external objects onto the calendar + - droppable (boolean, to turn on/off) + - dropAccept (to filter which events the calendar will accept) + - drop (trigger) +- selectable options can now be specified with a View Option Hash +- bugfixes + - dragged & reverted events having wrong time text ([406]) + - bug rendering events that have an endtime with seconds, but no hours/minutes ([477]) + - gotoDate date overflow bug ([429]) + - wrong date reported when clicking on edge of last column in agenda views [412] +- support newlines in event titles +- select/unselect callbacks now passes native js event + +[406]: https://code.google.com/p/fullcalendar/issues/detail?id=406 +[477]: https://code.google.com/p/fullcalendar/issues/detail?id=477 +[429]: https://code.google.com/p/fullcalendar/issues/detail?id=429 +[412]: https://code.google.com/p/fullcalendar/issues/detail?id=412 + + +v1.4.6 (2010-05-31) +------------------- + +- "selecting" days or timeslots + - options: selectable, selectHelper, unselectAuto, unselectCancel + - callbacks: select, unselect + - methods: select, unselect +- when dragging an event, the highlighting reflects the duration of the event +- code compressing by Google Closure Compiler +- bundled with jQuery 1.4.2 and jQuery UI 1.8.1 + + +v1.4.5 (2010-02-21) +------------------- + +- lazyFetching option, which can force the calendar to fetch events on every view/date change +- scroll state of agenda views are preserved when switching back to view +- bugfixes + - calling methods on an uninitialized fullcalendar throws error + - IE6/7 bug where an entire view becomes invisible ([320]) + - error when rendering a hidden calendar (in jquery ui tabs for example) in IE ([340]) + - interconnected bugs related to calendar resizing and scrollbars + - when switching views or clicking prev/next, calendar would "blink" ([333]) + - liquid-width calendar's events shifted (depending on initial height of browser) ([341]) + - more robust underlying algorithm for calendar resizing + +[320]: https://code.google.com/p/fullcalendar/issues/detail?id=320 +[340]: https://code.google.com/p/fullcalendar/issues/detail?id=340 +[333]: https://code.google.com/p/fullcalendar/issues/detail?id=333 +[341]: https://code.google.com/p/fullcalendar/issues/detail?id=341 + + +v1.4.4 (2010-02-03) +------------------- + +- optimized event rendering in all views (events render in 1/10 the time) +- gotoDate() does not force the calendar to unnecessarily rerender +- render() method now correctly readjusts height + + +v1.4.3 (2009-12-22) +------------------- + +- added destroy method +- Google Calendar event pages respect currentTimezone +- caching now handled by jQuery's ajax +- protection from setting aspectRatio to zero +- bugfixes + - parseISO8601 and DST caused certain events to display day before + - button positioning problem in IE6 + - ajax event source removed after recently being added, events still displayed + - event not displayed when end is an empty string + - dynamically setting calendar height when no events have been fetched, throws error + + +v1.4.2 (2009-12-02) +------------------- + +- eventAfterRender trigger +- getDate & getView methods +- height & contentHeight options (explicitly sets the pixel height) +- minTime & maxTime options (restricts shown hours in agenda view) +- getters [for all options] and setters [for height, contentHeight, and aspectRatio ONLY! stay tuned..] +- render method now readjusts calendar's size +- bugfixes + - lightbox scripts that use iframes (like fancybox) + - day-of-week classNames were off when firstDay=1 + - guaranteed space on right side of agenda events (even when stacked) + - accepts ISO8601 dates with a space (instead of 'T') + + +v1.4.1 (2009-10-31) +------------------- + +- can exclude weekends with new 'weekends' option +- gcal feed 'currentTimezone' option +- bugfixes + - year/month/date option sometimes wouldn't set correctly (depending on current date) + - daylight savings issue caused agenda views to start at 1am (for BST users) +- cleanup of gcal.js code + + +v1.4 (2009-10-19) +----------------- + +- agendaWeek and agendaDay views +- added some options for agenda views: + - allDaySlot + - allDayText + - firstHour + - slotMinutes + - defaultEventMinutes + - axisFormat +- modified some existing options/triggers to work with agenda views: + - dragOpacity and timeFormat can now accept a "View Hash" (a new concept) + - dayClick now has an allDay parameter + - eventDrop now has an an allDay parameter + (this will affect those who use revertFunc, adjust parameter list) +- added 'prevYear' and 'nextYear' for buttons in header +- minor change for theme users, ui-state-hover not applied to active/inactive buttons +- added event-color-changing example in docs +- better defaults for right-to-left themed button icons + + +v1.3.2 (2009-10-13) +------------------- + +- Bugfixes (please upgrade from 1.3.1!) + - squashed potential infinite loop when addMonths and addDays + is called with an invalid date + - $.fullCalendar.parseDate() now correctly parses IETF format + - when switching views, the 'today' button sticks inactive, fixed +- gotoDate now can accept a single Date argument +- documentation for changes in 1.3.1 and 1.3.2 now on website + + +v1.3.1 (2009-09-30) +------------------- + +- Important Bugfixes (please upgrade from 1.3!) + - When current date was late in the month, for long months, and prev/next buttons + were clicked in month-view, some months would be skipped/repeated + - In certain time zones, daylight savings time would cause certain days + to be misnumbered in month-view +- Subtle change in way week interval is chosen when switching from month to basicWeek/basicDay view +- Added 'allDayDefault' option +- Added 'changeView' and 'render' methods + + +v1.3 (2009-09-21) +----------------- + +- different 'views': month/basicWeek/basicDay +- more flexible 'header' system for buttons +- themable by jQuery UI themes +- resizable events (require jQuery UI resizable plugin) +- rescoped & rewritten CSS, enhanced default look +- cleaner css & rendering techniques for right-to-left +- reworked options & API to support multiple views / be consistent with jQuery UI +- refactoring of entire codebase + - broken into different JS & CSS files, assembled w/ build scripts + - new test suite for new features, uses firebug-lite +- refactored docs +- Options + - + date + - + defaultView + - + aspectRatio + - + disableResizing + - + monthNames (use instead of $.fullCalendar.monthNames) + - + monthNamesShort (use instead of $.fullCalendar.monthAbbrevs) + - + dayNames (use instead of $.fullCalendar.dayNames) + - + dayNamesShort (use instead of $.fullCalendar.dayAbbrevs) + - + theme + - + buttonText + - + buttonIcons + - x draggable -> editable/disableDragging + - x fixedWeeks -> weekMode + - x abbrevDayHeadings -> columnFormat + - x buttons/title -> header + - x eventDragOpacity -> dragOpacity + - x eventRevertDuration -> dragRevertDuration + - x weekStart -> firstDay + - x rightToLeft -> isRTL + - x showTime (use 'allDay' CalEvent property instead) +- Triggered Actions + - + eventResizeStart + - + eventResizeStop + - + eventResize + - x monthDisplay -> viewDisplay + - x resize -> windowResize + - 'eventDrop' params changed, can revert if ajax cuts out +- CalEvent Properties + - x showTime -> allDay + - x draggable -> editable + - 'end' is now INCLUSIVE when allDay=true + - 'url' now produces a real tag, more native clicking/tab behavior +- Methods: + - + renderEvent + - x prevMonth -> prev + - x nextMonth -> next + - x prevYear/nextYear -> moveDate + - x refresh -> rerenderEvents/refetchEvents + - x removeEvent -> removeEvents + - x getEventsByID -> clientEvents +- Utilities: + - 'formatDate' format string completely changed (inspired by jQuery UI datepicker + datejs) + - 'formatDates' added to support date-ranges +- Google Calendar Options: + - x draggable -> editable +- Bugfixes + - gcal extension fetched 25 results max, now fetches all + + +v1.2.1 (2009-06-29) +------------------- + +- bugfixes + - allows and corrects invalid end dates for events + - doesn't throw an error in IE while rendering when display:none + - fixed 'loading' callback when used w/ multiple addEventSource calls + - gcal className can now be an array + + +v1.2 (2009-05-31) +----------------- + +- expanded API + - 'className' CalEvent attribute + - 'source' CalEvent attribute + - dynamically get/add/remove/update events of current month + - locale improvements: change month/day name text + - better date formatting ($.fullCalendar.formatDate) + - multiple 'event sources' allowed + - dynamically add/remove event sources +- options for prevYear and nextYear buttons +- docs have been reworked (include addition of Google Calendar docs) +- changed behavior of parseDate for number strings + (now interpets as unix timestamp, not MS times) +- bugfixes + - rightToLeft month start bug + - off-by-one errors with month formatting commands + - events from previous months sticking when clicking prev/next quickly +- Google Calendar API changed to work w/ multiple event sources + - can also provide 'className' and 'draggable' options +- date utilties moved from $ to $.fullCalendar +- more documentation in source code +- minified version of fullcalendar.js +- test suit (available from svn) +- top buttons now use `'); + buttonEl.addEventListener('click', buttonClick); + groupChildren.push(buttonEl); + } + } + }); + if (groupChildren.length > 1) { + groupEl = document.createElement('div'); + var buttonGroupClassName = theme.getClass('buttonGroup'); + if (isOnlyButtons && buttonGroupClassName) { + groupEl.classList.add(buttonGroupClassName); + } + appendToElement(groupEl, groupChildren); + sectionEl.appendChild(groupEl); + } + else { + appendToElement(sectionEl, groupChildren); // 1 or 0 children + } + }); + } + return sectionEl; + }; + Toolbar.prototype.updateToday = function (isTodayEnabled) { + this.toggleButtonEnabled('today', isTodayEnabled); + }; + Toolbar.prototype.updatePrev = function (isPrevEnabled) { + this.toggleButtonEnabled('prev', isPrevEnabled); + }; + Toolbar.prototype.updateNext = function (isNextEnabled) { + this.toggleButtonEnabled('next', isNextEnabled); + }; + Toolbar.prototype.updateTitle = function (text) { + findElements(this.el, 'h2').forEach(function (titleEl) { + titleEl.innerText = text; + }); + }; + Toolbar.prototype.updateActiveButton = function (buttonName) { + var className = this.theme.getClass('buttonActive'); + findElements(this.el, 'button').forEach(function (buttonEl) { + if (buttonName && buttonEl.classList.contains('fc-' + buttonName + '-button')) { + buttonEl.classList.add(className); + } + else { + buttonEl.classList.remove(className); + } + }); + }; + Toolbar.prototype.toggleButtonEnabled = function (buttonName, bool) { + findElements(this.el, '.fc-' + buttonName + '-button').forEach(function (buttonEl) { + buttonEl.disabled = !bool; + }); + }; + return Toolbar; + }(Component)); + + var CalendarComponent = /** @class */ (function (_super) { + __extends(CalendarComponent, _super); + function CalendarComponent(context, el) { + var _this = _super.call(this, context) || this; + _this._renderToolbars = memoizeRendering(_this.renderToolbars); + _this.buildViewPropTransformers = memoize(buildViewPropTransformers); + _this.el = el; + prependToElement(el, _this.contentEl = createElement('div', { className: 'fc-view-container' })); + var calendar = _this.calendar; + for (var _i = 0, _a = calendar.pluginSystem.hooks.viewContainerModifiers; _i < _a.length; _i++) { + var modifyViewContainer = _a[_i]; + modifyViewContainer(_this.contentEl, calendar); + } + _this.toggleElClassNames(true); + _this.computeTitle = memoize(computeTitle); + _this.parseBusinessHours = memoize(function (input) { + return parseBusinessHours(input, _this.calendar); + }); + return _this; + } + CalendarComponent.prototype.destroy = function () { + if (this.header) { + this.header.destroy(); + } + if (this.footer) { + this.footer.destroy(); + } + if (this.view) { + this.view.destroy(); + } + removeElement(this.contentEl); + this.toggleElClassNames(false); + _super.prototype.destroy.call(this); + }; + CalendarComponent.prototype.toggleElClassNames = function (bool) { + var classList = this.el.classList; + var dirClassName = 'fc-' + this.opt('dir'); + var themeClassName = this.theme.getClass('widget'); + if (bool) { + classList.add('fc'); + classList.add(dirClassName); + classList.add(themeClassName); + } + else { + classList.remove('fc'); + classList.remove(dirClassName); + classList.remove(themeClassName); + } + }; + CalendarComponent.prototype.render = function (props) { + this.freezeHeight(); + var title = this.computeTitle(props.dateProfile, props.viewSpec.options); + this._renderToolbars(props.viewSpec, props.dateProfile, props.currentDate, props.dateProfileGenerator, title); + this.renderView(props, title); + this.updateSize(); + this.thawHeight(); + }; + CalendarComponent.prototype.renderToolbars = function (viewSpec, dateProfile, currentDate, dateProfileGenerator, title) { + var headerLayout = this.opt('header'); + var footerLayout = this.opt('footer'); + var now = this.calendar.getNow(); + var todayInfo = dateProfileGenerator.build(now); + var prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate); + var nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate); + var toolbarProps = { + title: title, + activeButton: viewSpec.type, + isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now), + isPrevEnabled: prevInfo.isValid, + isNextEnabled: nextInfo.isValid + }; + if (headerLayout) { + if (!this.header) { + this.header = new Toolbar(this.context, 'fc-header-toolbar'); + prependToElement(this.el, this.header.el); + } + this.header.receiveProps(__assign({ layout: headerLayout }, toolbarProps)); + } + else if (this.header) { + this.header.destroy(); + this.header = null; + } + if (footerLayout) { + if (!this.footer) { + this.footer = new Toolbar(this.context, 'fc-footer-toolbar'); + appendToElement(this.el, this.footer.el); + } + this.footer.receiveProps(__assign({ layout: footerLayout }, toolbarProps)); + } + else if (this.footer) { + this.footer.destroy(); + this.footer = null; + } + }; + CalendarComponent.prototype.renderView = function (props, title) { + var view = this.view; + var viewSpec = props.viewSpec, dateProfileGenerator = props.dateProfileGenerator; + if (!view || view.viewSpec !== viewSpec) { + if (view) { + view.destroy(); + } + view = this.view = new viewSpec['class']({ + calendar: this.calendar, + view: null, + dateEnv: this.dateEnv, + theme: this.theme, + options: viewSpec.options + }, viewSpec, dateProfileGenerator, this.contentEl); + } + else { + view.addScroll(view.queryScroll()); + } + view.title = title; // for the API + var viewProps = { + dateProfile: props.dateProfile, + businessHours: this.parseBusinessHours(viewSpec.options.businessHours), + eventStore: props.eventStore, + eventUiBases: props.eventUiBases, + dateSelection: props.dateSelection, + eventSelection: props.eventSelection, + eventDrag: props.eventDrag, + eventResize: props.eventResize + }; + var transformers = this.buildViewPropTransformers(this.calendar.pluginSystem.hooks.viewPropsTransformers); + for (var _i = 0, transformers_1 = transformers; _i < transformers_1.length; _i++) { + var transformer = transformers_1[_i]; + __assign(viewProps, transformer.transform(viewProps, viewSpec, props, view)); + } + view.receiveProps(viewProps); + }; + // Sizing + // ----------------------------------------------------------------------------------------------------------------- + CalendarComponent.prototype.updateSize = function (isResize) { + if (isResize === void 0) { isResize = false; } + var view = this.view; + if (isResize) { + view.addScroll(view.queryScroll()); + } + if (isResize || this.isHeightAuto == null) { + this.computeHeightVars(); + } + view.updateSize(isResize, this.viewHeight, this.isHeightAuto); + view.updateNowIndicator(); // we need to guarantee this will run after updateSize + view.popScroll(isResize); + }; + CalendarComponent.prototype.computeHeightVars = function () { + var calendar = this.calendar; // yuck. need to handle dynamic options + var heightInput = calendar.opt('height'); + var contentHeightInput = calendar.opt('contentHeight'); + this.isHeightAuto = heightInput === 'auto' || contentHeightInput === 'auto'; + if (typeof contentHeightInput === 'number') { // exists and not 'auto' + this.viewHeight = contentHeightInput; + } + else if (typeof contentHeightInput === 'function') { // exists and is a function + this.viewHeight = contentHeightInput(); + } + else if (typeof heightInput === 'number') { // exists and not 'auto' + this.viewHeight = heightInput - this.queryToolbarsHeight(); + } + else if (typeof heightInput === 'function') { // exists and is a function + this.viewHeight = heightInput() - this.queryToolbarsHeight(); + } + else if (heightInput === 'parent') { // set to height of parent element + this.viewHeight = this.el.parentNode.offsetHeight - this.queryToolbarsHeight(); + } + else { + this.viewHeight = Math.round(this.contentEl.offsetWidth / + Math.max(calendar.opt('aspectRatio'), .5)); + } + }; + CalendarComponent.prototype.queryToolbarsHeight = function () { + var height = 0; + if (this.header) { + height += computeHeightAndMargins(this.header.el); + } + if (this.footer) { + height += computeHeightAndMargins(this.footer.el); + } + return height; + }; + // Height "Freezing" + // ----------------------------------------------------------------------------------------------------------------- + CalendarComponent.prototype.freezeHeight = function () { + applyStyle(this.el, { + height: this.el.offsetHeight, + overflow: 'hidden' + }); + }; + CalendarComponent.prototype.thawHeight = function () { + applyStyle(this.el, { + height: '', + overflow: '' + }); + }; + return CalendarComponent; + }(Component)); + // Title and Date Formatting + // ----------------------------------------------------------------------------------------------------------------- + // Computes what the title at the top of the calendar should be for this view + function computeTitle(dateProfile, viewOptions) { + var range; + // for views that span a large unit of time, show the proper interval, ignoring stray days before and after + if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) { + range = dateProfile.currentRange; + } + else { // for day units or smaller, use the actual day range + range = dateProfile.activeRange; + } + return this.dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || computeTitleFormat(dateProfile), viewOptions.titleRangeSeparator), { isEndExclusive: dateProfile.isRangeAllDay }); + } + // Generates the format string that should be used to generate the title for the current date range. + // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`. + function computeTitleFormat(dateProfile) { + var currentRangeUnit = dateProfile.currentRangeUnit; + if (currentRangeUnit === 'year') { + return { year: 'numeric' }; + } + else if (currentRangeUnit === 'month') { + return { year: 'numeric', month: 'long' }; // like "September 2014" + } + else { + var days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end); + if (days !== null && days > 1) { + // multi-day range. shorter, like "Sep 9 - 10 2014" + return { year: 'numeric', month: 'short', day: 'numeric' }; + } + else { + // one day. longer, like "September 9 2014" + return { year: 'numeric', month: 'long', day: 'numeric' }; + } + } + } + // Plugin + // ----------------------------------------------------------------------------------------------------------------- + function buildViewPropTransformers(theClasses) { + return theClasses.map(function (theClass) { + return new theClass(); + }); + } + + var Interaction = /** @class */ (function () { + function Interaction(settings) { + this.component = settings.component; + } + Interaction.prototype.destroy = function () { + }; + return Interaction; + }()); + function parseInteractionSettings(component, input) { + return { + component: component, + el: input.el, + useEventCenter: input.useEventCenter != null ? input.useEventCenter : true + }; + } + function interactionSettingsToStore(settings) { + var _a; + return _a = {}, + _a[settings.component.uid] = settings, + _a; + } + // global state + var interactionSettingsStore = {}; + + /* + Detects when the user clicks on an event within a DateComponent + */ + var EventClicking = /** @class */ (function (_super) { + __extends(EventClicking, _super); + function EventClicking(settings) { + var _this = _super.call(this, settings) || this; + _this.handleSegClick = function (ev, segEl) { + var component = _this.component; + var seg = getElSeg(segEl); + if (seg && // might be the
surrounding the more link + component.isValidSegDownEl(ev.target)) { + // our way to simulate a link click for elements that can't be tags + // grab before trigger fired in case trigger trashes DOM thru rerendering + var hasUrlContainer = elementClosest(ev.target, '.fc-has-url'); + var url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : ''; + component.publiclyTrigger('eventClick', [ + { + el: segEl, + event: new EventApi(component.calendar, seg.eventRange.def, seg.eventRange.instance), + jsEvent: ev, + view: component.view + } + ]); + if (url && !ev.defaultPrevented) { + window.location.href = url; + } + } + }; + var component = settings.component; + _this.destroy = listenBySelector(component.el, 'click', component.fgSegSelector + ',' + component.bgSegSelector, _this.handleSegClick); + return _this; + } + return EventClicking; + }(Interaction)); + + /* + Triggers events and adds/removes core classNames when the user's pointer + enters/leaves event-elements of a component. + */ + var EventHovering = /** @class */ (function (_super) { + __extends(EventHovering, _super); + function EventHovering(settings) { + var _this = _super.call(this, settings) || this; + // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it + _this.handleEventElRemove = function (el) { + if (el === _this.currentSegEl) { + _this.handleSegLeave(null, _this.currentSegEl); + } + }; + _this.handleSegEnter = function (ev, segEl) { + if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper + segEl.classList.add('fc-allow-mouse-resize'); + _this.currentSegEl = segEl; + _this.triggerEvent('eventMouseEnter', ev, segEl); + } + }; + _this.handleSegLeave = function (ev, segEl) { + if (_this.currentSegEl) { + segEl.classList.remove('fc-allow-mouse-resize'); + _this.currentSegEl = null; + _this.triggerEvent('eventMouseLeave', ev, segEl); + } + }; + var component = settings.component; + _this.removeHoverListeners = listenToHoverBySelector(component.el, component.fgSegSelector + ',' + component.bgSegSelector, _this.handleSegEnter, _this.handleSegLeave); + component.calendar.on('eventElRemove', _this.handleEventElRemove); + return _this; + } + EventHovering.prototype.destroy = function () { + this.removeHoverListeners(); + this.component.calendar.off('eventElRemove', this.handleEventElRemove); + }; + EventHovering.prototype.triggerEvent = function (publicEvName, ev, segEl) { + var component = this.component; + var seg = getElSeg(segEl); + if (!ev || component.isValidSegDownEl(ev.target)) { + component.publiclyTrigger(publicEvName, [ + { + el: segEl, + event: new EventApi(this.component.calendar, seg.eventRange.def, seg.eventRange.instance), + jsEvent: ev, + view: component.view + } + ]); + } + }; + return EventHovering; + }(Interaction)); + + var StandardTheme = /** @class */ (function (_super) { + __extends(StandardTheme, _super); + function StandardTheme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return StandardTheme; + }(Theme)); + StandardTheme.prototype.classes = { + widget: 'fc-unthemed', + widgetHeader: 'fc-widget-header', + widgetContent: 'fc-widget-content', + buttonGroup: 'fc-button-group', + button: 'fc-button fc-button-primary', + buttonActive: 'fc-button-active', + popoverHeader: 'fc-widget-header', + popoverContent: 'fc-widget-content', + // day grid + headerRow: 'fc-widget-header', + dayRow: 'fc-widget-content', + // list view + listView: 'fc-widget-content' + }; + StandardTheme.prototype.baseIconClass = 'fc-icon'; + StandardTheme.prototype.iconClasses = { + close: 'fc-icon-x', + prev: 'fc-icon-chevron-left', + next: 'fc-icon-chevron-right', + prevYear: 'fc-icon-chevrons-left', + nextYear: 'fc-icon-chevrons-right' + }; + StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; + StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon'; + StandardTheme.prototype.iconOverridePrefix = 'fc-icon-'; + + var Calendar = /** @class */ (function () { + function Calendar(el, overrides) { + var _this = this; + this.parseRawLocales = memoize(parseRawLocales); + this.buildLocale = memoize(buildLocale); + this.buildDateEnv = memoize(buildDateEnv); + this.buildTheme = memoize(buildTheme); + this.buildEventUiSingleBase = memoize(this._buildEventUiSingleBase); + this.buildSelectionConfig = memoize(this._buildSelectionConfig); + this.buildEventUiBySource = memoizeOutput(buildEventUiBySource, isObjectsSimilar); + this.buildEventUiBases = memoize(buildEventUiBases); + this.interactionsStore = {}; + this.actionQueue = []; + this.isReducing = false; + // isDisplaying: boolean = false // installed in DOM? accepting renders? + this.needsRerender = false; // needs a render? + this.needsFullRerender = false; + this.isRendering = false; // currently in the executeRender function? + this.renderingPauseDepth = 0; + this.buildDelayedRerender = memoize(buildDelayedRerender); + this.afterSizingTriggers = {}; + this.isViewUpdated = false; + this.isDatesUpdated = false; + this.isEventsUpdated = false; + this.el = el; + this.optionsManager = new OptionsManager(overrides || {}); + this.pluginSystem = new PluginSystem(); + // only do once. don't do in handleOptions. because can't remove plugins + this.addPluginInputs(this.optionsManager.computed.plugins || []); + this.handleOptions(this.optionsManager.computed); + this.publiclyTrigger('_init'); // for tests + this.hydrate(); + this.calendarInteractions = this.pluginSystem.hooks.calendarInteractions + .map(function (calendarInteractionClass) { + return new calendarInteractionClass(_this); + }); + } + Calendar.prototype.addPluginInputs = function (pluginInputs) { + var pluginDefs = refinePluginDefs(pluginInputs); + for (var _i = 0, pluginDefs_1 = pluginDefs; _i < pluginDefs_1.length; _i++) { + var pluginDef = pluginDefs_1[_i]; + this.pluginSystem.add(pluginDef); + } + }; + Object.defineProperty(Calendar.prototype, "view", { + // public API + get: function () { + return this.component ? this.component.view : null; + }, + enumerable: true, + configurable: true + }); + // Public API for rendering + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.render = function () { + if (!this.component) { + this.renderableEventStore = createEmptyEventStore(); + this.bindHandlers(); + this.executeRender(); + } + else { + this.requestRerender(true); + } + }; + Calendar.prototype.destroy = function () { + if (this.component) { + this.unbindHandlers(); + this.component.destroy(); // don't null-out. in case API needs access + this.component = null; // umm ??? + for (var _i = 0, _a = this.calendarInteractions; _i < _a.length; _i++) { + var interaction = _a[_i]; + interaction.destroy(); + } + this.publiclyTrigger('_destroyed'); + } + }; + // Handlers + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.bindHandlers = function () { + var _this = this; + // event delegation for nav links + this.removeNavLinkListener = listenBySelector(this.el, 'click', 'a[data-goto]', function (ev, anchorEl) { + var gotoOptions = anchorEl.getAttribute('data-goto'); + gotoOptions = gotoOptions ? JSON.parse(gotoOptions) : {}; + var dateEnv = _this.dateEnv; + var dateMarker = dateEnv.createMarker(gotoOptions.date); + var viewType = gotoOptions.type; + // property like "navLinkDayClick". might be a string or a function + var customAction = _this.viewOpt('navLink' + capitaliseFirstLetter(viewType) + 'Click'); + if (typeof customAction === 'function') { + customAction(dateEnv.toDate(dateMarker), ev); + } + else { + if (typeof customAction === 'string') { + viewType = customAction; + } + _this.zoomTo(dateMarker, viewType); + } + }); + if (this.opt('handleWindowResize')) { + window.addEventListener('resize', this.windowResizeProxy = debounce(// prevents rapid calls + this.windowResize.bind(this), this.opt('windowResizeDelay'))); + } + }; + Calendar.prototype.unbindHandlers = function () { + this.removeNavLinkListener(); + if (this.windowResizeProxy) { + window.removeEventListener('resize', this.windowResizeProxy); + this.windowResizeProxy = null; + } + }; + // Dispatcher + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.hydrate = function () { + var _this = this; + this.state = this.buildInitialState(); + var rawSources = this.opt('eventSources') || []; + var singleRawSource = this.opt('events'); + var sources = []; // parsed + if (singleRawSource) { + rawSources.unshift(singleRawSource); + } + for (var _i = 0, rawSources_1 = rawSources; _i < rawSources_1.length; _i++) { + var rawSource = rawSources_1[_i]; + var source = parseEventSource(rawSource, this); + if (source) { + sources.push(source); + } + } + this.batchRendering(function () { + _this.dispatch({ type: 'INIT' }); // pass in sources here? + _this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: sources }); + _this.dispatch({ + type: 'SET_VIEW_TYPE', + viewType: _this.opt('defaultView') || _this.pluginSystem.hooks.defaultView + }); + }); + }; + Calendar.prototype.buildInitialState = function () { + return { + viewType: null, + loadingLevel: 0, + eventSourceLoadingLevel: 0, + currentDate: this.getInitialDate(), + dateProfile: null, + eventSources: {}, + eventStore: createEmptyEventStore(), + dateSelection: null, + eventSelection: '', + eventDrag: null, + eventResize: null + }; + }; + Calendar.prototype.dispatch = function (action) { + this.actionQueue.push(action); + if (!this.isReducing) { + this.isReducing = true; + var oldState = this.state; + while (this.actionQueue.length) { + this.state = this.reduce(this.state, this.actionQueue.shift(), this); + } + var newState = this.state; + this.isReducing = false; + if (!oldState.loadingLevel && newState.loadingLevel) { + this.publiclyTrigger('loading', [true]); + } + else if (oldState.loadingLevel && !newState.loadingLevel) { + this.publiclyTrigger('loading', [false]); + } + var view = this.component && this.component.view; + if (oldState.eventStore !== newState.eventStore || this.needsFullRerender) { + if (oldState.eventStore) { + this.isEventsUpdated = true; + } + } + if (oldState.dateProfile !== newState.dateProfile || this.needsFullRerender) { + if (oldState.dateProfile && view) { // why would view be null!? + this.publiclyTrigger('datesDestroy', [ + { + view: view, + el: view.el + } + ]); + } + this.isDatesUpdated = true; + } + if (oldState.viewType !== newState.viewType || this.needsFullRerender) { + if (oldState.viewType && view) { // why would view be null!? + this.publiclyTrigger('viewSkeletonDestroy', [ + { + view: view, + el: view.el + } + ]); + } + this.isViewUpdated = true; + } + this.requestRerender(); + } + }; + Calendar.prototype.reduce = function (state, action, calendar) { + return reduce(state, action, calendar); + }; + // Render Queue + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.requestRerender = function (needsFull) { + if (needsFull === void 0) { needsFull = false; } + this.needsRerender = true; + this.needsFullRerender = this.needsFullRerender || needsFull; + this.delayedRerender(); // will call a debounced-version of tryRerender + }; + Calendar.prototype.tryRerender = function () { + if (this.component && // must be accepting renders + this.needsRerender && // indicates that a rerender was requested + !this.renderingPauseDepth && // not paused + !this.isRendering // not currently in the render loop + ) { + this.executeRender(); + } + }; + Calendar.prototype.batchRendering = function (func) { + this.renderingPauseDepth++; + func(); + this.renderingPauseDepth--; + if (this.needsRerender) { + this.requestRerender(); + } + }; + // Rendering + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.executeRender = function () { + var needsFullRerender = this.needsFullRerender; // save before clearing + // clear these BEFORE the render so that new values will accumulate during render + this.needsRerender = false; + this.needsFullRerender = false; + this.isRendering = true; + this.renderComponent(needsFullRerender); + this.isRendering = false; + // received a rerender request while rendering + if (this.needsRerender) { + this.delayedRerender(); + } + }; + /* + don't call this directly. use executeRender instead + */ + Calendar.prototype.renderComponent = function (needsFull) { + var _a = this, state = _a.state, component = _a.component; + var viewType = state.viewType; + var viewSpec = this.viewSpecs[viewType]; + var savedScroll = (needsFull && component) ? component.view.queryScroll() : null; + if (!viewSpec) { + throw new Error("View type \"" + viewType + "\" is not valid"); + } + // if event sources are still loading and progressive rendering hasn't been enabled, + // keep rendering the last fully loaded set of events + var renderableEventStore = this.renderableEventStore = + (state.eventSourceLoadingLevel && !this.opt('progressiveEventRendering')) ? + this.renderableEventStore : + state.eventStore; + var eventUiSingleBase = this.buildEventUiSingleBase(viewSpec.options); + var eventUiBySource = this.buildEventUiBySource(state.eventSources); + var eventUiBases = this.eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource); + if (needsFull || !component) { + if (component) { + component.freezeHeight(); // next component will unfreeze it + component.destroy(); + } + component = this.component = new CalendarComponent({ + calendar: this, + view: null, + dateEnv: this.dateEnv, + theme: this.theme, + options: this.optionsManager.computed + }, this.el); + } + component.receiveProps(__assign({}, state, { viewSpec: viewSpec, dateProfile: state.dateProfile, dateProfileGenerator: this.dateProfileGenerators[viewType], eventStore: renderableEventStore, eventUiBases: eventUiBases, dateSelection: state.dateSelection, eventSelection: state.eventSelection, eventDrag: state.eventDrag, eventResize: state.eventResize })); + if (savedScroll) { + component.view.applyScroll(savedScroll, false); + } + if (this.isViewUpdated) { + this.isViewUpdated = false; + this.publiclyTrigger('viewSkeletonRender', [ + { + view: component.view, + el: component.view.el + } + ]); + } + if (this.isDatesUpdated) { + this.isDatesUpdated = false; + this.publiclyTrigger('datesRender', [ + { + view: component.view, + el: component.view.el + } + ]); + } + if (this.isEventsUpdated) { + this.isEventsUpdated = false; + } + this.releaseAfterSizingTriggers(); + }; + // Options + // ----------------------------------------------------------------------------------------------------------------- + /* + Not meant for public API + */ + Calendar.prototype.resetOptions = function (options) { + var _this = this; + var changeHandlers = this.pluginSystem.hooks.optionChangeHandlers; + var oldOptions = this.optionsManager.overrides; + var oldNormalOptions = {}; + var normalOptions = {}; + var specialOptions = {}; + for (var name_1 in oldOptions) { + if (!changeHandlers[name_1]) { + oldNormalOptions[name_1] = oldOptions[name_1]; + } + } + for (var name_2 in options) { + if (changeHandlers[name_2]) { + specialOptions[name_2] = options[name_2]; + } + else { + normalOptions[name_2] = options[name_2]; + } + } + this.batchRendering(function () { + if (anyKeysRemoved(oldNormalOptions, normalOptions)) { + _this.processOptions(options, 'reset'); + } + else { + _this.processOptions(computeChangedProps(oldNormalOptions, normalOptions)); + } + // handle special options last + for (var name_3 in specialOptions) { + changeHandlers[name_3](specialOptions[name_3], _this); + } + }); + }; + /* + Not meant for public API. Won't give the same precedence that setOption does + */ + Calendar.prototype.setOptions = function (options) { + var _this = this; + var changeHandlers = this.pluginSystem.hooks.optionChangeHandlers; + var normalOptions = {}; + var specialOptions = {}; + for (var name_4 in options) { + if (changeHandlers[name_4]) { + specialOptions[name_4] = options[name_4]; + } + else { + normalOptions[name_4] = options[name_4]; + } + } + this.batchRendering(function () { + _this.processOptions(normalOptions); + // handle special options last + for (var name_5 in specialOptions) { + changeHandlers[name_5](specialOptions[name_5], _this); + } + }); + }; + Calendar.prototype.processOptions = function (options, mode) { + var _this = this; + var oldDateEnv = this.dateEnv; // do this before handleOptions + var isTimeZoneDirty = false; + var isSizeDirty = false; + var anyDifficultOptions = false; + for (var name_6 in options) { + if (/^(height|contentHeight|aspectRatio)$/.test(name_6)) { + isSizeDirty = true; + } + else if (/^(defaultDate|defaultView)$/.test(name_6)) ; + else { + anyDifficultOptions = true; + if (name_6 === 'timeZone') { + isTimeZoneDirty = true; + } + } + } + if (mode === 'reset') { + anyDifficultOptions = true; + this.optionsManager.reset(options); + } + else if (mode === 'dynamic') { + this.optionsManager.addDynamic(options); // takes higher precedence + } + else { + this.optionsManager.add(options); + } + if (anyDifficultOptions) { + this.handleOptions(this.optionsManager.computed); // only for "difficult" options + this.needsFullRerender = true; + this.batchRendering(function () { + if (isTimeZoneDirty) { + _this.dispatch({ + type: 'CHANGE_TIMEZONE', + oldDateEnv: oldDateEnv + }); + } + /* HACK + has the same effect as calling this.requestRerender(true) + but recomputes the state's dateProfile + */ + _this.dispatch({ + type: 'SET_VIEW_TYPE', + viewType: _this.state.viewType + }); + }); + } + if (isSizeDirty) { + this.updateSize(); + } + }; + Calendar.prototype.setOption = function (name, val) { + var _a; + this.processOptions((_a = {}, _a[name] = val, _a), 'dynamic'); + }; + Calendar.prototype.getOption = function (name) { + return this.optionsManager.computed[name]; + }; + Calendar.prototype.opt = function (name) { + return this.optionsManager.computed[name]; + }; + Calendar.prototype.viewOpt = function (name) { + return this.viewOpts()[name]; + }; + Calendar.prototype.viewOpts = function () { + return this.viewSpecs[this.state.viewType].options; + }; + /* + rebuilds things based off of a complete set of refined options + */ + Calendar.prototype.handleOptions = function (options) { + var _this = this; + var pluginHooks = this.pluginSystem.hooks; + this.defaultAllDayEventDuration = createDuration(options.defaultAllDayEventDuration); + this.defaultTimedEventDuration = createDuration(options.defaultTimedEventDuration); + this.delayedRerender = this.buildDelayedRerender(options.rerenderDelay); + this.theme = this.buildTheme(options); + var available = this.parseRawLocales(options.locales); + this.availableRawLocales = available.map; + var locale = this.buildLocale(options.locale || available.defaultCode, available.map); + this.dateEnv = this.buildDateEnv(locale, options.timeZone, pluginHooks.namedTimeZonedImpl, options.firstDay, options.weekNumberCalculation, options.weekLabel, pluginHooks.cmdFormatter); + this.selectionConfig = this.buildSelectionConfig(options); // needs dateEnv. do after :( + // ineffecient to do every time? + this.viewSpecs = buildViewSpecs(pluginHooks.views, this.optionsManager); + // ineffecient to do every time? + this.dateProfileGenerators = mapHash(this.viewSpecs, function (viewSpec) { + return new viewSpec.class.prototype.dateProfileGeneratorClass(viewSpec, _this); + }); + }; + Calendar.prototype.getAvailableLocaleCodes = function () { + return Object.keys(this.availableRawLocales); + }; + Calendar.prototype._buildSelectionConfig = function (rawOpts) { + return processScopedUiProps('select', rawOpts, this); + }; + Calendar.prototype._buildEventUiSingleBase = function (rawOpts) { + if (rawOpts.editable) { // so 'editable' affected events + rawOpts = __assign({}, rawOpts, { eventEditable: true }); + } + return processScopedUiProps('event', rawOpts, this); + }; + // Trigger + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.hasPublicHandlers = function (name) { + return this.hasHandlers(name) || + this.opt(name); // handler specified in options + }; + Calendar.prototype.publiclyTrigger = function (name, args) { + var optHandler = this.opt(name); + this.triggerWith(name, this, args); + if (optHandler) { + return optHandler.apply(this, args); + } + }; + Calendar.prototype.publiclyTriggerAfterSizing = function (name, args) { + var afterSizingTriggers = this.afterSizingTriggers; + (afterSizingTriggers[name] || (afterSizingTriggers[name] = [])).push(args); + }; + Calendar.prototype.releaseAfterSizingTriggers = function () { + var afterSizingTriggers = this.afterSizingTriggers; + for (var name_7 in afterSizingTriggers) { + for (var _i = 0, _a = afterSizingTriggers[name_7]; _i < _a.length; _i++) { + var args = _a[_i]; + this.publiclyTrigger(name_7, args); + } + } + this.afterSizingTriggers = {}; + }; + // View + // ----------------------------------------------------------------------------------------------------------------- + // Returns a boolean about whether the view is okay to instantiate at some point + Calendar.prototype.isValidViewType = function (viewType) { + return Boolean(this.viewSpecs[viewType]); + }; + Calendar.prototype.changeView = function (viewType, dateOrRange) { + var dateMarker = null; + if (dateOrRange) { + if (dateOrRange.start && dateOrRange.end) { // a range + this.optionsManager.addDynamic({ visibleRange: dateOrRange }); // will not rerender + this.handleOptions(this.optionsManager.computed); // ...but yuck + } + else { // a date + dateMarker = this.dateEnv.createMarker(dateOrRange); // just like gotoDate + } + } + this.unselect(); + this.dispatch({ + type: 'SET_VIEW_TYPE', + viewType: viewType, + dateMarker: dateMarker + }); + }; + // Forces navigation to a view for the given date. + // `viewType` can be a specific view name or a generic one like "week" or "day". + // needs to change + Calendar.prototype.zoomTo = function (dateMarker, viewType) { + var spec; + viewType = viewType || 'day'; // day is default zoom + spec = this.viewSpecs[viewType] || + this.getUnitViewSpec(viewType); + this.unselect(); + if (spec) { + this.dispatch({ + type: 'SET_VIEW_TYPE', + viewType: spec.type, + dateMarker: dateMarker + }); + } + else { + this.dispatch({ + type: 'SET_DATE', + dateMarker: dateMarker + }); + } + }; + // Given a duration singular unit, like "week" or "day", finds a matching view spec. + // Preference is given to views that have corresponding buttons. + Calendar.prototype.getUnitViewSpec = function (unit) { + var viewTypes; + var i; + var spec; + // put views that have buttons first. there will be duplicates, but oh well + viewTypes = this.component.header.viewsWithButtons; // TODO: include footer as well? + for (var viewType in this.viewSpecs) { + viewTypes.push(viewType); + } + for (i = 0; i < viewTypes.length; i++) { + spec = this.viewSpecs[viewTypes[i]]; + if (spec) { + if (spec.singleUnit === unit) { + return spec; + } + } + } + }; + // Current Date + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.getInitialDate = function () { + var defaultDateInput = this.opt('defaultDate'); + // compute the initial ambig-timezone date + if (defaultDateInput != null) { + return this.dateEnv.createMarker(defaultDateInput); + } + else { + return this.getNow(); // getNow already returns unzoned + } + }; + Calendar.prototype.prev = function () { + this.unselect(); + this.dispatch({ type: 'PREV' }); + }; + Calendar.prototype.next = function () { + this.unselect(); + this.dispatch({ type: 'NEXT' }); + }; + Calendar.prototype.prevYear = function () { + this.unselect(); + this.dispatch({ + type: 'SET_DATE', + dateMarker: this.dateEnv.addYears(this.state.currentDate, -1) + }); + }; + Calendar.prototype.nextYear = function () { + this.unselect(); + this.dispatch({ + type: 'SET_DATE', + dateMarker: this.dateEnv.addYears(this.state.currentDate, 1) + }); + }; + Calendar.prototype.today = function () { + this.unselect(); + this.dispatch({ + type: 'SET_DATE', + dateMarker: this.getNow() + }); + }; + Calendar.prototype.gotoDate = function (zonedDateInput) { + this.unselect(); + this.dispatch({ + type: 'SET_DATE', + dateMarker: this.dateEnv.createMarker(zonedDateInput) + }); + }; + Calendar.prototype.incrementDate = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // else, warn about invalid input? + this.unselect(); + this.dispatch({ + type: 'SET_DATE', + dateMarker: this.dateEnv.add(this.state.currentDate, delta) + }); + } + }; + // for external API + Calendar.prototype.getDate = function () { + return this.dateEnv.toDate(this.state.currentDate); + }; + // Date Formatting Utils + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.formatDate = function (d, formatter) { + var dateEnv = this.dateEnv; + return dateEnv.format(dateEnv.createMarker(d), createFormatter(formatter)); + }; + // `settings` is for formatter AND isEndExclusive + Calendar.prototype.formatRange = function (d0, d1, settings) { + var dateEnv = this.dateEnv; + return dateEnv.formatRange(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings, this.opt('defaultRangeSeparator')), settings); + }; + Calendar.prototype.formatIso = function (d, omitTime) { + var dateEnv = this.dateEnv; + return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime: omitTime }); + }; + // Sizing + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.windowResize = function (ev) { + if (!this.isHandlingWindowResize && + this.component && // why? + ev.target === window // not a jqui resize event + ) { + this.isHandlingWindowResize = true; + this.updateSize(); + this.publiclyTrigger('windowResize', [this.view]); + this.isHandlingWindowResize = false; + } + }; + Calendar.prototype.updateSize = function () { + if (this.component) { // when? + this.component.updateSize(true); + } + }; + // Component Registration + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.registerInteractiveComponent = function (component, settingsInput) { + var settings = parseInteractionSettings(component, settingsInput); + var DEFAULT_INTERACTIONS = [ + EventClicking, + EventHovering + ]; + var interactionClasses = DEFAULT_INTERACTIONS.concat(this.pluginSystem.hooks.componentInteractions); + var interactions = interactionClasses.map(function (interactionClass) { + return new interactionClass(settings); + }); + this.interactionsStore[component.uid] = interactions; + interactionSettingsStore[component.uid] = settings; + }; + Calendar.prototype.unregisterInteractiveComponent = function (component) { + for (var _i = 0, _a = this.interactionsStore[component.uid]; _i < _a.length; _i++) { + var listener = _a[_i]; + listener.destroy(); + } + delete this.interactionsStore[component.uid]; + delete interactionSettingsStore[component.uid]; + }; + // Date Selection / Event Selection / DayClick + // ----------------------------------------------------------------------------------------------------------------- + // this public method receives start/end dates in any format, with any timezone + // NOTE: args were changed from v3 + Calendar.prototype.select = function (dateOrObj, endDate) { + var selectionInput; + if (endDate == null) { + if (dateOrObj.start != null) { + selectionInput = dateOrObj; + } + else { + selectionInput = { + start: dateOrObj, + end: null + }; + } + } + else { + selectionInput = { + start: dateOrObj, + end: endDate + }; + } + var selection = parseDateSpan(selectionInput, this.dateEnv, createDuration({ days: 1 }) // TODO: cache this? + ); + if (selection) { // throw parse error otherwise? + this.dispatch({ type: 'SELECT_DATES', selection: selection }); + this.triggerDateSelect(selection); + } + }; + // public method + Calendar.prototype.unselect = function (pev) { + if (this.state.dateSelection) { + this.dispatch({ type: 'UNSELECT_DATES' }); + this.triggerDateUnselect(pev); + } + }; + Calendar.prototype.triggerDateSelect = function (selection, pev) { + var arg = this.buildDateSpanApi(selection); + arg.jsEvent = pev ? pev.origEvent : null; + arg.view = this.view; + this.publiclyTrigger('select', [arg]); + }; + Calendar.prototype.triggerDateUnselect = function (pev) { + this.publiclyTrigger('unselect', [ + { + jsEvent: pev ? pev.origEvent : null, + view: this.view + } + ]); + }; + // TODO: receive pev? + Calendar.prototype.triggerDateClick = function (dateSpan, dayEl, view, ev) { + var arg = this.buildDatePointApi(dateSpan); + arg.dayEl = dayEl; + arg.jsEvent = ev; + arg.view = view; + this.publiclyTrigger('dateClick', [arg]); + }; + Calendar.prototype.buildDatePointApi = function (dateSpan) { + var props = {}; + for (var _i = 0, _a = this.pluginSystem.hooks.datePointTransforms; _i < _a.length; _i++) { + var transform = _a[_i]; + __assign(props, transform(dateSpan, this)); + } + __assign(props, buildDatePointApi(dateSpan, this.dateEnv)); + return props; + }; + Calendar.prototype.buildDateSpanApi = function (dateSpan) { + var props = {}; + for (var _i = 0, _a = this.pluginSystem.hooks.dateSpanTransforms; _i < _a.length; _i++) { + var transform = _a[_i]; + __assign(props, transform(dateSpan, this)); + } + __assign(props, buildDateSpanApi(dateSpan, this.dateEnv)); + return props; + }; + // Date Utils + // ----------------------------------------------------------------------------------------------------------------- + // Returns a DateMarker for the current date, as defined by the client's computer or from the `now` option + Calendar.prototype.getNow = function () { + var now = this.opt('now'); + if (typeof now === 'function') { + now = now(); + } + if (now == null) { + return this.dateEnv.createNowMarker(); + } + return this.dateEnv.createMarker(now); + }; + // Event-Date Utilities + // ----------------------------------------------------------------------------------------------------------------- + // Given an event's allDay status and start date, return what its fallback end date should be. + // TODO: rename to computeDefaultEventEnd + Calendar.prototype.getDefaultEventEnd = function (allDay, marker) { + var end = marker; + if (allDay) { + end = startOfDay(end); + end = this.dateEnv.add(end, this.defaultAllDayEventDuration); + } + else { + end = this.dateEnv.add(end, this.defaultTimedEventDuration); + } + return end; + }; + // Public Events API + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.addEvent = function (eventInput, sourceInput) { + if (eventInput instanceof EventApi) { + var def = eventInput._def; + var instance = eventInput._instance; + // not already present? don't want to add an old snapshot + if (!this.state.eventStore.defs[def.defId]) { + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore({ def: def, instance: instance }) // TODO: better util for two args? + }); + } + return eventInput; + } + var sourceId; + if (sourceInput instanceof EventSourceApi) { + sourceId = sourceInput.internalEventSource.sourceId; + } + else if (sourceInput != null) { + var sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function + if (!sourceApi) { + console.warn('Could not find an event source with ID "' + sourceInput + '"'); // TODO: test + return null; + } + else { + sourceId = sourceApi.internalEventSource.sourceId; + } + } + var tuple = parseEvent(eventInput, sourceId, this); + if (tuple) { + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore(tuple) + }); + return new EventApi(this, tuple.def, tuple.def.recurringDef ? null : tuple.instance); + } + return null; + }; + // TODO: optimize + Calendar.prototype.getEventById = function (id) { + var _a = this.state.eventStore, defs = _a.defs, instances = _a.instances; + id = String(id); + for (var defId in defs) { + var def = defs[defId]; + if (def.publicId === id) { + if (def.recurringDef) { + return new EventApi(this, def, null); + } + else { + for (var instanceId in instances) { + var instance = instances[instanceId]; + if (instance.defId === def.defId) { + return new EventApi(this, def, instance); + } + } + } + } + } + return null; + }; + Calendar.prototype.getEvents = function () { + var _a = this.state.eventStore, defs = _a.defs, instances = _a.instances; + var eventApis = []; + for (var id in instances) { + var instance = instances[id]; + var def = defs[instance.defId]; + eventApis.push(new EventApi(this, def, instance)); + } + return eventApis; + }; + Calendar.prototype.removeAllEvents = function () { + this.dispatch({ type: 'REMOVE_ALL_EVENTS' }); + }; + Calendar.prototype.rerenderEvents = function () { + this.dispatch({ type: 'RESET_EVENTS' }); + }; + // Public Event Sources API + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.getEventSources = function () { + var sourceHash = this.state.eventSources; + var sourceApis = []; + for (var internalId in sourceHash) { + sourceApis.push(new EventSourceApi(this, sourceHash[internalId])); + } + return sourceApis; + }; + Calendar.prototype.getEventSourceById = function (id) { + var sourceHash = this.state.eventSources; + id = String(id); + for (var sourceId in sourceHash) { + if (sourceHash[sourceId].publicId === id) { + return new EventSourceApi(this, sourceHash[sourceId]); + } + } + return null; + }; + Calendar.prototype.addEventSource = function (sourceInput) { + if (sourceInput instanceof EventSourceApi) { + // not already present? don't want to add an old snapshot + if (!this.state.eventSources[sourceInput.internalEventSource.sourceId]) { + this.dispatch({ + type: 'ADD_EVENT_SOURCES', + sources: [sourceInput.internalEventSource] + }); + } + return sourceInput; + } + var eventSource = parseEventSource(sourceInput, this); + if (eventSource) { // TODO: error otherwise? + this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] }); + return new EventSourceApi(this, eventSource); + } + return null; + }; + Calendar.prototype.removeAllEventSources = function () { + this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' }); + }; + Calendar.prototype.refetchEvents = function () { + this.dispatch({ type: 'FETCH_EVENT_SOURCES' }); + }; + return Calendar; + }()); + EmitterMixin.mixInto(Calendar); + // for memoizers + // ----------------------------------------------------------------------------------------------------------------- + function buildDateEnv(locale, timeZone, namedTimeZoneImpl, firstDay, weekNumberCalculation, weekLabel, cmdFormatter) { + return new DateEnv({ + calendarSystem: 'gregory', + timeZone: timeZone, + namedTimeZoneImpl: namedTimeZoneImpl, + locale: locale, + weekNumberCalculation: weekNumberCalculation, + firstDay: firstDay, + weekLabel: weekLabel, + cmdFormatter: cmdFormatter + }); + } + function buildTheme(calendarOptions) { + var themeClass = this.pluginSystem.hooks.themeClasses[calendarOptions.themeSystem] || StandardTheme; + return new themeClass(calendarOptions); + } + function buildDelayedRerender(wait) { + var func = this.tryRerender.bind(this); + if (wait != null) { + func = debounce(func, wait); + } + return func; + } + function buildEventUiBySource(eventSources) { + return mapHash(eventSources, function (eventSource) { + return eventSource.ui; + }); + } + function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) { + var eventUiBases = { '': eventUiSingleBase }; + for (var defId in eventDefs) { + var def = eventDefs[defId]; + if (def.sourceId && eventUiBySource[def.sourceId]) { + eventUiBases[defId] = eventUiBySource[def.sourceId]; + } + } + return eventUiBases; + } + + var View = /** @class */ (function (_super) { + __extends(View, _super); + function View(context, viewSpec, dateProfileGenerator, parentEl) { + var _this = _super.call(this, context, createElement('div', { className: 'fc-view fc-' + viewSpec.type + '-view' }), true // isView (HACK) + ) || this; + _this.renderDatesMem = memoizeRendering(_this.renderDatesWrap, _this.unrenderDatesWrap); + _this.renderBusinessHoursMem = memoizeRendering(_this.renderBusinessHours, _this.unrenderBusinessHours, [_this.renderDatesMem]); + _this.renderDateSelectionMem = memoizeRendering(_this.renderDateSelectionWrap, _this.unrenderDateSelectionWrap, [_this.renderDatesMem]); + _this.renderEventsMem = memoizeRendering(_this.renderEvents, _this.unrenderEvents, [_this.renderDatesMem]); + _this.renderEventSelectionMem = memoizeRendering(_this.renderEventSelectionWrap, _this.unrenderEventSelectionWrap, [_this.renderEventsMem]); + _this.renderEventDragMem = memoizeRendering(_this.renderEventDragWrap, _this.unrenderEventDragWrap, [_this.renderDatesMem]); + _this.renderEventResizeMem = memoizeRendering(_this.renderEventResizeWrap, _this.unrenderEventResizeWrap, [_this.renderDatesMem]); + _this.viewSpec = viewSpec; + _this.dateProfileGenerator = dateProfileGenerator; + _this.type = viewSpec.type; + _this.eventOrderSpecs = parseFieldSpecs(_this.opt('eventOrder')); + _this.nextDayThreshold = createDuration(_this.opt('nextDayThreshold')); + parentEl.appendChild(_this.el); + _this.initialize(); + return _this; + } + View.prototype.initialize = function () { + }; + Object.defineProperty(View.prototype, "activeStart", { + // Date Setting/Unsetting + // ----------------------------------------------------------------------------------------------------------------- + get: function () { + return this.dateEnv.toDate(this.props.dateProfile.activeRange.start); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(View.prototype, "activeEnd", { + get: function () { + return this.dateEnv.toDate(this.props.dateProfile.activeRange.end); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(View.prototype, "currentStart", { + get: function () { + return this.dateEnv.toDate(this.props.dateProfile.currentRange.start); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(View.prototype, "currentEnd", { + get: function () { + return this.dateEnv.toDate(this.props.dateProfile.currentRange.end); + }, + enumerable: true, + configurable: true + }); + // General Rendering + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.render = function (props) { + this.renderDatesMem(props.dateProfile); + this.renderBusinessHoursMem(props.businessHours); + this.renderDateSelectionMem(props.dateSelection); + this.renderEventsMem(props.eventStore); + this.renderEventSelectionMem(props.eventSelection); + this.renderEventDragMem(props.eventDrag); + this.renderEventResizeMem(props.eventResize); + }; + View.prototype.destroy = function () { + _super.prototype.destroy.call(this); + this.renderDatesMem.unrender(); // should unrender everything else + }; + // Sizing + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.updateSize = function (isResize, viewHeight, isAuto) { + var calendar = this.calendar; + if (isResize || calendar.isViewUpdated || calendar.isDatesUpdated || calendar.isEventsUpdated) { + // sort of the catch-all sizing + // anything that might cause dimension changes + this.updateBaseSize(isResize, viewHeight, isAuto); + } + }; + View.prototype.updateBaseSize = function (isResize, viewHeight, isAuto) { + }; + // Date Rendering + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.renderDatesWrap = function (dateProfile) { + this.renderDates(dateProfile); + this.addScroll({ isDateInit: true }); + this.startNowIndicator(dateProfile); // shouldn't render yet because updateSize will be called soon + }; + View.prototype.unrenderDatesWrap = function () { + this.stopNowIndicator(); + this.unrenderDates(); + }; + View.prototype.renderDates = function (dateProfile) { }; + View.prototype.unrenderDates = function () { }; + // Business Hours + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.renderBusinessHours = function (businessHours) { }; + View.prototype.unrenderBusinessHours = function () { }; + // Date Selection + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.renderDateSelectionWrap = function (selection) { + if (selection) { + this.renderDateSelection(selection); + } + }; + View.prototype.unrenderDateSelectionWrap = function (selection) { + if (selection) { + this.unrenderDateSelection(selection); + } + }; + View.prototype.renderDateSelection = function (selection) { }; + View.prototype.unrenderDateSelection = function (selection) { }; + // Event Rendering + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.renderEvents = function (eventStore) { }; + View.prototype.unrenderEvents = function () { }; + // util for subclasses + View.prototype.sliceEvents = function (eventStore, allDay) { + var props = this.props; + return sliceEventStore(eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? this.nextDayThreshold : null).fg; + }; + // Event Selection + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.renderEventSelectionWrap = function (instanceId) { + if (instanceId) { + this.renderEventSelection(instanceId); + } + }; + View.prototype.unrenderEventSelectionWrap = function (instanceId) { + if (instanceId) { + this.unrenderEventSelection(instanceId); + } + }; + View.prototype.renderEventSelection = function (instanceId) { }; + View.prototype.unrenderEventSelection = function (instanceId) { }; + // Event Drag + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.renderEventDragWrap = function (state) { + if (state) { + this.renderEventDrag(state); + } + }; + View.prototype.unrenderEventDragWrap = function (state) { + if (state) { + this.unrenderEventDrag(state); + } + }; + View.prototype.renderEventDrag = function (state) { }; + View.prototype.unrenderEventDrag = function (state) { }; + // Event Resize + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.renderEventResizeWrap = function (state) { + if (state) { + this.renderEventResize(state); + } + }; + View.prototype.unrenderEventResizeWrap = function (state) { + if (state) { + this.unrenderEventResize(state); + } + }; + View.prototype.renderEventResize = function (state) { }; + View.prototype.unrenderEventResize = function (state) { }; + /* Now Indicator + ------------------------------------------------------------------------------------------------------------------*/ + // Immediately render the current time indicator and begins re-rendering it at an interval, + // which is defined by this.getNowIndicatorUnit(). + // TODO: somehow do this for the current whole day's background too + View.prototype.startNowIndicator = function (dateProfile) { + var _this = this; + var dateEnv = this.dateEnv; + var unit; + var update; + var delay; // ms wait value + if (this.opt('nowIndicator')) { + unit = this.getNowIndicatorUnit(dateProfile); + if (unit) { + update = this.updateNowIndicator.bind(this); + this.initialNowDate = this.calendar.getNow(); + this.initialNowQueriedMs = new Date().valueOf(); + // wait until the beginning of the next interval + delay = dateEnv.add(dateEnv.startOf(this.initialNowDate, unit), createDuration(1, unit)).valueOf() - this.initialNowDate.valueOf(); + // TODO: maybe always use setTimeout, waiting until start of next unit + this.nowIndicatorTimeoutID = setTimeout(function () { + _this.nowIndicatorTimeoutID = null; + update(); + if (unit === 'second') { + delay = 1000; // every second + } + else { + delay = 1000 * 60; // otherwise, every minute + } + _this.nowIndicatorIntervalID = setInterval(update, delay); // update every interval + }, delay); + } + // rendering will be initiated in updateSize + } + }; + // rerenders the now indicator, computing the new current time from the amount of time that has passed + // since the initial getNow call. + View.prototype.updateNowIndicator = function () { + if (this.props.dateProfile && // a way to determine if dates were rendered yet + this.initialNowDate // activated before? + ) { + this.unrenderNowIndicator(); // won't unrender if unnecessary + this.renderNowIndicator(addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs)); + this.isNowIndicatorRendered = true; + } + }; + // Immediately unrenders the view's current time indicator and stops any re-rendering timers. + // Won't cause side effects if indicator isn't rendered. + View.prototype.stopNowIndicator = function () { + if (this.isNowIndicatorRendered) { + if (this.nowIndicatorTimeoutID) { + clearTimeout(this.nowIndicatorTimeoutID); + this.nowIndicatorTimeoutID = null; + } + if (this.nowIndicatorIntervalID) { + clearInterval(this.nowIndicatorIntervalID); + this.nowIndicatorIntervalID = null; + } + this.unrenderNowIndicator(); + this.isNowIndicatorRendered = false; + } + }; + View.prototype.getNowIndicatorUnit = function (dateProfile) { + // subclasses should implement + }; + // Renders a current time indicator at the given datetime + View.prototype.renderNowIndicator = function (date) { + // SUBCLASSES MUST PASS TO CHILDREN! + }; + // Undoes the rendering actions from renderNowIndicator + View.prototype.unrenderNowIndicator = function () { + // SUBCLASSES MUST PASS TO CHILDREN! + }; + /* Scroller + ------------------------------------------------------------------------------------------------------------------*/ + View.prototype.addScroll = function (scroll) { + var queuedScroll = this.queuedScroll || (this.queuedScroll = {}); + __assign(queuedScroll, scroll); + }; + View.prototype.popScroll = function (isResize) { + this.applyQueuedScroll(isResize); + this.queuedScroll = null; + }; + View.prototype.applyQueuedScroll = function (isResize) { + this.applyScroll(this.queuedScroll || {}, isResize); + }; + View.prototype.queryScroll = function () { + var scroll = {}; + if (this.props.dateProfile) { // dates rendered yet? + __assign(scroll, this.queryDateScroll()); + } + return scroll; + }; + View.prototype.applyScroll = function (scroll, isResize) { + if (scroll.isDateInit) { + delete scroll.isDateInit; + if (this.props.dateProfile) { // dates rendered yet? + __assign(scroll, this.computeInitialDateScroll()); + } + } + if (this.props.dateProfile) { // dates rendered yet? + this.applyDateScroll(scroll); + } + }; + View.prototype.computeInitialDateScroll = function () { + return {}; // subclasses must implement + }; + View.prototype.queryDateScroll = function () { + return {}; // subclasses must implement + }; + View.prototype.applyDateScroll = function (scroll) { + // subclasses must implement + }; + return View; + }(DateComponent)); + EmitterMixin.mixInto(View); + View.prototype.usesMinMaxTime = false; + View.prototype.dateProfileGeneratorClass = DateProfileGenerator; + + var FgEventRenderer = /** @class */ (function () { + function FgEventRenderer(context) { + this.segs = []; + this.isSizeDirty = false; + this.context = context; + } + FgEventRenderer.prototype.renderSegs = function (segs, mirrorInfo) { + this.rangeUpdated(); // called too frequently :( + // render an `.el` on each seg + // returns a subset of the segs. segs that were actually rendered + segs = this.renderSegEls(segs, mirrorInfo); + this.segs = segs; + this.attachSegs(segs, mirrorInfo); + this.isSizeDirty = true; + this.context.view.triggerRenderedSegs(this.segs, Boolean(mirrorInfo)); + }; + FgEventRenderer.prototype.unrender = function (_segs, mirrorInfo) { + this.context.view.triggerWillRemoveSegs(this.segs, Boolean(mirrorInfo)); + this.detachSegs(this.segs); + this.segs = []; + }; + // Updates values that rely on options and also relate to range + FgEventRenderer.prototype.rangeUpdated = function () { + var options = this.context.options; + var displayEventTime; + var displayEventEnd; + this.eventTimeFormat = createFormatter(options.eventTimeFormat || this.computeEventTimeFormat(), options.defaultRangeSeparator); + displayEventTime = options.displayEventTime; + if (displayEventTime == null) { + displayEventTime = this.computeDisplayEventTime(); // might be based off of range + } + displayEventEnd = options.displayEventEnd; + if (displayEventEnd == null) { + displayEventEnd = this.computeDisplayEventEnd(); // might be based off of range + } + this.displayEventTime = displayEventTime; + this.displayEventEnd = displayEventEnd; + }; + // Renders and assigns an `el` property for each foreground event segment. + // Only returns segments that successfully rendered. + FgEventRenderer.prototype.renderSegEls = function (segs, mirrorInfo) { + var html = ''; + var i; + if (segs.length) { // don't build an empty html string + // build a large concatenation of event segment HTML + for (i = 0; i < segs.length; i++) { + html += this.renderSegHtml(segs[i], mirrorInfo); + } + // Grab individual elements from the combined HTML string. Use each as the default rendering. + // Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false. + htmlToElements(html).forEach(function (el, i) { + var seg = segs[i]; + if (el) { + seg.el = el; + } + }); + segs = filterSegsViaEls(this.context.view, segs, Boolean(mirrorInfo)); + } + return segs; + }; + // Generic utility for generating the HTML classNames for an event segment's element + FgEventRenderer.prototype.getSegClasses = function (seg, isDraggable, isResizable, mirrorInfo) { + var classes = [ + 'fc-event', + seg.isStart ? 'fc-start' : 'fc-not-start', + seg.isEnd ? 'fc-end' : 'fc-not-end' + ].concat(seg.eventRange.ui.classNames); + if (isDraggable) { + classes.push('fc-draggable'); + } + if (isResizable) { + classes.push('fc-resizable'); + } + if (mirrorInfo) { + classes.push('fc-mirror'); + if (mirrorInfo.isDragging) { + classes.push('fc-dragging'); + } + if (mirrorInfo.isResizing) { + classes.push('fc-resizing'); + } + } + return classes; + }; + // Compute the text that should be displayed on an event's element. + // `range` can be the Event object itself, or something range-like, with at least a `start`. + // If event times are disabled, or the event has no time, will return a blank string. + // If not specified, formatter will default to the eventTimeFormat setting, + // and displayEnd will default to the displayEventEnd setting. + FgEventRenderer.prototype.getTimeText = function (eventRange, formatter, displayEnd) { + var def = eventRange.def, instance = eventRange.instance; + return this._getTimeText(instance.range.start, def.hasEnd ? instance.range.end : null, def.allDay, formatter, displayEnd, instance.forcedStartTzo, instance.forcedEndTzo); + }; + FgEventRenderer.prototype._getTimeText = function (start, end, allDay, formatter, displayEnd, forcedStartTzo, forcedEndTzo) { + var dateEnv = this.context.dateEnv; + if (formatter == null) { + formatter = this.eventTimeFormat; + } + if (displayEnd == null) { + displayEnd = this.displayEventEnd; + } + if (this.displayEventTime && !allDay) { + if (displayEnd && end) { + return dateEnv.formatRange(start, end, formatter, { + forcedStartTzo: forcedStartTzo, + forcedEndTzo: forcedEndTzo + }); + } + else { + return dateEnv.format(start, formatter, { + forcedTzo: forcedStartTzo + }); + } + } + return ''; + }; + FgEventRenderer.prototype.computeEventTimeFormat = function () { + return { + hour: 'numeric', + minute: '2-digit', + omitZeroMinute: true + }; + }; + FgEventRenderer.prototype.computeDisplayEventTime = function () { + return true; + }; + FgEventRenderer.prototype.computeDisplayEventEnd = function () { + return true; + }; + // Utility for generating event skin-related CSS properties + FgEventRenderer.prototype.getSkinCss = function (ui) { + return { + 'background-color': ui.backgroundColor, + 'border-color': ui.borderColor, + color: ui.textColor + }; + }; + FgEventRenderer.prototype.sortEventSegs = function (segs) { + var specs = this.context.view.eventOrderSpecs; + var objs = segs.map(buildSegCompareObj); + objs.sort(function (obj0, obj1) { + return compareByFieldSpecs(obj0, obj1, specs); + }); + return objs.map(function (c) { + return c._seg; + }); + }; + FgEventRenderer.prototype.computeSizes = function (force) { + if (force || this.isSizeDirty) { + this.computeSegSizes(this.segs); + } + }; + FgEventRenderer.prototype.assignSizes = function (force) { + if (force || this.isSizeDirty) { + this.assignSegSizes(this.segs); + this.isSizeDirty = false; + } + }; + FgEventRenderer.prototype.computeSegSizes = function (segs) { + }; + FgEventRenderer.prototype.assignSegSizes = function (segs) { + }; + // Manipulation on rendered segs + FgEventRenderer.prototype.hideByHash = function (hash) { + if (hash) { + for (var _i = 0, _a = this.segs; _i < _a.length; _i++) { + var seg = _a[_i]; + if (hash[seg.eventRange.instance.instanceId]) { + seg.el.style.visibility = 'hidden'; + } + } + } + }; + FgEventRenderer.prototype.showByHash = function (hash) { + if (hash) { + for (var _i = 0, _a = this.segs; _i < _a.length; _i++) { + var seg = _a[_i]; + if (hash[seg.eventRange.instance.instanceId]) { + seg.el.style.visibility = ''; + } + } + } + }; + FgEventRenderer.prototype.selectByInstanceId = function (instanceId) { + if (instanceId) { + for (var _i = 0, _a = this.segs; _i < _a.length; _i++) { + var seg = _a[_i]; + var eventInstance = seg.eventRange.instance; + if (eventInstance && eventInstance.instanceId === instanceId && + seg.el // necessary? + ) { + seg.el.classList.add('fc-selected'); + } + } + } + }; + FgEventRenderer.prototype.unselectByInstanceId = function (instanceId) { + if (instanceId) { + for (var _i = 0, _a = this.segs; _i < _a.length; _i++) { + var seg = _a[_i]; + if (seg.el) { // necessary? + seg.el.classList.remove('fc-selected'); + } + } + } + }; + return FgEventRenderer; + }()); + // returns a object with all primitive props that can be compared + function buildSegCompareObj(seg) { + var eventDef = seg.eventRange.def; + var range = seg.eventRange.instance.range; + var start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events + var end = range.end ? range.end.valueOf() : 0; // " + return __assign({}, eventDef.extendedProps, eventDef, { id: eventDef.publicId, start: start, + end: end, duration: end - start, allDay: Number(eventDef.allDay), _seg: seg // for later retrieval + }); + } + + var FillRenderer = /** @class */ (function () { + function FillRenderer(context) { + this.fillSegTag = 'div'; + this.dirtySizeFlags = {}; + this.context = context; + this.containerElsByType = {}; + this.segsByType = {}; + } + FillRenderer.prototype.getSegsByType = function (type) { + return this.segsByType[type] || []; + }; + FillRenderer.prototype.renderSegs = function (type, segs) { + var _a; + var renderedSegs = this.renderSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs + var containerEls = this.attachSegs(type, renderedSegs); + if (containerEls) { + (_a = (this.containerElsByType[type] || (this.containerElsByType[type] = []))).push.apply(_a, containerEls); + } + this.segsByType[type] = renderedSegs; + if (type === 'bgEvent') { + this.context.view.triggerRenderedSegs(renderedSegs, false); // isMirror=false + } + this.dirtySizeFlags[type] = true; + }; + // Unrenders a specific type of fill that is currently rendered on the grid + FillRenderer.prototype.unrender = function (type) { + var segs = this.segsByType[type]; + if (segs) { + if (type === 'bgEvent') { + this.context.view.triggerWillRemoveSegs(segs, false); // isMirror=false + } + this.detachSegs(type, segs); + } + }; + // Renders and assigns an `el` property for each fill segment. Generic enough to work with different types. + // Only returns segments that successfully rendered. + FillRenderer.prototype.renderSegEls = function (type, segs) { + var _this = this; + var html = ''; + var i; + if (segs.length) { + // build a large concatenation of segment HTML + for (i = 0; i < segs.length; i++) { + html += this.renderSegHtml(type, segs[i]); + } + // Grab individual elements from the combined HTML string. Use each as the default rendering. + // Then, compute the 'el' for each segment. + htmlToElements(html).forEach(function (el, i) { + var seg = segs[i]; + if (el) { + seg.el = el; + } + }); + if (type === 'bgEvent') { + segs = filterSegsViaEls(this.context.view, segs, false // isMirror. background events can never be mirror elements + ); + } + // correct element type? (would be bad if a non-TD were inserted into a table for example) + segs = segs.filter(function (seg) { + return elementMatches(seg.el, _this.fillSegTag); + }); + } + return segs; + }; + // Builds the HTML needed for one fill segment. Generic enough to work with different types. + FillRenderer.prototype.renderSegHtml = function (type, seg) { + var css = null; + var classNames = []; + if (type !== 'highlight' && type !== 'businessHours') { + css = { + 'background-color': seg.eventRange.ui.backgroundColor + }; + } + if (type !== 'highlight') { + classNames = classNames.concat(seg.eventRange.ui.classNames); + } + if (type === 'businessHours') { + classNames.push('fc-bgevent'); + } + else { + classNames.push('fc-' + type.toLowerCase()); + } + return '<' + this.fillSegTag + + (classNames.length ? ' class="' + classNames.join(' ') + '"' : '') + + (css ? ' style="' + cssToStr(css) + '"' : '') + + '>'; + }; + FillRenderer.prototype.detachSegs = function (type, segs) { + var containerEls = this.containerElsByType[type]; + if (containerEls) { + containerEls.forEach(removeElement); + delete this.containerElsByType[type]; + } + }; + FillRenderer.prototype.computeSizes = function (force) { + for (var type in this.segsByType) { + if (force || this.dirtySizeFlags[type]) { + this.computeSegSizes(this.segsByType[type]); + } + } + }; + FillRenderer.prototype.assignSizes = function (force) { + for (var type in this.segsByType) { + if (force || this.dirtySizeFlags[type]) { + this.assignSegSizes(this.segsByType[type]); + } + } + this.dirtySizeFlags = {}; + }; + FillRenderer.prototype.computeSegSizes = function (segs) { + }; + FillRenderer.prototype.assignSegSizes = function (segs) { + }; + return FillRenderer; + }()); + + var NamedTimeZoneImpl = /** @class */ (function () { + function NamedTimeZoneImpl(timeZoneName) { + this.timeZoneName = timeZoneName; + } + return NamedTimeZoneImpl; + }()); + + /* + An abstraction for a dragging interaction originating on an event. + Does higher-level things than PointerDragger, such as possibly: + - a "mirror" that moves with the pointer + - a minimum number of pixels or other criteria for a true drag to begin + + subclasses must emit: + - pointerdown + - dragstart + - dragmove + - pointerup + - dragend + */ + var ElementDragging = /** @class */ (function () { + function ElementDragging(el) { + this.emitter = new EmitterMixin(); + } + ElementDragging.prototype.destroy = function () { + }; + ElementDragging.prototype.setMirrorIsVisible = function (bool) { + // optional if subclass doesn't want to support a mirror + }; + ElementDragging.prototype.setMirrorNeedsRevert = function (bool) { + // optional if subclass doesn't want to support a mirror + }; + ElementDragging.prototype.setAutoScrollEnabled = function (bool) { + // optional + }; + return ElementDragging; + }()); + + function formatDate(dateInput, settings) { + if (settings === void 0) { settings = {}; } + var dateEnv = buildDateEnv$1(settings); + var formatter = createFormatter(settings); + var dateMeta = dateEnv.createMarkerMeta(dateInput); + if (!dateMeta) { // TODO: warning? + return ''; + } + return dateEnv.format(dateMeta.marker, formatter, { + forcedTzo: dateMeta.forcedTzo + }); + } + function formatRange(startInput, endInput, settings // mixture of env and formatter settings + ) { + var dateEnv = buildDateEnv$1(typeof settings === 'object' && settings ? settings : {}); // pass in if non-null object + var formatter = createFormatter(settings, globalDefaults.defaultRangeSeparator); + var startMeta = dateEnv.createMarkerMeta(startInput); + var endMeta = dateEnv.createMarkerMeta(endInput); + if (!startMeta || !endMeta) { // TODO: warning? + return ''; + } + return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, { + forcedStartTzo: startMeta.forcedTzo, + forcedEndTzo: endMeta.forcedTzo, + isEndExclusive: settings.isEndExclusive + }); + } + // TODO: more DRY and optimized + function buildDateEnv$1(settings) { + var locale = buildLocale(settings.locale || 'en', parseRawLocales([]).map); // TODO: don't hardcode 'en' everywhere + // ensure required settings + settings = __assign({ timeZone: globalDefaults.timeZone, calendarSystem: 'gregory' }, settings, { locale: locale }); + return new DateEnv(settings); + } + + var DRAG_META_PROPS = { + startTime: createDuration, + duration: createDuration, + create: Boolean, + sourceId: String + }; + var DRAG_META_DEFAULTS = { + create: true + }; + function parseDragMeta(raw) { + var leftoverProps = {}; + var refined = refineProps(raw, DRAG_META_PROPS, DRAG_META_DEFAULTS, leftoverProps); + refined.leftoverProps = leftoverProps; + return refined; + } + + // Computes a default column header formatting string if `colFormat` is not explicitly defined + function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) { + // if more than one week row, or if there are a lot of columns with not much space, + // put just the day numbers will be in each cell + if (!datesRepDistinctDays || dayCnt > 10) { + return { weekday: 'short' }; // "Sat" + } + else if (dayCnt > 1) { + return { weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }; // "Sat 11/12" + } + else { + return { weekday: 'long' }; // "Saturday" + } + } + function renderDateCell(dateMarker, dateProfile, datesRepDistinctDays, colCnt, colHeadFormat, context, colspan, otherAttrs) { + var view = context.view, dateEnv = context.dateEnv, theme = context.theme, options = context.options; + var isDateValid = rangeContainsMarker(dateProfile.activeRange, dateMarker); // TODO: called too frequently. cache somehow. + var classNames = [ + 'fc-day-header', + theme.getClass('widgetHeader') + ]; + var innerHtml; + if (typeof options.columnHeaderHtml === 'function') { + innerHtml = options.columnHeaderHtml(dateEnv.toDate(dateMarker)); + } + else if (typeof options.columnHeaderText === 'function') { + innerHtml = htmlEscape(options.columnHeaderText(dateEnv.toDate(dateMarker))); + } + else { + innerHtml = htmlEscape(dateEnv.format(dateMarker, colHeadFormat)); + } + // if only one row of days, the classNames on the header can represent the specific days beneath + if (datesRepDistinctDays) { + classNames = classNames.concat( + // includes the day-of-week class + // noThemeHighlight=true (don't highlight the header) + getDayClasses(dateMarker, dateProfile, context, true)); + } + else { + classNames.push('fc-' + DAY_IDS[dateMarker.getUTCDay()]); // only add the day-of-week class + } + return '' + + ' 1 ? + ' colspan="' + colspan + '"' : + '') + + (otherAttrs ? + ' ' + otherAttrs : + '') + + '>' + + (isDateValid ? + // don't make a link if the heading could represent multiple days, or if there's only one day (forceOff) + buildGotoAnchorHtml(view, { date: dateMarker, forceOff: !datesRepDistinctDays || colCnt === 1 }, innerHtml) : + // if not valid, display text, but no link + innerHtml) + + ''; + } + + var DayHeader = /** @class */ (function (_super) { + __extends(DayHeader, _super); + function DayHeader(context, parentEl) { + var _this = _super.call(this, context) || this; + parentEl.innerHTML = ''; // because might be nbsp + parentEl.appendChild(_this.el = htmlToElement('
' + + '' + + '' + + '
' + + '
')); + _this.thead = _this.el.querySelector('thead'); + return _this; + } + DayHeader.prototype.destroy = function () { + removeElement(this.el); + }; + DayHeader.prototype.render = function (props) { + var dates = props.dates, datesRepDistinctDays = props.datesRepDistinctDays; + var parts = []; + if (props.renderIntroHtml) { + parts.push(props.renderIntroHtml()); + } + var colHeadFormat = createFormatter(this.opt('columnHeaderFormat') || + computeFallbackHeaderFormat(datesRepDistinctDays, dates.length)); + for (var _i = 0, dates_1 = dates; _i < dates_1.length; _i++) { + var date = dates_1[_i]; + parts.push(renderDateCell(date, props.dateProfile, datesRepDistinctDays, dates.length, colHeadFormat, this.context)); + } + if (this.isRtl) { + parts.reverse(); + } + this.thead.innerHTML = '' + parts.join('') + ''; + }; + return DayHeader; + }(Component)); + + var DaySeries = /** @class */ (function () { + function DaySeries(range, dateProfileGenerator) { + var date = range.start; + var end = range.end; + var indices = []; + var dates = []; + var dayIndex = -1; + while (date < end) { // loop each day from start to end + if (dateProfileGenerator.isHiddenDay(date)) { + indices.push(dayIndex + 0.5); // mark that it's between indices + } + else { + dayIndex++; + indices.push(dayIndex); + dates.push(date); + } + date = addDays(date, 1); + } + this.dates = dates; + this.indices = indices; + this.cnt = dates.length; + } + DaySeries.prototype.sliceRange = function (range) { + var firstIndex = this.getDateDayIndex(range.start); // inclusive first index + var lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index + var clippedFirstIndex = Math.max(0, firstIndex); + var clippedLastIndex = Math.min(this.cnt - 1, lastIndex); + // deal with in-between indices + clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell + clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell + if (clippedFirstIndex <= clippedLastIndex) { + return { + firstIndex: clippedFirstIndex, + lastIndex: clippedLastIndex, + isStart: firstIndex === clippedFirstIndex, + isEnd: lastIndex === clippedLastIndex + }; + } + else { + return null; + } + }; + // Given a date, returns its chronolocial cell-index from the first cell of the grid. + // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets. + // If before the first offset, returns a negative number. + // If after the last offset, returns an offset past the last cell offset. + // Only works for *start* dates of cells. Will not work for exclusive end dates for cells. + DaySeries.prototype.getDateDayIndex = function (date) { + var indices = this.indices; + var dayOffset = Math.floor(diffDays(this.dates[0], date)); + if (dayOffset < 0) { + return indices[0] - 1; + } + else if (dayOffset >= indices.length) { + return indices[indices.length - 1] + 1; + } + else { + return indices[dayOffset]; + } + }; + return DaySeries; + }()); + + var DayTable = /** @class */ (function () { + function DayTable(daySeries, breakOnWeeks) { + var dates = daySeries.dates; + var daysPerRow; + var firstDay; + var rowCnt; + if (breakOnWeeks) { + // count columns until the day-of-week repeats + firstDay = dates[0].getUTCDay(); + for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow++) { + if (dates[daysPerRow].getUTCDay() === firstDay) { + break; + } + } + rowCnt = Math.ceil(dates.length / daysPerRow); + } + else { + rowCnt = 1; + daysPerRow = dates.length; + } + this.rowCnt = rowCnt; + this.colCnt = daysPerRow; + this.daySeries = daySeries; + this.cells = this.buildCells(); + this.headerDates = this.buildHeaderDates(); + } + DayTable.prototype.buildCells = function () { + var rows = []; + for (var row = 0; row < this.rowCnt; row++) { + var cells = []; + for (var col = 0; col < this.colCnt; col++) { + cells.push(this.buildCell(row, col)); + } + rows.push(cells); + } + return rows; + }; + DayTable.prototype.buildCell = function (row, col) { + return { + date: this.daySeries.dates[row * this.colCnt + col] + }; + }; + DayTable.prototype.buildHeaderDates = function () { + var dates = []; + for (var col = 0; col < this.colCnt; col++) { + dates.push(this.cells[0][col].date); + } + return dates; + }; + DayTable.prototype.sliceRange = function (range) { + var colCnt = this.colCnt; + var seriesSeg = this.daySeries.sliceRange(range); + var segs = []; + if (seriesSeg) { + var firstIndex = seriesSeg.firstIndex, lastIndex = seriesSeg.lastIndex; + var index = firstIndex; + while (index <= lastIndex) { + var row = Math.floor(index / colCnt); + var nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1); + segs.push({ + row: row, + firstCol: index % colCnt, + lastCol: (nextIndex - 1) % colCnt, + isStart: seriesSeg.isStart && index === firstIndex, + isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex + }); + index = nextIndex; + } + } + return segs; + }; + return DayTable; + }()); + + var Slicer = /** @class */ (function () { + function Slicer() { + this.sliceBusinessHours = memoize(this._sliceBusinessHours); + this.sliceDateSelection = memoize(this._sliceDateSpan); + this.sliceEventStore = memoize(this._sliceEventStore); + this.sliceEventDrag = memoize(this._sliceInteraction); + this.sliceEventResize = memoize(this._sliceInteraction); + } + Slicer.prototype.sliceProps = function (props, dateProfile, nextDayThreshold, component) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + var eventUiBases = props.eventUiBases; + var eventSegs = this.sliceEventStore.apply(this, [props.eventStore, eventUiBases, dateProfile, nextDayThreshold, component].concat(extraArgs)); + return { + dateSelectionSegs: this.sliceDateSelection.apply(this, [props.dateSelection, eventUiBases, component].concat(extraArgs)), + businessHourSegs: this.sliceBusinessHours.apply(this, [props.businessHours, dateProfile, nextDayThreshold, component].concat(extraArgs)), + fgEventSegs: eventSegs.fg, + bgEventSegs: eventSegs.bg, + eventDrag: this.sliceEventDrag.apply(this, [props.eventDrag, eventUiBases, dateProfile, nextDayThreshold, component].concat(extraArgs)), + eventResize: this.sliceEventResize.apply(this, [props.eventResize, eventUiBases, dateProfile, nextDayThreshold, component].concat(extraArgs)), + eventSelection: props.eventSelection + }; // TODO: give interactionSegs? + }; + Slicer.prototype.sliceNowDate = function (// does not memoize + date, component) { + var extraArgs = []; + for (var _i = 2; _i < arguments.length; _i++) { + extraArgs[_i - 2] = arguments[_i]; + } + return this._sliceDateSpan.apply(this, [{ range: { start: date, end: addMs(date, 1) }, allDay: false }, + {}, + component].concat(extraArgs)); + }; + Slicer.prototype._sliceBusinessHours = function (businessHours, dateProfile, nextDayThreshold, component) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (!businessHours) { + return []; + } + return this._sliceEventStore.apply(this, [expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), component.calendar), + {}, + dateProfile, + nextDayThreshold, + component].concat(extraArgs)).bg; + }; + Slicer.prototype._sliceEventStore = function (eventStore, eventUiBases, dateProfile, nextDayThreshold, component) { + var extraArgs = []; + for (var _i = 5; _i < arguments.length; _i++) { + extraArgs[_i - 5] = arguments[_i]; + } + if (eventStore) { + var rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + bg: this.sliceEventRanges(rangeRes.bg, component, extraArgs), + fg: this.sliceEventRanges(rangeRes.fg, component, extraArgs) + }; + } + else { + return { bg: [], fg: [] }; + } + }; + Slicer.prototype._sliceInteraction = function (interaction, eventUiBases, dateProfile, nextDayThreshold, component) { + var extraArgs = []; + for (var _i = 5; _i < arguments.length; _i++) { + extraArgs[_i - 5] = arguments[_i]; + } + if (!interaction) { + return null; + } + var rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + segs: this.sliceEventRanges(rangeRes.fg, component, extraArgs), + affectedInstances: interaction.affectedEvents.instances, + isEvent: interaction.isEvent, + sourceSeg: interaction.origSeg + }; + }; + Slicer.prototype._sliceDateSpan = function (dateSpan, eventUiBases, component) { + var extraArgs = []; + for (var _i = 3; _i < arguments.length; _i++) { + extraArgs[_i - 3] = arguments[_i]; + } + if (!dateSpan) { + return []; + } + var eventRange = fabricateEventRange(dateSpan, eventUiBases, component.calendar); + var segs = this.sliceRange.apply(this, [dateSpan.range].concat(extraArgs)); + for (var _a = 0, segs_1 = segs; _a < segs_1.length; _a++) { + var seg = segs_1[_a]; + seg.component = component; + seg.eventRange = eventRange; + } + return segs; + }; + /* + "complete" seg means it has component and eventRange + */ + Slicer.prototype.sliceEventRanges = function (eventRanges, component, // TODO: kill + extraArgs) { + var segs = []; + for (var _i = 0, eventRanges_1 = eventRanges; _i < eventRanges_1.length; _i++) { + var eventRange = eventRanges_1[_i]; + segs.push.apply(segs, this.sliceEventRange(eventRange, component, extraArgs)); + } + return segs; + }; + /* + "complete" seg means it has component and eventRange + */ + Slicer.prototype.sliceEventRange = function (eventRange, component, // TODO: kill + extraArgs) { + var segs = this.sliceRange.apply(this, [eventRange.range].concat(extraArgs)); + for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) { + var seg = segs_2[_i]; + seg.component = component; + seg.eventRange = eventRange; + seg.isStart = eventRange.isStart && seg.isStart; + seg.isEnd = eventRange.isEnd && seg.isEnd; + } + return segs; + }; + return Slicer; + }()); + /* + for incorporating minTime/maxTime if appropriate + TODO: should be part of DateProfile! + TimelineDateProfile already does this btw + */ + function computeActiveRange(dateProfile, isComponentAllDay) { + var range = dateProfile.activeRange; + if (isComponentAllDay) { + return range; + } + return { + start: addMs(range.start, dateProfile.minTime.milliseconds), + end: addMs(range.end, dateProfile.maxTime.milliseconds - 864e5) // 864e5 = ms in a day + }; + } + + // exports + // -------------------------------------------------------------------------------------------------- + var version = '4.0.2'; + + exports.Calendar = Calendar; + exports.Component = Component; + exports.DateComponent = DateComponent; + exports.DateEnv = DateEnv; + exports.DateProfileGenerator = DateProfileGenerator; + exports.DayHeader = DayHeader; + exports.DaySeries = DaySeries; + exports.DayTable = DayTable; + exports.ElementDragging = ElementDragging; + exports.ElementScrollController = ElementScrollController; + exports.EmitterMixin = EmitterMixin; + exports.EventApi = EventApi; + exports.FgEventRenderer = FgEventRenderer; + exports.FillRenderer = FillRenderer; + exports.Interaction = Interaction; + exports.Mixin = Mixin; + exports.NamedTimeZoneImpl = NamedTimeZoneImpl; + exports.PositionCache = PositionCache; + exports.ScrollComponent = ScrollComponent; + exports.ScrollController = ScrollController; + exports.Slicer = Slicer; + exports.Splitter = Splitter; + exports.Theme = Theme; + exports.View = View; + exports.WindowScrollController = WindowScrollController; + exports.addDays = addDays; + exports.addDurations = addDurations; + exports.addMs = addMs; + exports.addWeeks = addWeeks; + exports.allowContextMenu = allowContextMenu; + exports.allowSelection = allowSelection; + exports.appendToElement = appendToElement; + exports.applyAll = applyAll; + exports.applyMutationToEventStore = applyMutationToEventStore; + exports.applyStyle = applyStyle; + exports.applyStyleProp = applyStyleProp; + exports.asRoughMinutes = asRoughMinutes; + exports.asRoughMs = asRoughMs; + exports.asRoughSeconds = asRoughSeconds; + exports.buildGotoAnchorHtml = buildGotoAnchorHtml; + exports.buildSegCompareObj = buildSegCompareObj; + exports.capitaliseFirstLetter = capitaliseFirstLetter; + exports.combineEventUis = combineEventUis; + exports.compareByFieldSpec = compareByFieldSpec; + exports.compareByFieldSpecs = compareByFieldSpecs; + exports.compareNumbers = compareNumbers; + exports.compensateScroll = compensateScroll; + exports.computeClippingRect = computeClippingRect; + exports.computeEdges = computeEdges; + exports.computeFallbackHeaderFormat = computeFallbackHeaderFormat; + exports.computeHeightAndMargins = computeHeightAndMargins; + exports.computeInnerRect = computeInnerRect; + exports.computeRect = computeRect; + exports.computeVisibleDayRange = computeVisibleDayRange; + exports.config = config; + exports.constrainPoint = constrainPoint; + exports.createDuration = createDuration; + exports.createElement = createElement; + exports.createEmptyEventStore = createEmptyEventStore; + exports.createEventInstance = createEventInstance; + exports.createFormatter = createFormatter; + exports.createPlugin = createPlugin; + exports.cssToStr = cssToStr; + exports.debounce = debounce; + exports.diffDates = diffDates; + exports.diffDayAndTime = diffDayAndTime; + exports.diffDays = diffDays; + exports.diffPoints = diffPoints; + exports.diffWeeks = diffWeeks; + exports.diffWholeDays = diffWholeDays; + exports.diffWholeWeeks = diffWholeWeeks; + exports.disableCursor = disableCursor; + exports.distributeHeight = distributeHeight; + exports.elementClosest = elementClosest; + exports.elementMatches = elementMatches; + exports.enableCursor = enableCursor; + exports.eventTupleToStore = eventTupleToStore; + exports.filterEventStoreDefs = filterEventStoreDefs; + exports.filterHash = filterHash; + exports.findChildren = findChildren; + exports.findElements = findElements; + exports.flexibleCompare = flexibleCompare; + exports.forceClassName = forceClassName; + exports.formatDate = formatDate; + exports.formatIsoTimeString = formatIsoTimeString; + exports.formatRange = formatRange; + exports.freezeRaw = freezeRaw; + exports.getAllDayHtml = getAllDayHtml; + exports.getClippingParents = getClippingParents; + exports.getDayClasses = getDayClasses; + exports.getElSeg = getElSeg; + exports.getRectCenter = getRectCenter; + exports.getRelevantEvents = getRelevantEvents; + exports.globalDefaults = globalDefaults; + exports.greatestDurationDenominator = greatestDurationDenominator; + exports.hasBgRendering = hasBgRendering; + exports.htmlEscape = htmlEscape; + exports.htmlToElement = htmlToElement; + exports.insertAfterElement = insertAfterElement; + exports.interactionSettingsStore = interactionSettingsStore; + exports.interactionSettingsToStore = interactionSettingsToStore; + exports.intersectRanges = intersectRanges; + exports.intersectRects = intersectRects; + exports.isArraysEqual = isArraysEqual; + exports.isDateSpansEqual = isDateSpansEqual; + exports.isInt = isInt; + exports.isInteractionValid = isInteractionValid; + exports.isMultiDayRange = isMultiDayRange; + exports.isObjectsSimilar = isObjectsSimilar; + exports.isPropsValid = isPropsValid; + exports.isSingleDay = isSingleDay; + exports.isValidDate = isValidDate; + exports.isValuesSimilar = isValuesSimilar; + exports.listenBySelector = listenBySelector; + exports.mapHash = mapHash; + exports.matchCellWidths = matchCellWidths; + exports.memoize = memoize; + exports.memoizeOutput = memoizeOutput; + exports.memoizeRendering = memoizeRendering; + exports.mergeEventStores = mergeEventStores; + exports.multiplyDuration = multiplyDuration; + exports.padStart = padStart; + exports.parseBusinessHours = parseBusinessHours; + exports.parseDragMeta = parseDragMeta; + exports.parseEventDef = parseEventDef; + exports.parseFieldSpecs = parseFieldSpecs; + exports.parseMarker = parse; + exports.pointInsideRect = pointInsideRect; + exports.prependToElement = prependToElement; + exports.preventContextMenu = preventContextMenu; + exports.preventDefault = preventDefault; + exports.preventSelection = preventSelection; + exports.processScopedUiProps = processScopedUiProps; + exports.rangeContainsMarker = rangeContainsMarker; + exports.rangeContainsRange = rangeContainsRange; + exports.rangesEqual = rangesEqual; + exports.rangesIntersect = rangesIntersect; + exports.refineProps = refineProps; + exports.removeElement = removeElement; + exports.removeExact = removeExact; + exports.renderDateCell = renderDateCell; + exports.requestJson = requestJson; + exports.sliceEventStore = sliceEventStore; + exports.startOfDay = startOfDay; + exports.subtractInnerElHeight = subtractInnerElHeight; + exports.translateRect = translateRect; + exports.uncompensateScroll = uncompensateScroll; + exports.undistributeHeight = undistributeHeight; + exports.unpromisify = unpromisify; + exports.version = version; + exports.whenTransitionDone = whenTransitionDone; + exports.wholeDivideDurations = wholeDivideDurations; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/library/fullcalendar/packages/core/main.min.css b/library/fullcalendar/packages/core/main.min.css new file mode 100644 index 000000000..3ac6b3e2f --- /dev/null +++ b/library/fullcalendar/packages/core/main.min.css @@ -0,0 +1,5 @@ +/*! +FullCalendar Core Package v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/.fc-button:not(:disabled),.fc-event.fc-draggable,.fc-event[href],.fc-popover .fc-header .fc-close,a.fc-more,a[data-goto]{cursor:pointer}.fc-bg,.fc-row .fc-bgevent-skeleton,.fc-row .fc-highlight-skeleton{bottom:0}.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}body .fc{font-size:1em}.fc-highlight{background:#bce8f1;opacity:.3}.fc-bgevent{background:#8fdf82;opacity:.3}.fc-nonbusiness{background:#d7d7d7}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding:2px 4px}.fc-rtl .fc-popover .fc-header{flex-direction:row-reverse}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-popover .fc-header .fc-close{opacity:.65;font-size:1.1em}.fc-divider{border-style:solid;border-width:1px}hr.fc-divider{height:0;margin:0;padding:0 0 2px;border-width:1px 0}.fc-bg table,.fc-row .fc-bgevent-skeleton table,.fc-row .fc-highlight-skeleton table{height:100%}.fc-bg,.fc-bgevent-skeleton,.fc-highlight-skeleton,.fc-mirror-skeleton{position:absolute;top:0;left:0;right:0}.fc table{width:100%;box-sizing:border-box;table-layout:fixed;border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}a[data-goto]:hover{text-decoration:underline}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-bgevent-skeleton td,.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-bgevent-skeleton{z-index:2}.fc-row .fc-highlight-skeleton{z-index:3}.fc-row .fc-content-skeleton{position:relative;z-index:4;padding-bottom:2px}.fc-row .fc-mirror-skeleton{z-index:5}.fc .fc-row .fc-content-skeleton table,.fc .fc-row .fc-content-skeleton td,.fc .fc-row .fc-mirror-skeleton td{background:0 0;border-color:transparent}.fc-row .fc-content-skeleton td,.fc-row .fc-mirror-skeleton td{border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-mirror-skeleton tbody td{border-top:0}.fc-scroller{-webkit-overflow-scrolling:touch}.fc-scroller>.fc-day-grid,.fc-scroller>.fc-time-grid{position:relative;width:100%}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.4;border-radius:3px;border:1px solid #3788d8}.fc-event,.fc-event-dot{background-color:#3788d8}.fc-event,.fc-event:hover{color:#fff;text-decoration:none}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc-event .fc-content{position:relative;z-index:2}.fc-event .fc-resizer{position:absolute;z-index:4;display:none}.fc-event.fc-allow-mouse-resize .fc-resizer,.fc-event.fc-selected .fc-resizer{display:block}.fc-event.fc-selected .fc-resizer:before{content:"";position:absolute;z-index:9999;top:50%;left:50%;width:40px;height:40px;margin-left:-20px;margin-top:-20px}.fc-event.fc-selected{z-index:9999!important;box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event.fc-selected:after{content:"";position:absolute;z-index:1;top:-1px;right:-1px;bottom:-1px;left:-1px;background:#000;opacity:.25}.fc-event.fc-dragging.fc-selected{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-event.fc-dragging:not(.fc-selected){opacity:.75}.fc-h-event.fc-selected:before{content:"";position:absolute;z-index:3;top:-10px;bottom:-10px;left:0;right:0}.fc-ltr .fc-h-event.fc-not-start,.fc-rtl .fc-h-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-h-event.fc-not-end,.fc-rtl .fc-h-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-ltr .fc-h-event .fc-start-resizer,.fc-rtl .fc-h-event .fc-end-resizer{cursor:w-resize;left:-1px}.fc-ltr .fc-h-event .fc-end-resizer,.fc-rtl .fc-h-event .fc-start-resizer{cursor:e-resize;right:-1px}.fc-h-event.fc-allow-mouse-resize .fc-resizer{width:7px;top:-1px;bottom:-1px}.fc-h-event.fc-selected .fc-resizer{border-radius:4px;border-width:1px;width:6px;height:6px;border-style:solid;border-color:inherit;background:#fff;top:50%;margin-top:-4px}.fc-ltr .fc-h-event.fc-selected .fc-start-resizer,.fc-rtl .fc-h-event.fc-selected .fc-end-resizer{margin-left:-4px}.fc-ltr .fc-h-event.fc-selected .fc-end-resizer,.fc-rtl .fc-h-event.fc-selected .fc-start-resizer{margin-right:-4px}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}tr:first-child>td>.fc-day-grid-event{margin-top:2px}.fc-mirror-skeleton tr:first-child>td>.fc-day-grid-event{margin-top:0}.fc-day-grid-event .fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer{margin-left:-2px}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer{margin-right:-2px}a.fc-more{margin:1px 3px;font-size:.85em;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc-limited{display:none}.fc-button,.fc-icon{display:inline-block;font-weight:400;text-align:center}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-now-indicator{position:absolute;border:0 solid red}.fc-unselectable{-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent}.fc-unthemed .fc-content,.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-list-view,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff}.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-popover .fc-header{background:#eee}.fc-unthemed td.fc-today{background:#fcf8e3}.fc-unthemed .fc-disabled-day{background:#d7d7d7;opacity:.3}@font-face{font-family:fcicons;src:url("data:application/x-font-ttf;charset=utf-8;base64,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") format("truetype");font-weight:400;font-style:normal}.fc-icon{font-family:fcicons!important;speak:none;font-style:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;width:1em;height:1em}.fc-icon-chevron-left:before{content:"\e900"}.fc-icon-chevron-right:before{content:"\e901"}.fc-icon-chevrons-left:before{content:"\e902"}.fc-icon-chevrons-right:before{content:"\e903"}.fc-icon-minus-square:before{content:"\e904"}.fc-icon-plus-square:before{content:"\e905"}.fc-icon-x:before{content:"\e906"}.fc-button{overflow:visible;text-transform:none;margin:0;font-family:inherit}.fc-button::-moz-focus-inner{padding:0;border-style:none}.fc-button{-webkit-appearance:button;color:#212529;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.4em .65em;font-size:1em;line-height:1.5;border-radius:.25em}.fc-button:hover{color:#212529;text-decoration:none}.fc-button:focus{outline:0;-webkit-box-shadow:0 0 0 .2rem rgba(44,62,80,.25);box-shadow:0 0 0 .2rem rgba(44,62,80,.25)}.fc-button:disabled{opacity:.65}.fc-button-primary{color:#fff;background-color:#2C3E50;border-color:#2C3E50}.fc-button-primary:hover{color:#fff;background-color:#1e2b37;border-color:#1a252f}.fc-button-primary:focus{-webkit-box-shadow:0 0 0 .2rem rgba(76,91,106,.5);box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc-button-primary:disabled{color:#fff;background-color:#2C3E50;border-color:#2C3E50}.fc-button-primary:not(:disabled).fc-button-active,.fc-button-primary:not(:disabled):active{color:#fff;background-color:#1a252f;border-color:#151e27}.fc-button-primary:not(:disabled).fc-button-active:focus,.fc-button-primary:not(:disabled):active:focus{-webkit-box-shadow:0 0 0 .2rem rgba(76,91,106,.5);box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc-button .fc-icon{vertical-align:middle;font-size:1.5em}.fc-button-group{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.fc-button-group>.fc-button{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.fc-button-group>.fc-button.fc-button-active,.fc-button-group>.fc-button:active,.fc-button-group>.fc-button:focus,.fc-button-group>.fc-button:hover{z-index:1}.fc-button-group>.fc-button:not(:first-child){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-button-group>.fc-button:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.fc-unthemed .fc-popover{border-width:1px;border-style:solid}.fc-unthemed .fc-list-item:hover td{background-color:#f5f5f5}.fc-toolbar{display:flex;justify-content:space-between;align-items:center}.fc-toolbar.fc-header-toolbar{margin-bottom:1.5em}.fc-toolbar.fc-footer-toolbar{margin-top:1.5em}.fc-toolbar>*>:not(:first-child){margin-left:.75em}.fc-toolbar h2{font-size:1.75em;margin:0}.fc-view-container{position:relative}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}@media print{.fc-bg,.fc-bgevent-container,.fc-bgevent-skeleton,.fc-business-container,.fc-event .fc-resizer,.fc-highlight-container,.fc-highlight-skeleton,.fc-mirror-container,.fc-mirror-skeleton{display:none}.fc tbody .fc-row,.fc-time-grid{min-height:0!important}.fc-time-grid .fc-event.fc-not-end:after,.fc-time-grid .fc-event.fc-not-start:before{content:"..."}.fc{max-width:100%!important}.fc-event{background:#fff!important;color:#000!important;page-break-inside:avoid}.fc hr,.fc tbody,.fc td,.fc th,.fc thead,.fc-row{border-color:#ccc!important;background:#fff!important}.fc tbody .fc-row{height:auto!important}.fc tbody .fc-row .fc-content-skeleton{position:static;padding-bottom:0!important}.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td{padding-bottom:1em}.fc tbody .fc-row .fc-content-skeleton table{height:1em}.fc-more,.fc-more-cell{display:none!important}.fc tr.fc-limited{display:table-row!important}.fc td.fc-limited{display:table-cell!important}.fc-popover,.fc-timeGrid-view .fc-axis{display:none}.fc-slats,.fc-time-grid hr{display:none!important}.fc button,.fc-button-group,.fc-time-grid .fc-event .fc-time span{display:none}.fc-time-grid .fc-content-skeleton{position:static}.fc-time-grid .fc-content-skeleton table{height:4em}.fc-time-grid .fc-event-container{margin:0!important}.fc-time-grid .fc-event{position:static!important;margin:3px 2px!important}.fc-time-grid .fc-event.fc-not-end{border-bottom-width:1px!important}.fc-time-grid .fc-event.fc-not-start{border-top-width:1px!important}.fc-time-grid .fc-event .fc-time{white-space:normal!important}.fc-time-grid .fc-event .fc-time:after{content:attr(data-full)}.fc-day-grid-container,.fc-scroller,.fc-time-grid-container{overflow:visible!important;height:auto!important}.fc-row{border:0!important;margin:0!important}} \ No newline at end of file diff --git a/library/fullcalendar/packages/core/main.min.js b/library/fullcalendar/packages/core/main.min.js new file mode 100644 index 000000000..a961abf95 --- /dev/null +++ b/library/fullcalendar/packages/core/main.min.js @@ -0,0 +1,9 @@ +/*! +FullCalendar Core Package v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):(e=e||self,t(e.FullCalendar={}))}(this,function(e){"use strict";function t(e,t,n){var r=document.createElement(e);if(t)for(var i in t)"style"===i?g(r,t[i]):mi[i]?r[i]=t[i]:r.setAttribute(i,t[i]);return"string"==typeof n?r.innerHTML=n:null!=n&&a(r,n),r}function n(e){e=e.trim();var t=document.createElement(o(e));return t.innerHTML=e,t.firstChild}function r(e){return Array.prototype.slice.call(i(e))}function i(e){e=e.trim();var t=document.createElement(o(e));return t.innerHTML=e,t.childNodes}function o(e){return Ei[e.substr(0,3)]||"div"}function a(e,t){for(var n=l(t),r=0;r=t.left&&e.left=t.top&&e.top
");document.body.appendChild(e);var n=e.firstChild,r=n.getBoundingClientRect().left>e.getBoundingClientRect().left;return c(e),r}function I(e){return e=Math.max(0,e),e=Math.round(e)}function C(e,t){void 0===t&&(t=!1);var n=window.getComputedStyle(e),r=parseInt(n.borderLeftWidth,10)||0,i=parseInt(n.borderRightWidth,10)||0,o=parseInt(n.borderTopWidth,10)||0,a=parseInt(n.borderBottomWidth,10)||0,s=I(e.offsetWidth-e.clientWidth-r-i),u=I(e.offsetHeight-e.clientHeight-o-a),l={borderLeft:r,borderRight:i,borderTop:o,borderBottom:a,scrollbarBottom:u,scrollbarLeft:0,scrollbarRight:0};return w()&&"rtl"===n.direction?l.scrollbarLeft=s:l.scrollbarRight=s,t&&(l.paddingLeft=parseInt(n.paddingLeft,10)||0,l.paddingRight=parseInt(n.paddingRight,10)||0,l.paddingTop=parseInt(n.paddingTop,10)||0,l.paddingBottom=parseInt(n.paddingBottom,10)||0),l}function M(e,t){void 0===t&&(t=!1);var n=k(e),r=C(e,t),i={left:n.left+r.borderLeft+r.scrollbarLeft,right:n.right-r.borderRight-r.scrollbarRight,top:n.top+r.borderTop,bottom:n.bottom-r.borderBottom-r.scrollbarBottom};return t&&(i.left+=r.paddingLeft,i.right-=r.paddingRight,i.top+=r.paddingTop,i.bottom-=r.paddingBottom),i}function k(e){var t=e.getBoundingClientRect();return{left:t.left+window.pageXOffset,top:t.top+window.pageYOffset,right:t.right+window.pageXOffset,bottom:t.bottom+window.pageYOffset}}function O(){return{left:window.pageXOffset,right:window.pageXOffset+document.documentElement.clientWidth,top:window.pageYOffset,bottom:window.pageYOffset+document.documentElement.clientHeight}}function _(e){var t=window.getComputedStyle(e);return e.getBoundingClientRect().height+parseInt(t.marginTop,10)+parseInt(t.marginBottom,10)}function P(e){for(var t=[];e instanceof HTMLElement;){var n=window.getComputedStyle(e);if("fixed"===n.position)break;/(auto|scroll)/.test(n.overflow+n.overflowY+n.overflowX)&&t.push(e),e=e.parentNode}return t}function H(e){return P(e).map(function(e){return M(e)}).concat(O()).reduce(function(e,t){return E(e,t)||t})}function x(e){e.preventDefault()}function N(e,t,n,r){function i(e){var t=d(e.target,n);t&&r.call(t,e,t)}return e.addEventListener(t,i),function(){e.removeEventListener(t,i)}}function z(e,t,n,r){var i;return N(e,"mouseover",t,function(e,t){if(t!==i){i=t,n(e,t);var o=function(e){i=null,r(e,t),t.removeEventListener("mouseleave",o)};t.addEventListener("mouseleave",o)}})}function U(e,t){var n=function(r){t(r),wi.forEach(function(t){e.removeEventListener(t,n)})};wi.forEach(function(t){e.addEventListener(t,n)})}function L(e,t){var n=ie(e);return n[2]+=7*t,oe(n)}function A(e,t){var n=ie(e);return n[2]+=t,oe(n)}function V(e,t){var n=ie(e);return n[6]+=t,oe(n)}function B(e,t){return F(e,t)/7}function F(e,t){return(t.valueOf()-e.valueOf())/864e5}function W(e,t){return(t.valueOf()-e.valueOf())/36e5}function Z(e,t){return(t.valueOf()-e.valueOf())/6e4}function j(e,t){return(t.valueOf()-e.valueOf())/1e3}function Y(e,t){var n=X(e),r=X(t);return{years:0,months:0,days:Math.round(F(n,r)),milliseconds:t.valueOf()-r.valueOf()-(e.valueOf()-n.valueOf())}}function q(e,t){var n=G(e,t);return null!==n&&n%7==0?n/7:null}function G(e,t){return se(e)===se(t)?Math.round(F(e,t)):null}function X(e){return oe([e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()])}function J(e){return oe([e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours()])}function K(e){return oe([e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes()])}function Q(e){return oe([e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds()])}function $(e,t,n){var r=e.getUTCFullYear(),i=ee(e,r,t,n);if(i<1)return ee(e,r-1,t,n);var o=ee(e,r+1,t,n);return o>=1?Math.min(i,o):i}function ee(e,t,n,r){var i=oe([t,0,1+te(t,n,r)]),o=X(e),a=Math.round(F(i,o));return Math.floor(a/7)+1}function te(e,t,n){var r=7+t-n;return-(7+oe([e,0,r]).getUTCDay()-t)%7+r-1}function ne(e){return[e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds()]}function re(e){return new Date(e[0],e[1]||0,null==e[2]?1:e[2],e[3]||0,e[4]||0,e[5]||0)}function ie(e){return[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds()]}function oe(e){return 1===e.length&&(e=e.concat([0])),new Date(Date.UTC.apply(Date,e))}function ae(e){return!isNaN(e.valueOf())}function se(e){return 1e3*e.getUTCHours()*60*60+1e3*e.getUTCMinutes()*60+1e3*e.getUTCSeconds()+e.getUTCMilliseconds()}function ue(e,t){var n;return"string"==typeof e?le(e):"object"==typeof e&&e?ce(e):"number"==typeof e?ce((n={},n[t||"milliseconds"]=e,n)):null}function le(e){var t=Ci.exec(e);if(t){var n=t[1]?-1:1;return{years:0,months:0,days:n*(t[2]?parseInt(t[2],10):0),milliseconds:n*(60*(t[3]?parseInt(t[3],10):0)*60*1e3+60*(t[4]?parseInt(t[4],10):0)*1e3+1e3*(t[5]?parseInt(t[5],10):0)+(t[6]?parseInt(t[6],10):0))}}return null}function ce(e){return{years:e.years||e.year||0,months:e.months||e.month||0,days:(e.days||e.day||0)+7*de(e),milliseconds:60*(e.hours||e.hour||0)*60*1e3+60*(e.minutes||e.minute||0)*1e3+1e3*(e.seconds||e.second||0)+(e.milliseconds||e.millisecond||e.ms||0)}}function de(e){return e.weeks||e.week||0}function fe(e,t){return e.years===t.years&&e.months===t.months&&e.days===t.days&&e.milliseconds===t.milliseconds}function pe(e){return 0===e.years&&0===e.months&&1===e.days&&0===e.milliseconds}function he(e,t){return{years:e.years+t.years,months:e.months+t.months,days:e.days+t.days,milliseconds:e.milliseconds+t.milliseconds}}function ve(e,t){return{years:e.years-t.years,months:e.months-t.months,days:e.days-t.days,milliseconds:e.milliseconds-t.milliseconds}}function ge(e,t){return{years:e.years*t,months:e.months*t,days:e.days*t,milliseconds:e.milliseconds*t}}function ye(e){return Ee(e)/365}function me(e){return Ee(e)/30}function Ee(e){return be(e)/864e5}function Se(e){return be(e)/6e4}function De(e){return be(e)/1e3}function be(e){return 31536e6*e.years+2592e6*e.months+864e5*e.days+e.milliseconds}function Te(e,t){for(var n=null,r=0;rt&&(t=r)}}),t++,e.forEach(function(e){e.style.width=t+"px"}),t}function Pe(e,t){var n={position:"relative",left:-1};g(e,n),g(t,n);var r=e.offsetHeight-t.offsetHeight,i={position:"",left:""};return g(e,i),g(t,i),r}function He(e){e.classList.add("fc-unselectable"),e.addEventListener("selectstart",x)}function xe(e){e.classList.remove("fc-unselectable"),e.removeEventListener("selectstart",x)}function Ne(e){e.addEventListener("contextmenu",x)}function ze(e){e.removeEventListener("contextmenu",x)}function Ue(e){var t,n,r=[],i=[];for("string"==typeof e?i=e.split(/\s*,\s*/):"function"==typeof e?i=[e]:Array.isArray(e)&&(i=e),t=0;t=be(t)&&(r=A(r,1))}return e.start&&(n=X(e.start),r&&r<=n&&(r=A(n,1))),{start:n,end:r}}function Qe(e){var t=Ke(e);return F(t.start,t.end)>1}function $e(e,t,n,r){return"year"===r?ue(n.diffWholeYears(e,t),"year"):"month"===r?ue(n.diffWholeMonths(e,t),"month"):Y(e,t)}function et(e,t){function n(){this.constructor=e}Mi(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}function tt(e,t,n,r,i){for(var o=0;o=0;o--)if("object"==typeof(a=e[o][r])&&a)i.unshift(a);else if(void 0!==a){u[r]=a;break}i.length&&(u[r]=rt(i))}for(n=e.length-1;n>=0;n--){s=e[n];for(r in s)r in u||(u[r]=s[r])}return u}function it(e,t){var n={};for(var r in e)t(e[r],r)&&(n[r]=e[r]);return n}function ot(e,t){var n={};for(var r in e)n[r]=t(e[r],r);return n}function at(e){for(var t={},n=0,r=e;no&&i.push({start:o,end:r.start}),r.end>o&&(o=r.end);return ot.start)&&(null===e.start||null===t.end||e.start=e.start)&&(null===e.end||null!==t.end&&t.end<=e.end)}function Rt(e,t){return(null===e.start||t>=e.start)&&(null===e.end||t=t.end?new Date(t.end.valueOf()-1):e}function Ct(e,t){for(var n=0,r=0;r10&&(null==t?r=r.replace("Z",""):0!==t&&(r=r.replace("Z",Wt(t,!0)))),r}function Ft(e){return Fe(e.getUTCHours(),2)+":"+Fe(e.getUTCMinutes(),2)+":"+Fe(e.getUTCSeconds(),2)}function Wt(e,t){void 0===t&&(t=!1);var n=e<0?"-":"+",r=Math.abs(e),i=Math.floor(r/60),o=Math.round(r%60);return t?n+Fe(i,2)+":"+Fe(o,2):"GMT"+n+i+(o?":"+Fe(o,2):"")}function Zt(e,t,n,r){var i=jt(e,n.calendarSystem);return{date:i,start:i,end:t?jt(t,n.calendarSystem):null,timeZone:n.timeZone,localeCodes:n.locale.codes,separator:r}}function jt(e,t){var n=t.markerToArray(e.marker);return{marker:e.marker,timeZoneOffset:e.timeZoneOffset,array:n,year:n[0],month:n[1],day:n[2],hour:n[3],minute:n[4],second:n[5],millisecond:n[6]}}function Yt(e,t,n,r){var i={},o={},a={},s=[],u=[],l=Kt(e.defs,t);for(var c in e.defs){var d=e.defs[c];"inverse-background"===d.rendering&&(d.groupId?(i[d.groupId]=[],a[d.groupId]||(a[d.groupId]=d)):o[c]=[])}for(var f in e.instances){var p=e.instances[f],d=e.defs[p.defId],h=l[d.defId],v=p.range,g=!d.allDay&&r?Ke(v,r):v,y=Dt(g,n);y&&("inverse-background"===d.rendering?d.groupId?i[d.groupId].push(y):o[p.defId].push(y):("background"===d.rendering?s:u).push({def:d,ui:h,instance:p,range:y,isStart:g.start&&g.start.valueOf()===y.start.valueOf(),isEnd:g.end&&g.end.valueOf()===y.end.valueOf()}))}for(var m in i)for(var E=i[m],S=Et(E,n),D=0,b=S;D/g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\n/g,"
")}function Tn(e){var t=[];for(var n in e){var r=e[n];null!=r&&""!==r&&t.push(n+":"+r)}return t.join(";")}function wn(e){var t=[];for(var n in e){var r=e[n];null!=r&&t.push(n+'="'+bn(r)+'"')}return t.join(" ")}function Rn(e){return Array.isArray(e)?e:"string"==typeof e?e.split(/\s+/):[]}function In(e,t,n){var r=Ge(e,Fi,{},n),i=Dn(r.constraint,t);return{startEditable:null!=r.startEditable?r.startEditable:r.editable,durationEditable:null!=r.durationEditable?r.durationEditable:r.editable,constraints:null!=i?[i]:[],overlap:r.overlap,allows:null!=r.allow?[r.allow]:[],backgroundColor:r.backgroundColor||r.color,borderColor:r.borderColor||r.color,textColor:r.textColor,classNames:r.classNames.concat(r.className)}}function Cn(e,t,n,r){var i={},o={};for(var a in Fi){var s=e+Be(a);i[a]=t[s],o[s]=!0}if("event"===e&&(i.editable=t.editable),r)for(var a in t)o[a]||(r[a]=t[a]);return In(i,n)}function Mn(e){return e.reduce(kn,Wi)}function kn(e,t){return{startEditable:null!=t.startEditable?t.startEditable:e.startEditable,durationEditable:null!=t.durationEditable?t.durationEditable:e.durationEditable,constraints:e.constraints.concat(t.constraints),overlap:"boolean"==typeof t.overlap?t.overlap:e.overlap,allows:e.allows.concat(t.allows),backgroundColor:t.backgroundColor||e.backgroundColor,borderColor:t.borderColor||e.borderColor,textColor:t.textColor||e.textColor,classNames:e.classNames.concat(t.classNames)}}function On(e,t,n,r){var i=zn(t,n),o={},a=tt(e,i,n.dateEnv,n.pluginSystem.hooks.recurringTypes,o);if(a){var s=_n(o,t,a.allDay,Boolean(a.duration),n);return s.recurringDef={typeId:a.typeId,typeData:a.typeData,duration:a.duration},{def:s,instance:null}}var u={},l=Hn(e,i,n,u,r);if(l){var s=_n(u,t,l.allDay,l.hasEnd,n);return{def:s,instance:Pn(s.defId,l.range,l.forcedStartTzo,l.forcedEndTzo)}}return null}function _n(e,t,n,r,i){var o={},a=Nn(e,i,o);a.defId=String(Yi++),a.sourceId=t,a.allDay=n,a.hasEnd=r;for(var s=0,u=i.pluginSystem.hooks.eventDefParsers;s0){if(e.length!==t.length)return!1;for(var r=0;r0){for(var r in e)if(!(r in t))return!1;for(var r in t){if(!(r in e))return!1;if(!Vn(e[r],t[r],n-1))return!1}return!0}return!1}function Wn(e,t,n){void 0===n&&(n=1);var r={};for(var i in t)i in e&&Vn(e[i],t[i],n-1)||(r[i]=t[i]);return r}function Zn(e,t){for(var n in e)if(!(n in t))return!0;return!1}function jn(e,t,n){var r=[];e&&r.push(e),t&&r.push(t);var i={"":Mn(r)};return n&&ki(i,n),i}function Yn(e,t,n,r){var i,o,a,s,u=e.dateEnv;return t instanceof Date?i=t:(i=t.date,o=t.type,a=t.forceOff),s={date:u.formatIso(i,{omitTime:!0}),type:o||"day"},"string"==typeof n&&(r=n,n=null),n=n?" "+wn(n):"",r=r||"",!a&&e.opt("navLinks")?"'+r+"
":""+r+""}function qn(e){return e.opt("allDayHtml")||bn(e.opt("allDayText"))} +function Gn(e,t,n,r){var i,o,a=n.calendar,s=n.view,u=n.theme,l=n.dateEnv,c=[];return Rt(t.activeRange,e)?(c.push("fc-"+Ri[e.getUTCDay()]),s.opt("monthMode")&&l.getMonth(e)!==l.getMonth(t.currentRange.start)&&c.push("fc-other-month"),i=X(a.getNow()),o=A(i,1),e=o?c.push("fc-future"):(c.push("fc-today"),!0!==r&&c.push(u.getClass("today")))):c.push("fc-disabled-day"),c}function Xn(e,t,n){var r=!1,i=function(){r||(r=!0,t.apply(this,arguments))},o=function(){r||(r=!0,n&&n.apply(this,arguments))},a=e(i,o);a&&"function"==typeof a.then&&a.then(i,o)}function Jn(e,t,n){(e[t]||(e[t]=[])).push(n)}function Kn(e,t,n){n?e[t]&&(e[t]=e[t].filter(function(e){return e!==n})):delete e[t]}function Qn(e,t,n){var r={},i=!1;for(var o in t)o in e&&(e[o]===t[o]||n[o]&&n[o](e[o],t[o]))?r[o]=e[o]:(r[o]=t[o],i=!0);for(var o in e)if(!(o in t)){i=!0;break}return{anyChanges:i,comboProps:r}}function $n(e){return{id:String(so++),deps:e.deps||[],reducers:e.reducers||[],eventDefParsers:e.eventDefParsers||[],eventDragMutationMassagers:e.eventDragMutationMassagers||[],eventDefMutationAppliers:e.eventDefMutationAppliers||[],dateSelectionTransformers:e.dateSelectionTransformers||[],datePointTransforms:e.datePointTransforms||[],dateSpanTransforms:e.dateSpanTransforms||[],views:e.views||{},viewPropsTransformers:e.viewPropsTransformers||[],isPropsValid:e.isPropsValid||null,externalDefTransforms:e.externalDefTransforms||[],eventResizeJoinTransforms:e.eventResizeJoinTransforms||[],viewContainerModifiers:e.viewContainerModifiers||[],eventDropTransformers:e.eventDropTransformers||[],componentInteractions:e.componentInteractions||[],calendarInteractions:e.calendarInteractions||[],themeClasses:e.themeClasses||{},eventSourceDefs:e.eventSourceDefs||[],cmdFormatter:e.cmdFormatter,recurringTypes:e.recurringTypes||[],namedTimeZonedImpl:e.namedTimeZonedImpl,defaultView:e.defaultView||"",elementDraggingImpl:e.elementDraggingImpl,optionChangeHandlers:e.optionChangeHandlers||{}}}function er(e,t){return{reducers:e.reducers.concat(t.reducers),eventDefParsers:e.eventDefParsers.concat(t.eventDefParsers),eventDragMutationMassagers:e.eventDragMutationMassagers.concat(t.eventDragMutationMassagers),eventDefMutationAppliers:e.eventDefMutationAppliers.concat(t.eventDefMutationAppliers),dateSelectionTransformers:e.dateSelectionTransformers.concat(t.dateSelectionTransformers),datePointTransforms:e.datePointTransforms.concat(t.datePointTransforms),dateSpanTransforms:e.dateSpanTransforms.concat(t.dateSpanTransforms),views:ki({},e.views,t.views),viewPropsTransformers:e.viewPropsTransformers.concat(t.viewPropsTransformers),isPropsValid:t.isPropsValid||e.isPropsValid,externalDefTransforms:e.externalDefTransforms.concat(t.externalDefTransforms),eventResizeJoinTransforms:e.eventResizeJoinTransforms.concat(t.eventResizeJoinTransforms),viewContainerModifiers:e.viewContainerModifiers.concat(t.viewContainerModifiers),eventDropTransformers:e.eventDropTransformers.concat(t.eventDropTransformers),calendarInteractions:e.calendarInteractions.concat(t.calendarInteractions),componentInteractions:e.componentInteractions.concat(t.componentInteractions),themeClasses:ki({},e.themeClasses,t.themeClasses),eventSourceDefs:e.eventSourceDefs.concat(t.eventSourceDefs),cmdFormatter:t.cmdFormatter||e.cmdFormatter,recurringTypes:e.recurringTypes.concat(t.recurringTypes),namedTimeZonedImpl:t.namedTimeZonedImpl||e.namedTimeZonedImpl,defaultView:e.defaultView||t.defaultView,elementDraggingImpl:e.elementDraggingImpl||t.elementDraggingImpl,optionChangeHandlers:ki({},e.optionChangeHandlers,t.optionChangeHandlers)}}function tr(e,t,n,r,i){e=e.toUpperCase();var o=null;"GET"===e?t=nr(t,n):o=rr(n);var a=new XMLHttpRequest;a.open(e,t,!0),"GET"!==e&&a.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),a.onload=function(){if(a.status>=200&&a.status<400)try{var e=JSON.parse(a.responseText);r(e,a)}catch(e){i("Failure parsing JSON",a)}else i("Request failed",a)},a.onerror=function(){i("Request failed",a)},a.send(o)}function nr(e,t){return e+(-1===e.indexOf("?")?"?":"&")+rr(t)}function rr(e){var t=[];for(var n in e)t.push(encodeURIComponent(n)+"="+encodeURIComponent(e[n]));return t.join("&")}function ir(e,t,n){var r,i,o,a,s=n.dateEnv,u={};return r=e.startParam,null==r&&(r=n.opt("startParam")),i=e.endParam,null==i&&(i=n.opt("endParam")),o=e.timeZoneParam,null==o&&(o=n.opt("timeZoneParam")),a="function"==typeof e.extraParams?e.extraParams():e.extraParams||{},ki(u,a),u[r]=s.formatIso(t.start),u[i]=s.formatIso(t.end),"local"!==s.timeZone&&(u[o]=s.timeZone),u}function or(e,t,n,r){for(var i=e?at(e):null,o=X(n.start),a=n.end,s=[];o0?e[0].code:"en",n=window.FullCalendarLocalesAll||[],r=window.FullCalendarLocales||{},i=n.concat(st(r),e),o={en:wo},a=0,s=i;a0;i--){var o=r.slice(0,i).join("-");if(t[o])return t[o]}return null}function hr(e,t,n){var r=rt([wo,n],["buttonText"]);delete r.code;var i=r.week;return delete r.week,{codeArg:e,codes:t,week:i,simpleNumberFormat:new Intl.NumberFormat(e),options:r}}function vr(e){return new Io[e]}function gr(e){var t=null,n=!1,r=Mo.exec(e);r&&(n=!r[1],n?e+="T00:00:00Z":e=e.replace(ko,function(e,n,r,i,o){return t=n?0:(60*parseInt(i,10)+parseInt(o||0,10))*("-"===r?-1:1),""})+"Z");var i=new Date(e);return ae(i)?{marker:i,isTimeUnspecified:n,timeZoneOffset:t}:null}function yr(e,t){return!t.pluginSystem.hooks.eventSourceDefs[e.sourceDefId].ignoreRange}function mr(e,t){for(var n=t.pluginSystem.hooks.eventSourceDefs,r=n.length-1;r>=0;r--){var i=n[r],o=i.parseMeta(e);if(o){var a=Er("object"==typeof e?e:{},o,r,t);return a._raw=Xe(e),a}}return null}function Er(e,t,n,r){var i={},o=Ge(e,_o,{},i),a={},s=In(i,r,a);return o.isFetching=!1,o.latestFetchId="",o.fetchRange=null,o.publicId=String(e.id||""),o.sourceId=String(Po++),o.sourceDefId=n,o.meta=t,o.ui=s,o.extendedProps=a,o}function Sr(e,t,n,r){switch(t.type){case"ADD_EVENT_SOURCES":return Dr(e,t.sources,n?n.activeRange:null,r);case"REMOVE_EVENT_SOURCE":return br(e,t.sourceId);case"PREV":case"NEXT":case"SET_DATE":case"SET_VIEW_TYPE":return n?Tr(e,n.activeRange,r):e;case"FETCH_EVENT_SOURCES":case"CHANGE_TIMEZONE":return Rr(e,t.sourceIds?at(t.sourceIds):Mr(e,r),n?n.activeRange:null,r);case"RECEIVE_EVENTS":case"RECEIVE_EVENT_ERROR":return Cr(e,t.sourceId,t.fetchId,t.fetchRange);case"REMOVE_ALL_EVENT_SOURCES":return{};default:return e}}function Dr(e,t,n,r){for(var i={},o=0,a=t;oe.fetchRange.end:!e.latestFetchId}function Rr(e,t,n,r){var i={};for(var o in e){var a=e[o];t[o]?i[o]=Ir(a,n,r):i[o]=a}return i}function Ir(e,t,n){var r=n.pluginSystem.hooks.eventSourceDefs[e.sourceDefId],i=String(Ho++);return r.fetch({eventSource:e,calendar:n,range:t},function(r){var o,a,s=r.rawEvents,u=n.opt("eventSourceSuccess");e.success&&(a=e.success(s,r.xhr)),u&&(o=u(s,r.xhr)),s=a||o||s,n.dispatch({type:"RECEIVE_EVENTS",sourceId:e.sourceId,fetchId:i,fetchRange:t,rawEvents:s})},function(r){var o=n.opt("eventSourceFailure");console.warn(r.message,r),e.failure&&e.failure(r),o&&o(r),n.dispatch({type:"RECEIVE_EVENT_ERROR",sourceId:e.sourceId,fetchId:i,fetchRange:t,error:r})}),ki({},e,{isFetching:!0,latestFetchId:i})}function Cr(e,t,n,r){var i,o=e[t];return o&&n===o.latestFetchId?ki({},e,(i={},i[t]=ki({},o,{isFetching:!1,fetchRange:r}),i)):e}function Mr(e,t){return it(e,function(e){return yr(e,t)})}function kr(e,t){return bt(e.activeRange,t.activeRange)&&bt(e.validRange,t.validRange)&&fe(e.minTime,t.minTime)&&fe(e.maxTime,t.maxTime)}function Or(e,t,n){for(var r=_r(e.viewType,t),i=Pr(e.dateProfile,t,e.currentDate,r,n),o=Sr(e.eventSources,t,i,n),a=ki({},e,{viewType:r,dateProfile:i,currentDate:Hr(e.currentDate,t,i),eventSources:o,eventStore:rn(e.eventStore,t,o,i,n),dateSelection:xr(e.dateSelection,t,n),eventSelection:Nr(e.eventSelection,t),eventDrag:zr(e.eventDrag,t,o,n),eventResize:Ur(e.eventResize,t,o,n),eventSourceLoadingLevel:Lr(o),loadingLevel:Lr(o)}),s=0,u=n.pluginSystem.hooks.reducers;s1?{year:"numeric",month:"short",day:"numeric"}:{year:"numeric",month:"long",day:"numeric"}}function ni(e){return e.map(function(e){return new e})}function ri(e,t){return{component:e,el:t.el,useEventCenter:null==t.useEventCenter||t.useEventCenter}}function ii(e){var t;return t={},t[e.component.uid]=e,t}function oi(e,t,n,r,i,o,a){return new Oo({calendarSystem:"gregory",timeZone:t,namedTimeZoneImpl:n,locale:e,weekNumberCalculation:i,firstDay:r,weekLabel:o,cmdFormatter:a})}function ai(e){return new(this.pluginSystem.hooks.themeClasses[e.themeSystem]||Wo)(e)}function si(e){var t=this.tryRerender.bind(this);return null!=e&&(t=qe(t,e)),t}function ui(e){return ot(e,function(e){return e.ui})}function li(e,t,n){var r={"":t};for(var i in e){var o=e[i];o.sourceId&&n[o.sourceId]&&(r[i]=n[o.sourceId])}return r}function ci(e){var t=e.eventRange.def,n=e.eventRange.instance.range,r=n.start?n.start.valueOf():0,i=n.end?n.end.valueOf():0;return ki({},t.extendedProps,t,{id:t.publicId,start:r,end:i,duration:i-r,allDay:Number(t.allDay),_seg:e})}function di(e,t){void 0===t&&(t={});var n=pi(t),r=Vt(t),i=n.createMarkerMeta(e);return i?n.format(i.marker,r,{forcedTzo:i.forcedTzo}):""}function fi(e,t,n){var r=pi("object"==typeof n&&n?n:{}),i=Vt(n,So.defaultRangeSeparator),o=r.createMarkerMeta(e),a=r.createMarkerMeta(t);return o&&a?r.formatRange(o.marker,a.marker,i,{forcedStartTzo:o.forcedTzo,forcedEndTzo:a.forcedTzo,isEndExclusive:n.isEndExclusive}):""}function pi(e){var t=dr(e.locale||"en",cr([]).map);return e=ki({timeZone:So.timeZone,calendarSystem:"gregory"},e,{locale:t}),new Oo(e)}function hi(e){var t={},n=Ge(e,Jo,Ko,t);return n.leftoverProps=t,n}function vi(e,t){return!e||t>10?{weekday:"short"}:t>1?{weekday:"short",month:"numeric",day:"numeric",omitCommas:!0}:{weekday:"long"}}function gi(e,t,n,r,i,o,a,s){var u,l=o.view,c=o.dateEnv,d=o.theme,f=o.options,p=Rt(t.activeRange,e),h=["fc-day-header",d.getClass("widgetHeader")];return u="function"==typeof f.columnHeaderHtml?f.columnHeaderHtml(c.toDate(e)):bn("function"==typeof f.columnHeaderText?f.columnHeaderText(c.toDate(e)):c.format(e,i)),n?h=h.concat(Gn(e,t,o,!0)):h.push("fc-"+Ri[e.getUTCDay()]),'1?' colspan="'+a+'"':"")+(s?" "+s:"")+">"+(p?Yn(l,{date:e,forceOff:!n||1===r},u):u)+""}function yi(e,t){var n=e.activeRange;return t?n:{start:V(n.start,e.minTime.milliseconds),end:V(n.end,e.maxTime.milliseconds-864e5)}}var mi={className:!0,colSpan:!0,rowSpan:!0},Ei={"1)||"numeric"!==i.year&&"2-digit"!==i.year||"numeric"!==i.month&&"2-digit"!==i.month||"numeric"!==i.day&&"2-digit"!==i.day||(s=1);var u=this.format(e,n),l=this.format(t,n);if(u===l)return u;var c=Lt(i,s),d=_t(c,o,n),f=d(e),p=d(t),h=At(u,f,l,p),v=o.separator||"";return h?h.before+f+v+p+h.after:u+v+l},e.prototype.getLargestUnit=function(){switch(this.severity){case 7:case 6:case 5:return"year";case 4:return"month";case 3:return"week";default:return"day"}},e}(),Li=function(){function e(e,t){this.cmdStr=e,this.separator=t}return e.prototype.format=function(e,t){return t.cmdFormatter(this.cmdStr,Zt(e,null,t,this.separator))},e.prototype.formatRange=function(e,t,n){return n.cmdFormatter(this.cmdStr,Zt(e,t,n,this.separator))},e}(),Ai=function(){function e(e){this.func=e}return e.prototype.format=function(e,t){return this.func(Zt(e,null,t))},e.prototype.formatRange=function(e,t,n){return this.func(Zt(e,t,n))},e}(),Vi=function(){function e(e,t){this.calendar=e,this.internalEventSource=t}return e.prototype.remove=function(){this.calendar.dispatch({type:"REMOVE_EVENT_SOURCE",sourceId:this.internalEventSource.sourceId})},e.prototype.refetch=function(){this.calendar.dispatch({type:"FETCH_EVENT_SOURCES",sourceIds:[this.internalEventSource.sourceId]})},Object.defineProperty(e.prototype,"id",{get:function(){return this.internalEventSource.publicId},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"url",{get:function(){return this.internalEventSource.meta.url},enumerable:!0,configurable:!0}),e}(),Bi=function(){function e(e,t,n){this._calendar=e,this._def=t,this._instance=n||null}return e.prototype.setProp=function(e,t){var n,r;if(e in ji);else if(e in Zi)"function"==typeof Zi[e]&&(t=Zi[e](t)),this.mutate({standardProps:(n={},n[e]=t,n)});else if(e in Fi){var i=void 0;"function"==typeof Fi[e]&&(t=Fi[e](t)),"color"===e?i={backgroundColor:t,borderColor:t}:"editable"===e?i={startEditable:t,durationEditable:t}:(r={},r[e]=t,i=r),this.mutate({standardProps:{ui:i}})}},e.prototype.setExtendedProp=function(e,t){var n;this.mutate({extendedProps:(n={},n[e]=t,n)})},e.prototype.setStart=function(e,t){void 0===t&&(t={});var n=this._calendar.dateEnv,r=n.createMarker(e);if(r&&this._instance){var i=this._instance.range,o=$e(i.start,r,n,t.granularity),a=null;if(t.maintainDuration){a=ve($e(i.start,i.end,n,t.granularity),$e(r,i.end,n,t.granularity))}this.mutate({startDelta:o,endDelta:a})}},e.prototype.setEnd=function(e,t){void 0===t&&(t={});var n,r=this._calendar.dateEnv;if((null==e||(n=r.createMarker(e)))&&this._instance)if(n){var i=$e(this._instance.range.end,n,r,t.granularity);this.mutate({endDelta:i})}else this.mutate({standardProps:{hasEnd:!1}})},e.prototype.setDates=function(e,t,n){void 0===n&&(n={});var r,i=this._calendar.dateEnv,o={allDay:n.allDay},a=i.createMarker(e);if(a&&(null==t||(r=i.createMarker(t)))&&this._instance){var s=this._instance.range;!0===n.allDay&&(s=Je(s));var u=$e(s.start,a,i,n.granularity);if(r){var l=$e(s.end,r,i,n.granularity);this.mutate({startDelta:u,endDelta:l,standardProps:o})}else o.hasEnd=!1,this.mutate({startDelta:u,standardProps:o})}},e.prototype.moveStart=function(e){var t=ue(e);t&&this.mutate({startDelta:t})},e.prototype.moveEnd=function(e){var t=ue(e);t&&this.mutate({endDelta:t})},e.prototype.moveDates=function(e){var t=ue(e);t&&this.mutate({startDelta:t,endDelta:t})},e.prototype.setAllDay=function(e,t){void 0===t&&(t={});var n={allDay:e},r=t.maintainDuration;null==r&&(r=this._calendar.opt("allDayMaintainDuration")),this._def.allDay!==e&&(n.hasEnd=r),this.mutate({standardProps:n})},e.prototype.formatRange=function(e){var t=this._calendar.dateEnv,n=this._instance,r=Vt(e,this._calendar.opt("defaultRangeSeparator"));return this._def.hasEnd?t.formatRange(n.range.start,n.range.end,r,{forcedStartTzo:n.forcedStartTzo,forcedEndTzo:n.forcedEndTzo}):t.format(n.range.start,r,{forcedTzo:n.forcedStartTzo})},e.prototype.mutate=function(e){var t=this._def,n=this._instance;if(n){this._calendar.dispatch({type:"MUTATE_EVENTS",instanceId:n.instanceId,mutation:e,fromApi:!0});var r=this._calendar.state.eventStore;this._def=r.defs[t.defId],this._instance=r.instances[n.instanceId]}},e.prototype.remove=function(){this._calendar.dispatch({type:"REMOVE_EVENT_DEF",defId:this._def.defId})},Object.defineProperty(e.prototype,"source",{get:function(){var e=this._def.sourceId;return e?new Vi(this._calendar,this._calendar.state.eventSources[e]):null},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"start",{get:function(){return this._instance?this._calendar.dateEnv.toDate(this._instance.range.start):null},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"end",{get:function(){return this._instance&&this._def.hasEnd?this._calendar.dateEnv.toDate(this._instance.range.end):null},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"id",{get:function(){return this._def.publicId},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"groupId",{get:function(){return this._def.groupId},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"allDay",{get:function(){return this._def.allDay},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"title",{get:function(){return this._def.title},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"url",{get:function(){return this._def.url},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"rendering",{get:function(){return this._def.rendering},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"startEditable",{get:function(){return this._def.ui.startEditable},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"durationEditable",{get:function(){return this._def.ui.durationEditable},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"constraint",{get:function(){return this._def.ui.constraints[0]||null},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"overlap",{get:function(){return this._def.ui.overlap},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"allow",{get:function(){return this._def.ui.allows[0]||null},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"backgroundColor",{get:function(){return this._def.ui.backgroundColor},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"borderColor",{get:function(){return this._def.ui.borderColor},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"textColor",{get:function(){return this._def.ui.textColor},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"classNames",{get:function(){return this._def.ui.classNames},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"extendedProps",{get:function(){return this._def.extendedProps},enumerable:!0,configurable:!0}),e}(),Fi={editable:Boolean,startEditable:Boolean,durationEditable:Boolean,constraint:null,overlap:null,allow:null,className:Rn,classNames:Rn,color:String,backgroundColor:String,borderColor:String,textColor:String},Wi={startEditable:null,durationEditable:null,constraints:[],overlap:null,allows:[],backgroundColor:"",borderColor:"",textColor:"",classNames:[]},Zi={id:String,groupId:String,title:String,url:String,rendering:String,extendedProps:null},ji={start:null,date:null,end:null,allDay:null},Yi=0,qi={startTime:"09:00",endTime:"17:00",daysOfWeek:[1,2,3,4,5],rendering:"inverse-background",classNames:"fc-nonbusiness",groupId:"_businessHours"},Gi=vt(),Xi=function(){function e(){this.getKeysForEventDefs=kt(this._getKeysForEventDefs),this.splitDateSelection=kt(this._splitDateSpan),this.splitEventStore=kt(this._splitEventStore),this.splitIndividualUi=kt(this._splitIndividualUi),this.splitEventDrag=kt(this._splitInteraction),this.splitEventResize=kt(this._splitInteraction),this.eventUiBuilders={}}return e.prototype.splitProps=function(e){var t=this,n=this.getKeyInfo(e),r=this.getKeysForEventDefs(e.eventStore),i=this.splitDateSelection(e.dateSelection),o=this.splitIndividualUi(e.eventUiBases,r),a=this.splitEventStore(e.eventStore,r),s=this.splitEventDrag(e.eventDrag),u=this.splitEventResize(e.eventResize),l={};this.eventUiBuilders=ot(n,function(e,n){return t.eventUiBuilders[n]||kt(jn)});for(var c in n){var d=n[c],f=a[c]||Gi,p=this.eventUiBuilders[c];l[c]={businessHours:d.businessHours||e.businessHours,dateSelection:i[c]||null,eventStore:f,eventUiBases:p(e.eventUiBases[""],d.ui,o[c]),eventSelection:f.instances[e.eventSelection]?e.eventSelection:"",eventDrag:s[c]||null,eventResize:u[c]||null}}return l},e.prototype._splitDateSpan=function(e){var t={};if(e)for(var n=this.getKeysForDateSpan(e),r=0,i=n;r=n[t]&&e=n[t]&&e0},e.prototype.canScrollHorizontally=function(){return this.getMaxScrollLeft()>0},e.prototype.canScrollUp=function(){return this.getScrollTop()>0},e.prototype.canScrollDown=function(){return this.getScrollTop()0},e.prototype.canScrollRight=function(){return this.getScrollLeft()1&&(o=X(o),o=A(o,-1),o=r.add(o,n))),{start:i,end:o}},e.prototype.buildRangeFromDuration=function(e,t,n,r){function i(){s=c.startOf(e,d),u=c.add(s,n),l={start:s,end:u}}var o,a,s,u,l,c=this.dateEnv,d=this.options.dateAlignment;return d||(o=this.options.dateIncrement,o?(a=ue(o),d=be(a) ")),l=!1):((r=f[e])?(d=function(e){r.click&&r.click.call(E,e)},(v=s.getCustomButtonIconClass(r))||(v=s.getIconClass(e))||(g=r.text)):(a=c[e])?(i.viewsWithButtons.push(e),d=function(){u.changeView(e)},(g=a.buttonTextOverride)||(v=s.getIconClass(e))||(g=a.buttonTextDefault)):u[e]&&(d=function(){u[e]()},(g=p[e])||(v=s.getIconClass(e))||(g=h[e])),d&&(m=["fc-"+e+"-button",s.getClass("button")],g?(y=bn(g),S=""):v&&(y="",S=' aria-label="'+e+'"'),E=n('"),E.addEventListener("click",d),o.push(E)))}),o.length>1){r=document.createElement("div");var v=s.getClass("buttonGroup");l&&v&&r.classList.add(v),a(r,o),d.appendChild(r)}else a(d,o)}),d},r.prototype.updateToday=function(e){this.toggleButtonEnabled("today",e)},r.prototype.updatePrev=function(e){this.toggleButtonEnabled("prev",e)},r.prototype.updateNext=function(e){this.toggleButtonEnabled("next",e)},r.prototype.updateTitle=function(e){p(this.el,"h2").forEach(function(t){t.innerText=e})},r.prototype.updateActiveButton=function(e){var t=this.theme.getClass("buttonActive");p(this.el,"button").forEach(function(n){e&&n.classList.contains("fc-"+e+"-button")?n.classList.add(t):n.classList.remove(t)})},r.prototype.toggleButtonEnabled=function(e,t){p(this.el,".fc-"+e+"-button").forEach(function(e){e.disabled=!t})},r}(oo),Lo=function(e){function n(n,r){var i=e.call(this,n)||this;i._renderToolbars=An(i.renderToolbars),i.buildViewPropTransformers=kt(ni),i.el=r,s(r,i.contentEl=t("div",{className:"fc-view-container"}));for(var o=i.calendar,a=0,u=o.pluginSystem.hooks.viewContainerModifiers;a"},e.prototype.detachSegs=function(e,t){var n=this.containerElsByType[e];n&&(n.forEach(c),delete this.containerElsByType[e])},e.prototype.computeSizes=function(e){for(var t in this.segsByType)(e||this.dirtySizeFlags[t])&&this.computeSegSizes(this.segsByType[t])},e.prototype.assignSizes=function(e){for(var t in this.segsByType)(e||this.dirtySizeFlags[t])&&this.assignSegSizes(this.segsByType[t]);this.dirtySizeFlags={}},e.prototype.computeSegSizes=function(e){},e.prototype.assignSegSizes=function(e){},e}(),Go=function(){function e(e){this.timeZoneName=e}return e}(),Xo=function(){function e(e){this.emitter=new Ki}return e.prototype.destroy=function(){},e.prototype.setMirrorIsVisible=function(e){},e.prototype.setMirrorNeedsRevert=function(e){},e.prototype.setAutoScrollEnabled=function(e){},e}(),Jo={startTime:ue,duration:ue,create:Boolean,sourceId:String},Ko={create:!0},Qo=function(e){function t(t,r){var i=e.call(this,t)||this;return r.innerHTML="",r.appendChild(i.el=n('
')),i.thead=i.el.querySelector("thead"),i}return et(t,e),t.prototype.destroy=function(){c(this.el)},t.prototype.render=function(e){var t=e.dates,n=e.datesRepDistinctDays,r=[];e.renderIntroHtml&&r.push(e.renderIntroHtml());for(var i=Vt(this.opt("columnHeaderFormat")||vi(n,t.length)),o=0,a=t;o"+r.join("")+""},t}(oo),$o=function(){function e(e,t){for(var n=e.start,r=e.end,i=[],o=[],a=-1;n=t.length?t[t.length-1]+1:t[n]},e}(),ea=function(){function e(e,t){var n,r,i,o=e.dates;if(t){for(r=o[0].getUTCDay(),n=1;n * { + /* work around the way we do column resizing and ensure a minimum width */ + display: inline-block; + min-width: 1.25em; } diff --git a/library/fullcalendar/packages/daygrid/main.js b/library/fullcalendar/packages/daygrid/main.js new file mode 100644 index 000000000..e41463eb9 --- /dev/null +++ b/library/fullcalendar/packages/daygrid/main.js @@ -0,0 +1,1630 @@ +/*! +FullCalendar Day Grid Plugin v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@fullcalendar/core')) : + typeof define === 'function' && define.amd ? define(['exports', '@fullcalendar/core'], factory) : + (global = global || self, factory(global.FullCalendarDayGrid = {}, global.FullCalendar)); +}(this, function (exports, core) { 'use strict'; + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */ + /* global Reflect, Promise */ + + var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + + function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + + var DayGridDateProfileGenerator = /** @class */ (function (_super) { + __extends(DayGridDateProfileGenerator, _super); + function DayGridDateProfileGenerator() { + return _super !== null && _super.apply(this, arguments) || this; + } + // Computes the date range that will be rendered. + DayGridDateProfileGenerator.prototype.buildRenderRange = function (currentRange, currentRangeUnit, isRangeAllDay) { + var dateEnv = this.dateEnv; + var renderRange = _super.prototype.buildRenderRange.call(this, currentRange, currentRangeUnit, isRangeAllDay); + var start = renderRange.start; + var end = renderRange.end; + var endOfWeek; + // year and month views should be aligned with weeks. this is already done for week + if (/^(year|month)$/.test(currentRangeUnit)) { + start = dateEnv.startOfWeek(start); + // make end-of-week if not already + endOfWeek = dateEnv.startOfWeek(end); + if (endOfWeek.valueOf() !== end.valueOf()) { + end = core.addWeeks(endOfWeek, 1); + } + } + // ensure 6 weeks + if (this.options.monthMode && + this.options.fixedWeekCount) { + var rowCnt = Math.ceil(// could be partial weeks due to hiddenDays + core.diffWeeks(start, end)); + end = core.addWeeks(end, 6 - rowCnt); + } + return { start: start, end: end }; + }; + return DayGridDateProfileGenerator; + }(core.DateProfileGenerator)); + + /* A rectangular panel that is absolutely positioned over other content + ------------------------------------------------------------------------------------------------------------------------ + Options: + - className (string) + - content (HTML string, element, or element array) + - parentEl + - top + - left + - right (the x coord of where the right edge should be. not a "CSS" right) + - autoHide (boolean) + - show (callback) + - hide (callback) + */ + var Popover = /** @class */ (function () { + function Popover(options) { + var _this = this; + this.isHidden = true; + this.margin = 10; // the space required between the popover and the edges of the scroll container + // Triggered when the user clicks *anywhere* in the document, for the autoHide feature + this.documentMousedown = function (ev) { + // only hide the popover if the click happened outside the popover + if (_this.el && !_this.el.contains(ev.target)) { + _this.hide(); + } + }; + this.options = options; + } + // Shows the popover on the specified position. Renders it if not already + Popover.prototype.show = function () { + if (this.isHidden) { + if (!this.el) { + this.render(); + } + this.el.style.display = ''; + this.position(); + this.isHidden = false; + this.trigger('show'); + } + }; + // Hides the popover, through CSS, but does not remove it from the DOM + Popover.prototype.hide = function () { + if (!this.isHidden) { + this.el.style.display = 'none'; + this.isHidden = true; + this.trigger('hide'); + } + }; + // Creates `this.el` and renders content inside of it + Popover.prototype.render = function () { + var _this = this; + var options = this.options; + var el = this.el = core.createElement('div', { + className: 'fc-popover ' + (options.className || ''), + style: { + top: '0', + left: '0' + } + }); + if (typeof options.content === 'function') { + options.content(el); + } + options.parentEl.appendChild(el); + // when a click happens on anything inside with a 'fc-close' className, hide the popover + core.listenBySelector(el, 'click', '.fc-close', function (ev) { + _this.hide(); + }); + if (options.autoHide) { + document.addEventListener('mousedown', this.documentMousedown); + } + }; + // Hides and unregisters any handlers + Popover.prototype.destroy = function () { + this.hide(); + if (this.el) { + core.removeElement(this.el); + this.el = null; + } + document.removeEventListener('mousedown', this.documentMousedown); + }; + // Positions the popover optimally, using the top/left/right options + Popover.prototype.position = function () { + var options = this.options; + var el = this.el; + var elDims = el.getBoundingClientRect(); // only used for width,height + var origin = core.computeRect(el.offsetParent); + var clippingRect = core.computeClippingRect(options.parentEl); + var top; // the "position" (not "offset") values for the popover + var left; // + // compute top and left + top = options.top || 0; + if (options.left !== undefined) { + left = options.left; + } + else if (options.right !== undefined) { + left = options.right - elDims.width; // derive the left value from the right value + } + else { + left = 0; + } + // constrain to the view port. if constrained by two edges, give precedence to top/left + top = Math.min(top, clippingRect.bottom - elDims.height - this.margin); + top = Math.max(top, clippingRect.top + this.margin); + left = Math.min(left, clippingRect.right - elDims.width - this.margin); + left = Math.max(left, clippingRect.left + this.margin); + core.applyStyle(el, { + top: top - origin.top, + left: left - origin.left + }); + }; + // Triggers a callback. Calls a function in the option hash of the same name. + // Arguments beyond the first `name` are forwarded on. + // TODO: better code reuse for this. Repeat code + // can kill this??? + Popover.prototype.trigger = function (name) { + if (this.options[name]) { + this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); + } + }; + return Popover; + }()); + + /* Event-rendering methods for the DayGrid class + ----------------------------------------------------------------------------------------------------------------------*/ + // "Simple" is bad a name. has nothing to do with SimpleDayGrid + var SimpleDayGridEventRenderer = /** @class */ (function (_super) { + __extends(SimpleDayGridEventRenderer, _super); + function SimpleDayGridEventRenderer() { + return _super !== null && _super.apply(this, arguments) || this; + } + // Builds the HTML to be used for the default element for an individual segment + SimpleDayGridEventRenderer.prototype.renderSegHtml = function (seg, mirrorInfo) { + var options = this.context.options; + var eventRange = seg.eventRange; + var eventDef = eventRange.def; + var eventUi = eventRange.ui; + var allDay = eventDef.allDay; + var isDraggable = eventUi.startEditable; + var isResizableFromStart = allDay && seg.isStart && eventUi.durationEditable && options.eventResizableFromStart; + var isResizableFromEnd = allDay && seg.isEnd && eventUi.durationEditable; + var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd, mirrorInfo); + var skinCss = core.cssToStr(this.getSkinCss(eventUi)); + var timeHtml = ''; + var timeText; + var titleHtml; + classes.unshift('fc-day-grid-event', 'fc-h-event'); + // Only display a timed events time if it is the starting segment + if (seg.isStart) { + timeText = this.getTimeText(eventRange); + if (timeText) { + timeHtml = '' + core.htmlEscape(timeText) + ''; + } + } + titleHtml = + '' + + (core.htmlEscape(eventDef.title || '') || ' ') + // we always want one line of height + ''; + return '' + + '
' + + (options.dir === 'rtl' ? + titleHtml + ' ' + timeHtml : // put a natural space in between + timeHtml + ' ' + titleHtml // + ) + + '
' + + (isResizableFromStart ? + '
' : + '') + + (isResizableFromEnd ? + '
' : + '') + + '
'; + }; + // Computes a default event time formatting string if `eventTimeFormat` is not explicitly defined + SimpleDayGridEventRenderer.prototype.computeEventTimeFormat = function () { + return { + hour: 'numeric', + minute: '2-digit', + omitZeroMinute: true, + meridiem: 'narrow' + }; + }; + SimpleDayGridEventRenderer.prototype.computeDisplayEventEnd = function () { + return false; // TODO: somehow consider the originating DayGrid's column count + }; + return SimpleDayGridEventRenderer; + }(core.FgEventRenderer)); + + /* Event-rendering methods for the DayGrid class + ----------------------------------------------------------------------------------------------------------------------*/ + var DayGridEventRenderer = /** @class */ (function (_super) { + __extends(DayGridEventRenderer, _super); + function DayGridEventRenderer(dayGrid) { + var _this = _super.call(this, dayGrid.context) || this; + _this.dayGrid = dayGrid; + return _this; + } + // Renders the given foreground event segments onto the grid + DayGridEventRenderer.prototype.attachSegs = function (segs, mirrorInfo) { + var rowStructs = this.rowStructs = this.renderSegRows(segs); + // append to each row's content skeleton + this.dayGrid.rowEls.forEach(function (rowNode, i) { + rowNode.querySelector('.fc-content-skeleton > table').appendChild(rowStructs[i].tbodyEl); + }); + // removes the "more.." events popover + if (!mirrorInfo) { + this.dayGrid.removeSegPopover(); + } + }; + // Unrenders all currently rendered foreground event segments + DayGridEventRenderer.prototype.detachSegs = function () { + var rowStructs = this.rowStructs || []; + var rowStruct; + while ((rowStruct = rowStructs.pop())) { + core.removeElement(rowStruct.tbodyEl); + } + this.rowStructs = null; + }; + // Uses the given events array to generate elements that should be appended to each row's content skeleton. + // Returns an array of rowStruct objects (see the bottom of `renderSegRow`). + // PRECONDITION: each segment shoud already have a rendered and assigned `.el` + DayGridEventRenderer.prototype.renderSegRows = function (segs) { + var rowStructs = []; + var segRows; + var row; + segRows = this.groupSegRows(segs); // group into nested arrays + // iterate each row of segment groupings + for (row = 0; row < segRows.length; row++) { + rowStructs.push(this.renderSegRow(row, segRows[row])); + } + return rowStructs; + }; + // Given a row # and an array of segments all in the same row, render a element, a skeleton that contains + // the segments. Returns object with a bunch of internal data about how the render was calculated. + // NOTE: modifies rowSegs + DayGridEventRenderer.prototype.renderSegRow = function (row, rowSegs) { + var dayGrid = this.dayGrid; + var colCnt = dayGrid.colCnt, isRtl = dayGrid.isRtl; + var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels + var levelCnt = Math.max(1, segLevels.length); // ensure at least one level + var tbody = document.createElement('tbody'); + var segMatrix = []; // lookup for which segments are rendered into which level+col cells + var cellMatrix = []; // lookup for all elements of the level+col matrix + var loneCellMatrix = []; // lookup for elements that only take up a single column + var i; + var levelSegs; + var col; + var tr; + var j; + var seg; + var td; + // populates empty cells from the current column (`col`) to `endCol` + function emptyCellsUntil(endCol) { + while (col < endCol) { + // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell + td = (loneCellMatrix[i - 1] || [])[col]; + if (td) { + td.rowSpan = (td.rowSpan || 1) + 1; + } + else { + td = document.createElement('td'); + tr.appendChild(td); + } + cellMatrix[i][col] = td; + loneCellMatrix[i][col] = td; + col++; + } + } + for (i = 0; i < levelCnt; i++) { // iterate through all levels + levelSegs = segLevels[i]; + col = 0; + tr = document.createElement('tr'); + segMatrix.push([]); + cellMatrix.push([]); + loneCellMatrix.push([]); + // levelCnt might be 1 even though there are no actual levels. protect against this. + // this single empty row is useful for styling. + if (levelSegs) { + for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level + seg = levelSegs[j]; + var leftCol = isRtl ? (colCnt - 1 - seg.lastCol) : seg.firstCol; + var rightCol = isRtl ? (colCnt - 1 - seg.firstCol) : seg.lastCol; + emptyCellsUntil(leftCol); + // create a container that occupies or more columns. append the event element. + td = core.createElement('td', { className: 'fc-event-container' }, seg.el); + if (leftCol !== rightCol) { + td.colSpan = rightCol - leftCol + 1; + } + else { // a single-column segment + loneCellMatrix[i][col] = td; + } + while (col <= rightCol) { + cellMatrix[i][col] = td; + segMatrix[i][col] = seg; + col++; + } + tr.appendChild(td); + } + } + emptyCellsUntil(colCnt); // finish off the row + var introHtml = dayGrid.renderProps.renderIntroHtml(); + if (introHtml) { + if (dayGrid.isRtl) { + core.appendToElement(tr, introHtml); + } + else { + core.prependToElement(tr, introHtml); + } + } + tbody.appendChild(tr); + } + return { + row: row, + tbodyEl: tbody, + cellMatrix: cellMatrix, + segMatrix: segMatrix, + segLevels: segLevels, + segs: rowSegs + }; + }; + // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels. + // NOTE: modifies segs + DayGridEventRenderer.prototype.buildSegLevels = function (segs) { + var _a = this.dayGrid, isRtl = _a.isRtl, colCnt = _a.colCnt; + var levels = []; + var i; + var seg; + var j; + // Give preference to elements with certain criteria, so they have + // a chance to be closer to the top. + segs = this.sortEventSegs(segs); + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + // loop through levels, starting with the topmost, until the segment doesn't collide with other segments + for (j = 0; j < levels.length; j++) { + if (!isDaySegCollision(seg, levels[j])) { + break; + } + } + // `j` now holds the desired subrow index + seg.level = j; + seg.leftCol = isRtl ? (colCnt - 1 - seg.lastCol) : seg.firstCol; // for sorting only + seg.rightCol = isRtl ? (colCnt - 1 - seg.firstCol) : seg.lastCol // for sorting only + ; + (levels[j] || (levels[j] = [])).push(seg); + } + // order segments left-to-right. very important if calendar is RTL + for (j = 0; j < levels.length; j++) { + levels[j].sort(compareDaySegCols); + } + return levels; + }; + // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row + DayGridEventRenderer.prototype.groupSegRows = function (segs) { + var segRows = []; + var i; + for (i = 0; i < this.dayGrid.rowCnt; i++) { + segRows.push([]); + } + for (i = 0; i < segs.length; i++) { + segRows[segs[i].row].push(segs[i]); + } + return segRows; + }; + // Computes a default `displayEventEnd` value if one is not expliclty defined + DayGridEventRenderer.prototype.computeDisplayEventEnd = function () { + return this.dayGrid.colCnt === 1; // we'll likely have space if there's only one day + }; + return DayGridEventRenderer; + }(SimpleDayGridEventRenderer)); + // Computes whether two segments' columns collide. They are assumed to be in the same row. + function isDaySegCollision(seg, otherSegs) { + var i; + var otherSeg; + for (i = 0; i < otherSegs.length; i++) { + otherSeg = otherSegs[i]; + if (otherSeg.firstCol <= seg.lastCol && + otherSeg.lastCol >= seg.firstCol) { + return true; + } + } + return false; + } + // A cmp function for determining the leftmost event + function compareDaySegCols(a, b) { + return a.leftCol - b.leftCol; + } + + var DayGridMirrorRenderer = /** @class */ (function (_super) { + __extends(DayGridMirrorRenderer, _super); + function DayGridMirrorRenderer() { + return _super !== null && _super.apply(this, arguments) || this; + } + DayGridMirrorRenderer.prototype.attachSegs = function (segs, mirrorInfo) { + var sourceSeg = mirrorInfo.sourceSeg; + var rowStructs = this.rowStructs = this.renderSegRows(segs); + // inject each new event skeleton into each associated row + this.dayGrid.rowEls.forEach(function (rowNode, row) { + var skeletonEl = core.htmlToElement('
'); // will be absolutely positioned + var skeletonTopEl; + var skeletonTop; + // If there is an original segment, match the top position. Otherwise, put it at the row's top level + if (sourceSeg && sourceSeg.row === row) { + skeletonTopEl = sourceSeg.el; + } + else { + skeletonTopEl = rowNode.querySelector('.fc-content-skeleton tbody'); + if (!skeletonTopEl) { // when no events + skeletonTopEl = rowNode.querySelector('.fc-content-skeleton table'); + } + } + skeletonTop = skeletonTopEl.getBoundingClientRect().top - + rowNode.getBoundingClientRect().top; // the offsetParent origin + skeletonEl.style.top = skeletonTop + 'px'; + skeletonEl.querySelector('table').appendChild(rowStructs[row].tbodyEl); + rowNode.appendChild(skeletonEl); + }); + }; + return DayGridMirrorRenderer; + }(DayGridEventRenderer)); + + var DayGridFillRenderer = /** @class */ (function (_super) { + __extends(DayGridFillRenderer, _super); + function DayGridFillRenderer(dayGrid) { + var _this = _super.call(this, dayGrid.context) || this; + _this.fillSegTag = 'td'; // override the default tag name + _this.dayGrid = dayGrid; + return _this; + } + DayGridFillRenderer.prototype.renderSegs = function (type, segs) { + // don't render timed background events + if (type === 'bgEvent') { + segs = segs.filter(function (seg) { + return seg.eventRange.def.allDay; + }); + } + _super.prototype.renderSegs.call(this, type, segs); + }; + DayGridFillRenderer.prototype.attachSegs = function (type, segs) { + var els = []; + var i; + var seg; + var skeletonEl; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + skeletonEl = this.renderFillRow(type, seg); + this.dayGrid.rowEls[seg.row].appendChild(skeletonEl); + els.push(skeletonEl); + } + return els; + }; + // Generates the HTML needed for one row of a fill. Requires the seg's el to be rendered. + DayGridFillRenderer.prototype.renderFillRow = function (type, seg) { + var dayGrid = this.dayGrid; + var colCnt = dayGrid.colCnt, isRtl = dayGrid.isRtl; + var leftCol = isRtl ? (colCnt - 1 - seg.lastCol) : seg.firstCol; + var rightCol = isRtl ? (colCnt - 1 - seg.firstCol) : seg.lastCol; + var startCol = leftCol; + var endCol = rightCol + 1; + var className; + var skeletonEl; + var trEl; + if (type === 'businessHours') { + className = 'bgevent'; + } + else { + className = type.toLowerCase(); + } + skeletonEl = core.htmlToElement('
' + + '
' + + '
'); + trEl = skeletonEl.getElementsByTagName('tr')[0]; + if (startCol > 0) { + core.appendToElement(trEl, + // will create (startCol + 1) td's + new Array(startCol + 1).join('')); + } + seg.el.colSpan = endCol - startCol; + trEl.appendChild(seg.el); + if (endCol < colCnt) { + core.appendToElement(trEl, + // will create (colCnt - endCol) td's + new Array(colCnt - endCol + 1).join('')); + } + var introHtml = dayGrid.renderProps.renderIntroHtml(); + if (introHtml) { + if (dayGrid.isRtl) { + core.appendToElement(trEl, introHtml); + } + else { + core.prependToElement(trEl, introHtml); + } + } + return skeletonEl; + }; + return DayGridFillRenderer; + }(core.FillRenderer)); + + var DayTile = /** @class */ (function (_super) { + __extends(DayTile, _super); + function DayTile(context, el) { + var _this = _super.call(this, context, el) || this; + var eventRenderer = _this.eventRenderer = new DayTileEventRenderer(_this); + var renderFrame = _this.renderFrame = core.memoizeRendering(_this._renderFrame); + _this.renderFgEvents = core.memoizeRendering(eventRenderer.renderSegs.bind(eventRenderer), eventRenderer.unrender.bind(eventRenderer), [renderFrame]); + _this.renderEventSelection = core.memoizeRendering(eventRenderer.selectByInstanceId.bind(eventRenderer), eventRenderer.unselectByInstanceId.bind(eventRenderer), [_this.renderFgEvents]); + _this.renderEventDrag = core.memoizeRendering(eventRenderer.hideByHash.bind(eventRenderer), eventRenderer.showByHash.bind(eventRenderer), [renderFrame]); + _this.renderEventResize = core.memoizeRendering(eventRenderer.hideByHash.bind(eventRenderer), eventRenderer.showByHash.bind(eventRenderer), [renderFrame]); + context.calendar.registerInteractiveComponent(_this, { + el: _this.el, + useEventCenter: false + }); + return _this; + } + DayTile.prototype.render = function (props) { + this.renderFrame(props.date); + this.renderFgEvents(props.fgSegs); + this.renderEventSelection(props.eventSelection); + this.renderEventDrag(props.eventDragInstances); + this.renderEventResize(props.eventResizeInstances); + }; + DayTile.prototype.destroy = function () { + _super.prototype.destroy.call(this); + this.renderFrame.unrender(); // should unrender everything else + this.calendar.unregisterInteractiveComponent(this); + }; + DayTile.prototype._renderFrame = function (date) { + var _a = this, theme = _a.theme, dateEnv = _a.dateEnv; + var title = dateEnv.format(date, core.createFormatter(this.opt('dayPopoverFormat')) // TODO: cache + ); + this.el.innerHTML = + '
' + + '' + + core.htmlEscape(title) + + '' + + '' + + '
' + + '
' + + '
' + + '
'; + this.segContainerEl = this.el.querySelector('.fc-event-container'); + }; + DayTile.prototype.queryHit = function (positionLeft, positionTop, elWidth, elHeight) { + var date = this.props.date; // HACK + if (positionLeft < elWidth && positionTop < elHeight) { + return { + component: this, + dateSpan: { + allDay: true, + range: { start: date, end: core.addDays(date, 1) } + }, + dayEl: this.el, + rect: { + left: 0, + top: 0, + right: elWidth, + bottom: elHeight + }, + layer: 1 + }; + } + }; + return DayTile; + }(core.DateComponent)); + var DayTileEventRenderer = /** @class */ (function (_super) { + __extends(DayTileEventRenderer, _super); + function DayTileEventRenderer(dayTile) { + var _this = _super.call(this, dayTile.context) || this; + _this.dayTile = dayTile; + return _this; + } + DayTileEventRenderer.prototype.attachSegs = function (segs) { + for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) { + var seg = segs_1[_i]; + this.dayTile.segContainerEl.appendChild(seg.el); + } + }; + DayTileEventRenderer.prototype.detachSegs = function (segs) { + for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) { + var seg = segs_2[_i]; + core.removeElement(seg.el); + } + }; + return DayTileEventRenderer; + }(SimpleDayGridEventRenderer)); + + var DayBgRow = /** @class */ (function () { + function DayBgRow(context) { + this.context = context; + } + DayBgRow.prototype.renderHtml = function (props) { + var parts = []; + if (props.renderIntroHtml) { + parts.push(props.renderIntroHtml()); + } + for (var _i = 0, _a = props.cells; _i < _a.length; _i++) { + var cell = _a[_i]; + parts.push(renderCellHtml(cell.date, props.dateProfile, this.context, cell.htmlAttrs)); + } + if (!props.cells.length) { + parts.push(''); + } + if (this.context.options.dir === 'rtl') { + parts.reverse(); + } + return '' + parts.join('') + ''; + }; + return DayBgRow; + }()); + function renderCellHtml(date, dateProfile, context, otherAttrs) { + var dateEnv = context.dateEnv, theme = context.theme; + var isDateValid = core.rangeContainsMarker(dateProfile.activeRange, date); // TODO: called too frequently. cache somehow. + var classes = core.getDayClasses(date, dateProfile, context); + classes.unshift('fc-day', theme.getClass('widgetContent')); + return ''; + } + + var DAY_NUM_FORMAT = core.createFormatter({ day: 'numeric' }); + var WEEK_NUM_FORMAT = core.createFormatter({ week: 'numeric' }); + var DayGrid = /** @class */ (function (_super) { + __extends(DayGrid, _super); + function DayGrid(context, el, renderProps) { + var _this = _super.call(this, context, el) || this; + _this.bottomCoordPadding = 0; // hack for extending the hit area for the last row of the coordinate grid + _this.isCellSizesDirty = false; + var eventRenderer = _this.eventRenderer = new DayGridEventRenderer(_this); + var fillRenderer = _this.fillRenderer = new DayGridFillRenderer(_this); + _this.mirrorRenderer = new DayGridMirrorRenderer(_this); + var renderCells = _this.renderCells = core.memoizeRendering(_this._renderCells, _this._unrenderCells); + _this.renderBusinessHours = core.memoizeRendering(fillRenderer.renderSegs.bind(fillRenderer, 'businessHours'), fillRenderer.unrender.bind(fillRenderer, 'businessHours'), [renderCells]); + _this.renderDateSelection = core.memoizeRendering(fillRenderer.renderSegs.bind(fillRenderer, 'highlight'), fillRenderer.unrender.bind(fillRenderer, 'highlight'), [renderCells]); + _this.renderBgEvents = core.memoizeRendering(fillRenderer.renderSegs.bind(fillRenderer, 'bgEvent'), fillRenderer.unrender.bind(fillRenderer, 'bgEvent'), [renderCells]); + _this.renderFgEvents = core.memoizeRendering(eventRenderer.renderSegs.bind(eventRenderer), eventRenderer.unrender.bind(eventRenderer), [renderCells]); + _this.renderEventSelection = core.memoizeRendering(eventRenderer.selectByInstanceId.bind(eventRenderer), eventRenderer.unselectByInstanceId.bind(eventRenderer), [_this.renderFgEvents]); + _this.renderEventDrag = core.memoizeRendering(_this._renderEventDrag, _this._unrenderEventDrag, [renderCells]); + _this.renderEventResize = core.memoizeRendering(_this._renderEventResize, _this._unrenderEventResize, [renderCells]); + _this.renderProps = renderProps; + return _this; + } + DayGrid.prototype.render = function (props) { + var cells = props.cells; + this.rowCnt = cells.length; + this.colCnt = cells[0].length; + this.renderCells(cells, props.isRigid); + this.renderBusinessHours(props.businessHourSegs); + this.renderDateSelection(props.dateSelectionSegs); + this.renderBgEvents(props.bgEventSegs); + this.renderFgEvents(props.fgEventSegs); + this.renderEventSelection(props.eventSelection); + this.renderEventDrag(props.eventDrag); + this.renderEventResize(props.eventResize); + if (this.segPopoverTile) { + this.updateSegPopoverTile(); + } + }; + DayGrid.prototype.destroy = function () { + _super.prototype.destroy.call(this); + this.renderCells.unrender(); // will unrender everything else + }; + DayGrid.prototype.getCellRange = function (row, col) { + var start = this.props.cells[row][col].date; + var end = core.addDays(start, 1); + return { start: start, end: end }; + }; + DayGrid.prototype.updateSegPopoverTile = function (date, segs) { + var ownProps = this.props; + this.segPopoverTile.receiveProps({ + date: date || this.segPopoverTile.props.date, + fgSegs: segs || this.segPopoverTile.props.fgSegs, + eventSelection: ownProps.eventSelection, + eventDragInstances: ownProps.eventDrag ? ownProps.eventDrag.affectedInstances : null, + eventResizeInstances: ownProps.eventResize ? ownProps.eventResize.affectedInstances : null + }); + }; + /* Date Rendering + ------------------------------------------------------------------------------------------------------------------*/ + DayGrid.prototype._renderCells = function (cells, isRigid) { + var _a = this, view = _a.view, dateEnv = _a.dateEnv; + var _b = this, rowCnt = _b.rowCnt, colCnt = _b.colCnt; + var html = ''; + var row; + var col; + for (row = 0; row < rowCnt; row++) { + html += this.renderDayRowHtml(row, isRigid); + } + this.el.innerHTML = html; + this.rowEls = core.findElements(this.el, '.fc-row'); + this.cellEls = core.findElements(this.el, '.fc-day, .fc-disabled-day'); + if (this.isRtl) { + this.cellEls.reverse(); + } + this.rowPositions = new core.PositionCache(this.el, this.rowEls, false, true // vertical + ); + this.colPositions = new core.PositionCache(this.el, this.cellEls.slice(0, colCnt), // only the first row + true, false // horizontal + ); + // trigger dayRender with each cell's element + for (row = 0; row < rowCnt; row++) { + for (col = 0; col < colCnt; col++) { + this.publiclyTrigger('dayRender', [ + { + date: dateEnv.toDate(cells[row][col].date), + el: this.getCellEl(row, col), + view: view + } + ]); + } + } + this.isCellSizesDirty = true; + }; + DayGrid.prototype._unrenderCells = function () { + this.removeSegPopover(); + }; + // Generates the HTML for a single row, which is a div that wraps a table. + // `row` is the row number. + DayGrid.prototype.renderDayRowHtml = function (row, isRigid) { + var theme = this.theme; + var classes = ['fc-row', 'fc-week', theme.getClass('dayRow')]; + if (isRigid) { + classes.push('fc-rigid'); + } + var bgRow = new DayBgRow(this.context); + return '' + + '
' + + '
' + + '' + + bgRow.renderHtml({ + cells: this.props.cells[row], + dateProfile: this.props.dateProfile, + renderIntroHtml: this.renderProps.renderBgIntroHtml + }) + + '
' + + '
' + + '
' + + '' + + (this.getIsNumbersVisible() ? + '' + + this.renderNumberTrHtml(row) + + '' : + '') + + '
' + + '
' + + '
'; + }; + DayGrid.prototype.getIsNumbersVisible = function () { + return this.getIsDayNumbersVisible() || + this.renderProps.cellWeekNumbersVisible || + this.renderProps.colWeekNumbersVisible; + }; + DayGrid.prototype.getIsDayNumbersVisible = function () { + return this.rowCnt > 1; + }; + /* Grid Number Rendering + ------------------------------------------------------------------------------------------------------------------*/ + DayGrid.prototype.renderNumberTrHtml = function (row) { + var intro = this.renderProps.renderNumberIntroHtml(row, this); + return '' + + '' + + (this.isRtl ? '' : intro) + + this.renderNumberCellsHtml(row) + + (this.isRtl ? intro : '') + + ''; + }; + DayGrid.prototype.renderNumberCellsHtml = function (row) { + var htmls = []; + var col; + var date; + for (col = 0; col < this.colCnt; col++) { + date = this.props.cells[row][col].date; + htmls.push(this.renderNumberCellHtml(date)); + } + if (this.isRtl) { + htmls.reverse(); + } + return htmls.join(''); + }; + // Generates the HTML for the s of the "number" row in the DayGrid's content skeleton. + // The number row will only exist if either day numbers or week numbers are turned on. + DayGrid.prototype.renderNumberCellHtml = function (date) { + var _a = this, view = _a.view, dateEnv = _a.dateEnv; + var html = ''; + var isDateValid = core.rangeContainsMarker(this.props.dateProfile.activeRange, date); // TODO: called too frequently. cache somehow. + var isDayNumberVisible = this.getIsDayNumbersVisible() && isDateValid; + var classes; + var weekCalcFirstDow; + if (!isDayNumberVisible && !this.renderProps.cellWeekNumbersVisible) { + // no numbers in day cell (week number must be along the side) + return ''; // will create an empty space above events :( + } + classes = core.getDayClasses(date, this.props.dateProfile, this.context); + classes.unshift('fc-day-top'); + if (this.renderProps.cellWeekNumbersVisible) { + weekCalcFirstDow = dateEnv.weekDow; + } + html += ''; + if (this.renderProps.cellWeekNumbersVisible && (date.getUTCDay() === weekCalcFirstDow)) { + html += core.buildGotoAnchorHtml(view, { date: date, type: 'week' }, { 'class': 'fc-week-number' }, dateEnv.format(date, WEEK_NUM_FORMAT) // inner HTML + ); + } + if (isDayNumberVisible) { + html += core.buildGotoAnchorHtml(view, date, { 'class': 'fc-day-number' }, dateEnv.format(date, DAY_NUM_FORMAT) // inner HTML + ); + } + html += ''; + return html; + }; + /* Sizing + ------------------------------------------------------------------------------------------------------------------*/ + DayGrid.prototype.updateSize = function (isResize) { + var _a = this, fillRenderer = _a.fillRenderer, eventRenderer = _a.eventRenderer, mirrorRenderer = _a.mirrorRenderer; + if (isResize || this.isCellSizesDirty) { + this.buildColPositions(); + this.buildRowPositions(); + this.isCellSizesDirty = false; + } + fillRenderer.computeSizes(isResize); + eventRenderer.computeSizes(isResize); + mirrorRenderer.computeSizes(isResize); + fillRenderer.assignSizes(isResize); + eventRenderer.assignSizes(isResize); + mirrorRenderer.assignSizes(isResize); + }; + DayGrid.prototype.buildColPositions = function () { + this.colPositions.build(); + }; + DayGrid.prototype.buildRowPositions = function () { + this.rowPositions.build(); + this.rowPositions.bottoms[this.rowCnt - 1] += this.bottomCoordPadding; // hack + }; + /* Hit System + ------------------------------------------------------------------------------------------------------------------*/ + DayGrid.prototype.positionToHit = function (leftPosition, topPosition) { + var _a = this, colPositions = _a.colPositions, rowPositions = _a.rowPositions; + var col = colPositions.leftToIndex(leftPosition); + var row = rowPositions.topToIndex(topPosition); + if (row != null && col != null) { + return { + row: row, + col: col, + dateSpan: { + range: this.getCellRange(row, col), + allDay: true + }, + dayEl: this.getCellEl(row, col), + relativeRect: { + left: colPositions.lefts[col], + right: colPositions.rights[col], + top: rowPositions.tops[row], + bottom: rowPositions.bottoms[row] + } + }; + } + }; + /* Cell System + ------------------------------------------------------------------------------------------------------------------*/ + // FYI: the first column is the leftmost column, regardless of date + DayGrid.prototype.getCellEl = function (row, col) { + return this.cellEls[row * this.colCnt + col]; + }; + /* Event Drag Visualization + ------------------------------------------------------------------------------------------------------------------*/ + DayGrid.prototype._renderEventDrag = function (state) { + if (state) { + this.eventRenderer.hideByHash(state.affectedInstances); + this.fillRenderer.renderSegs('highlight', state.segs); + } + }; + DayGrid.prototype._unrenderEventDrag = function (state) { + if (state) { + this.eventRenderer.showByHash(state.affectedInstances); + this.fillRenderer.unrender('highlight'); + } + }; + /* Event Resize Visualization + ------------------------------------------------------------------------------------------------------------------*/ + DayGrid.prototype._renderEventResize = function (state) { + if (state) { + this.eventRenderer.hideByHash(state.affectedInstances); + this.fillRenderer.renderSegs('highlight', state.segs); + this.mirrorRenderer.renderSegs(state.segs, { isResizing: true, sourceSeg: state.sourceSeg }); + } + }; + DayGrid.prototype._unrenderEventResize = function (state) { + if (state) { + this.eventRenderer.showByHash(state.affectedInstances); + this.fillRenderer.unrender('highlight'); + this.mirrorRenderer.unrender(state.segs, { isResizing: true, sourceSeg: state.sourceSeg }); + } + }; + /* More+ Link Popover + ------------------------------------------------------------------------------------------------------------------*/ + DayGrid.prototype.removeSegPopover = function () { + if (this.segPopover) { + this.segPopover.hide(); // in handler, will call segPopover's removeElement + } + }; + // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid. + // `levelLimit` can be false (don't limit), a number, or true (should be computed). + DayGrid.prototype.limitRows = function (levelLimit) { + var rowStructs = this.eventRenderer.rowStructs || []; + var row; // row # + var rowLevelLimit; + for (row = 0; row < rowStructs.length; row++) { + this.unlimitRow(row); + if (!levelLimit) { + rowLevelLimit = false; + } + else if (typeof levelLimit === 'number') { + rowLevelLimit = levelLimit; + } + else { + rowLevelLimit = this.computeRowLevelLimit(row); + } + if (rowLevelLimit !== false) { + this.limitRow(row, rowLevelLimit); + } + } + }; + // Computes the number of levels a row will accomodate without going outside its bounds. + // Assumes the row is "rigid" (maintains a constant height regardless of what is inside). + // `row` is the row number. + DayGrid.prototype.computeRowLevelLimit = function (row) { + var rowEl = this.rowEls[row]; // the containing "fake" row div + var rowBottom = rowEl.getBoundingClientRect().bottom; // relative to viewport! + var trEls = core.findChildren(this.eventRenderer.rowStructs[row].tbodyEl); + var i; + var trEl; + // Reveal one level at a time and stop when we find one out of bounds + for (i = 0; i < trEls.length; i++) { + trEl = trEls[i]; + trEl.classList.remove('fc-limited'); // reset to original state (reveal) + if (trEl.getBoundingClientRect().bottom > rowBottom) { + return i; + } + } + return false; // should not limit at all + }; + // Limits the given grid row to the maximum number of levels and injects "more" links if necessary. + // `row` is the row number. + // `levelLimit` is a number for the maximum (inclusive) number of levels allowed. + DayGrid.prototype.limitRow = function (row, levelLimit) { + var _this = this; + var _a = this, colCnt = _a.colCnt, isRtl = _a.isRtl; + var rowStruct = this.eventRenderer.rowStructs[row]; + var moreNodes = []; // array of "more" links and DOM nodes + var col = 0; // col #, left-to-right (not chronologically) + var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right + var cellMatrix; // a matrix (by level, then column) of all elements in the row + var limitedNodes; // array of temporarily hidden level and segment DOM nodes + var i; + var seg; + var segsBelow; // array of segment objects below `seg` in the current `col` + var totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies + var colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column) + var td; + var rowSpan; + var segMoreNodes; // array of "more" cells that will stand-in for the current seg's cell + var j; + var moreTd; + var moreWrap; + var moreLink; + // Iterates through empty level cells and places "more" links inside if need be + var emptyCellsUntil = function (endCol) { + while (col < endCol) { + segsBelow = _this.getCellSegs(row, col, levelLimit); + if (segsBelow.length) { + td = cellMatrix[levelLimit - 1][col]; + moreLink = _this.renderMoreLink(row, col, segsBelow); + moreWrap = core.createElement('div', null, moreLink); + td.appendChild(moreWrap); + moreNodes.push(moreWrap[0]); + } + col++; + } + }; + if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit? + levelSegs = rowStruct.segLevels[levelLimit - 1]; + cellMatrix = rowStruct.cellMatrix; + limitedNodes = core.findChildren(rowStruct.tbodyEl).slice(levelLimit); // get level elements past the limit + limitedNodes.forEach(function (node) { + node.classList.add('fc-limited'); // hide elements and get a simple DOM-nodes array + }); + // iterate though segments in the last allowable level + for (i = 0; i < levelSegs.length; i++) { + seg = levelSegs[i]; + var leftCol = isRtl ? (colCnt - 1 - seg.lastCol) : seg.firstCol; + var rightCol = isRtl ? (colCnt - 1 - seg.firstCol) : seg.lastCol; + emptyCellsUntil(leftCol); // process empty cells before the segment + // determine *all* segments below `seg` that occupy the same columns + colSegsBelow = []; + totalSegsBelow = 0; + while (col <= rightCol) { + segsBelow = this.getCellSegs(row, col, levelLimit); + colSegsBelow.push(segsBelow); + totalSegsBelow += segsBelow.length; + col++; + } + if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links? + td = cellMatrix[levelLimit - 1][leftCol]; // the segment's parent cell + rowSpan = td.rowSpan || 1; + segMoreNodes = []; + // make a replacement for each column the segment occupies. will be one for each colspan + for (j = 0; j < colSegsBelow.length; j++) { + moreTd = core.createElement('td', { className: 'fc-more-cell', rowSpan: rowSpan }); + segsBelow = colSegsBelow[j]; + moreLink = this.renderMoreLink(row, leftCol + j, [seg].concat(segsBelow) // count seg as hidden too + ); + moreWrap = core.createElement('div', null, moreLink); + moreTd.appendChild(moreWrap); + segMoreNodes.push(moreTd); + moreNodes.push(moreTd); + } + td.classList.add('fc-limited'); + core.insertAfterElement(td, segMoreNodes); + limitedNodes.push(td); + } + } + emptyCellsUntil(this.colCnt); // finish off the level + rowStruct.moreEls = moreNodes; // for easy undoing later + rowStruct.limitedEls = limitedNodes; // for easy undoing later + } + }; + // Reveals all levels and removes all "more"-related elements for a grid's row. + // `row` is a row number. + DayGrid.prototype.unlimitRow = function (row) { + var rowStruct = this.eventRenderer.rowStructs[row]; + if (rowStruct.moreEls) { + rowStruct.moreEls.forEach(core.removeElement); + rowStruct.moreEls = null; + } + if (rowStruct.limitedEls) { + rowStruct.limitedEls.forEach(function (limitedEl) { + limitedEl.classList.remove('fc-limited'); + }); + rowStruct.limitedEls = null; + } + }; + // Renders an element that represents hidden event element for a cell. + // Responsible for attaching click handler as well. + DayGrid.prototype.renderMoreLink = function (row, col, hiddenSegs) { + var _this = this; + var _a = this, view = _a.view, dateEnv = _a.dateEnv; + var a = core.createElement('a', { className: 'fc-more' }); + a.innerText = this.getMoreLinkText(hiddenSegs.length); + a.addEventListener('click', function (ev) { + var clickOption = _this.opt('eventLimitClick'); + var _col = _this.isRtl ? _this.colCnt - col - 1 : col; // HACK: props.cells has different dir system? + var date = _this.props.cells[row][_col].date; + var moreEl = ev.currentTarget; + var dayEl = _this.getCellEl(row, col); + var allSegs = _this.getCellSegs(row, col); + // rescope the segments to be within the cell's date + var reslicedAllSegs = _this.resliceDaySegs(allSegs, date); + var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date); + if (typeof clickOption === 'function') { + // the returned value can be an atomic option + clickOption = _this.publiclyTrigger('eventLimitClick', [ + { + date: dateEnv.toDate(date), + allDay: true, + dayEl: dayEl, + moreEl: moreEl, + segs: reslicedAllSegs, + hiddenSegs: reslicedHiddenSegs, + jsEvent: ev, + view: view + } + ]); + } + if (clickOption === 'popover') { + _this.showSegPopover(row, col, moreEl, reslicedAllSegs); + } + else if (typeof clickOption === 'string') { // a view name + view.calendar.zoomTo(date, clickOption); + } + }); + return a; + }; + // Reveals the popover that displays all events within a cell + DayGrid.prototype.showSegPopover = function (row, col, moreLink, segs) { + var _this = this; + var _a = this, calendar = _a.calendar, view = _a.view, theme = _a.theme; + var _col = this.isRtl ? this.colCnt - col - 1 : col; // HACK: props.cells has different dir system? + var moreWrap = moreLink.parentNode; // the
wrapper around the + var topEl; // the element we want to match the top coordinate of + var options; + if (this.rowCnt === 1) { + topEl = view.el; // will cause the popover to cover any sort of header + } + else { + topEl = this.rowEls[row]; // will align with top of row + } + options = { + className: 'fc-more-popover ' + theme.getClass('popover'), + parentEl: view.el, + top: core.computeRect(topEl).top, + autoHide: true, + content: function (el) { + _this.segPopoverTile = new DayTile(_this.context, el); + _this.updateSegPopoverTile(_this.props.cells[row][_col].date, segs); + }, + hide: function () { + _this.segPopoverTile.destroy(); + _this.segPopoverTile = null; + _this.segPopover.destroy(); + _this.segPopover = null; + } + }; + // Determine horizontal coordinate. + // We use the moreWrap instead of the to avoid border confusion. + if (this.isRtl) { + options.right = core.computeRect(moreWrap).right + 1; // +1 to be over cell border + } + else { + options.left = core.computeRect(moreWrap).left - 1; // -1 to be over cell border + } + this.segPopover = new Popover(options); + this.segPopover.show(); + calendar.releaseAfterSizingTriggers(); // hack for eventPositioned + }; + // Given the events within an array of segment objects, reslice them to be in a single day + DayGrid.prototype.resliceDaySegs = function (segs, dayDate) { + var dayStart = dayDate; + var dayEnd = core.addDays(dayStart, 1); + var dayRange = { start: dayStart, end: dayEnd }; + var newSegs = []; + for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) { + var seg = segs_1[_i]; + var eventRange = seg.eventRange; + var origRange = eventRange.range; + var slicedRange = core.intersectRanges(origRange, dayRange); + if (slicedRange) { + newSegs.push(__assign({}, seg, { eventRange: { + def: eventRange.def, + ui: __assign({}, eventRange.ui, { durationEditable: false }), + instance: eventRange.instance, + range: slicedRange + }, isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(), isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf() })); + } + } + return newSegs; + }; + // Generates the text that should be inside a "more" link, given the number of events it represents + DayGrid.prototype.getMoreLinkText = function (num) { + var opt = this.opt('eventLimitText'); + if (typeof opt === 'function') { + return opt(num); + } + else { + return '+' + num + ' ' + opt; + } + }; + // Returns segments within a given cell. + // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs. + DayGrid.prototype.getCellSegs = function (row, col, startLevel) { + var segMatrix = this.eventRenderer.rowStructs[row].segMatrix; + var level = startLevel || 0; + var segs = []; + var seg; + while (level < segMatrix.length) { + seg = segMatrix[level][col]; + if (seg) { + segs.push(seg); + } + level++; + } + return segs; + }; + return DayGrid; + }(core.DateComponent)); + + var WEEK_NUM_FORMAT$1 = core.createFormatter({ week: 'numeric' }); + /* An abstract class for the daygrid views, as well as month view. Renders one or more rows of day cells. + ----------------------------------------------------------------------------------------------------------------------*/ + // It is a manager for a DayGrid subcomponent, which does most of the heavy lifting. + // It is responsible for managing width/height. + var DayGridView = /** @class */ (function (_super) { + __extends(DayGridView, _super); + function DayGridView(context, viewSpec, dateProfileGenerator, parentEl) { + var _this = _super.call(this, context, viewSpec, dateProfileGenerator, parentEl) || this; + /* Header Rendering + ------------------------------------------------------------------------------------------------------------------*/ + // Generates the HTML that will go before the day-of week header cells + _this.renderHeadIntroHtml = function () { + var theme = _this.theme; + if (_this.colWeekNumbersVisible) { + return '' + + '' + + '' + // needed for matchCellWidths + core.htmlEscape(_this.opt('weekLabel')) + + '' + + ''; + } + return ''; + }; + /* Day Grid Rendering + ------------------------------------------------------------------------------------------------------------------*/ + // Generates the HTML that will go before content-skeleton cells that display the day/week numbers + _this.renderDayGridNumberIntroHtml = function (row, dayGrid) { + var dateEnv = _this.dateEnv; + var weekStart = dayGrid.props.cells[row][0].date; + if (_this.colWeekNumbersVisible) { + return '' + + '' + + core.buildGotoAnchorHtml(// aside from link, important for matchCellWidths + _this, { date: weekStart, type: 'week', forceOff: dayGrid.colCnt === 1 }, dateEnv.format(weekStart, WEEK_NUM_FORMAT$1) // inner HTML + ) + + ''; + } + return ''; + }; + // Generates the HTML that goes before the day bg cells for each day-row + _this.renderDayGridBgIntroHtml = function () { + var theme = _this.theme; + if (_this.colWeekNumbersVisible) { + return ''; + } + return ''; + }; + // Generates the HTML that goes before every other type of row generated by DayGrid. + // Affects mirror-skeleton and highlight-skeleton rows. + _this.renderDayGridIntroHtml = function () { + if (_this.colWeekNumbersVisible) { + return ''; + } + return ''; + }; + _this.el.classList.add('fc-dayGrid-view'); + _this.el.innerHTML = _this.renderSkeletonHtml(); + _this.scroller = new core.ScrollComponent('hidden', // overflow x + 'auto' // overflow y + ); + var dayGridContainerEl = _this.scroller.el; + _this.el.querySelector('.fc-body > tr > td').appendChild(dayGridContainerEl); + dayGridContainerEl.classList.add('fc-day-grid-container'); + var dayGridEl = core.createElement('div', { className: 'fc-day-grid' }); + dayGridContainerEl.appendChild(dayGridEl); + var cellWeekNumbersVisible; + if (_this.opt('weekNumbers')) { + if (_this.opt('weekNumbersWithinDays')) { + cellWeekNumbersVisible = true; + _this.colWeekNumbersVisible = false; + } + else { + cellWeekNumbersVisible = false; + _this.colWeekNumbersVisible = true; + } + } + else { + _this.colWeekNumbersVisible = false; + cellWeekNumbersVisible = false; + } + _this.dayGrid = new DayGrid(_this.context, dayGridEl, { + renderNumberIntroHtml: _this.renderDayGridNumberIntroHtml, + renderBgIntroHtml: _this.renderDayGridBgIntroHtml, + renderIntroHtml: _this.renderDayGridIntroHtml, + colWeekNumbersVisible: _this.colWeekNumbersVisible, + cellWeekNumbersVisible: cellWeekNumbersVisible + }); + return _this; + } + DayGridView.prototype.destroy = function () { + _super.prototype.destroy.call(this); + this.dayGrid.destroy(); + this.scroller.destroy(); + }; + // Builds the HTML skeleton for the view. + // The day-grid component will render inside of a container defined by this HTML. + DayGridView.prototype.renderSkeletonHtml = function () { + var theme = this.theme; + return '' + + '' + + (this.opt('columnHeader') ? + '' + + '' + + '' + + '' + + '' : + '') + + '' + + '' + + '' + + '' + + '' + + '
 
'; + }; + // Generates an HTML attribute string for setting the width of the week number column, if it is known + DayGridView.prototype.weekNumberStyleAttr = function () { + if (this.weekNumberWidth != null) { + return 'style="width:' + this.weekNumberWidth + 'px"'; + } + return ''; + }; + // Determines whether each row should have a constant height + DayGridView.prototype.hasRigidRows = function () { + var eventLimit = this.opt('eventLimit'); + return eventLimit && typeof eventLimit !== 'number'; + }; + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + DayGridView.prototype.updateSize = function (isResize, viewHeight, isAuto) { + _super.prototype.updateSize.call(this, isResize, viewHeight, isAuto); // will call updateBaseSize. important that executes first + this.dayGrid.updateSize(isResize); + }; + // Refreshes the horizontal dimensions of the view + DayGridView.prototype.updateBaseSize = function (isResize, viewHeight, isAuto) { + var dayGrid = this.dayGrid; + var eventLimit = this.opt('eventLimit'); + var headRowEl = this.header ? this.header.el : null; // HACK + var scrollerHeight; + var scrollbarWidths; + // hack to give the view some height prior to dayGrid's columns being rendered + // TODO: separate setting height from scroller VS dayGrid. + if (!dayGrid.rowEls) { + if (!isAuto) { + scrollerHeight = this.computeScrollerHeight(viewHeight); + this.scroller.setHeight(scrollerHeight); + } + return; + } + if (this.colWeekNumbersVisible) { + // Make sure all week number cells running down the side have the same width. + this.weekNumberWidth = core.matchCellWidths(core.findElements(this.el, '.fc-week-number')); + } + // reset all heights to be natural + this.scroller.clear(); + if (headRowEl) { + core.uncompensateScroll(headRowEl); + } + dayGrid.removeSegPopover(); // kill the "more" popover if displayed + // is the event limit a constant level number? + if (eventLimit && typeof eventLimit === 'number') { + dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after + } + // distribute the height to the rows + // (viewHeight is a "recommended" value if isAuto) + scrollerHeight = this.computeScrollerHeight(viewHeight); + this.setGridHeight(scrollerHeight, isAuto); + // is the event limit dynamically calculated? + if (eventLimit && typeof eventLimit !== 'number') { + dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set + } + if (!isAuto) { // should we force dimensions of the scroll container? + this.scroller.setHeight(scrollerHeight); + scrollbarWidths = this.scroller.getScrollbarWidths(); + if (scrollbarWidths.left || scrollbarWidths.right) { // using scrollbars? + if (headRowEl) { + core.compensateScroll(headRowEl, scrollbarWidths); + } + // doing the scrollbar compensation might have created text overflow which created more height. redo + scrollerHeight = this.computeScrollerHeight(viewHeight); + this.scroller.setHeight(scrollerHeight); + } + // guarantees the same scrollbar widths + this.scroller.lockOverflow(scrollbarWidths); + } + }; + // given a desired total height of the view, returns what the height of the scroller should be + DayGridView.prototype.computeScrollerHeight = function (viewHeight) { + return viewHeight - + core.subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller + }; + // Sets the height of just the DayGrid component in this view + DayGridView.prototype.setGridHeight = function (height, isAuto) { + if (this.opt('monthMode')) { + // if auto, make the height of each row the height that it would be if there were 6 weeks + if (isAuto) { + height *= this.dayGrid.rowCnt / 6; + } + core.distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows + } + else { + if (isAuto) { + core.undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding + } + else { + core.distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows + } + } + }; + /* Scroll + ------------------------------------------------------------------------------------------------------------------*/ + DayGridView.prototype.computeInitialDateScroll = function () { + return { top: 0 }; + }; + DayGridView.prototype.queryDateScroll = function () { + return { top: this.scroller.getScrollTop() }; + }; + DayGridView.prototype.applyDateScroll = function (scroll) { + if (scroll.top !== undefined) { + this.scroller.setScrollTop(scroll.top); + } + }; + return DayGridView; + }(core.View)); + DayGridView.prototype.dateProfileGeneratorClass = DayGridDateProfileGenerator; + + var SimpleDayGrid = /** @class */ (function (_super) { + __extends(SimpleDayGrid, _super); + function SimpleDayGrid(context, dayGrid) { + var _this = _super.call(this, context, dayGrid.el) || this; + _this.slicer = new DayGridSlicer(); + _this.dayGrid = dayGrid; + context.calendar.registerInteractiveComponent(_this, { el: _this.dayGrid.el }); + return _this; + } + SimpleDayGrid.prototype.destroy = function () { + _super.prototype.destroy.call(this); + this.calendar.unregisterInteractiveComponent(this); + }; + SimpleDayGrid.prototype.render = function (props) { + var dayGrid = this.dayGrid; + var dateProfile = props.dateProfile, dayTable = props.dayTable; + dayGrid.receiveProps(__assign({}, this.slicer.sliceProps(props, dateProfile, props.nextDayThreshold, dayGrid, dayTable), { dateProfile: dateProfile, cells: dayTable.cells, isRigid: props.isRigid })); + }; + SimpleDayGrid.prototype.queryHit = function (positionLeft, positionTop) { + var rawHit = this.dayGrid.positionToHit(positionLeft, positionTop); + if (rawHit) { + return { + component: this.dayGrid, + dateSpan: rawHit.dateSpan, + dayEl: rawHit.dayEl, + rect: { + left: rawHit.relativeRect.left, + right: rawHit.relativeRect.right, + top: rawHit.relativeRect.top, + bottom: rawHit.relativeRect.bottom + }, + layer: 0 + }; + } + }; + return SimpleDayGrid; + }(core.DateComponent)); + var DayGridSlicer = /** @class */ (function (_super) { + __extends(DayGridSlicer, _super); + function DayGridSlicer() { + return _super !== null && _super.apply(this, arguments) || this; + } + DayGridSlicer.prototype.sliceRange = function (dateRange, dayTable) { + return dayTable.sliceRange(dateRange); + }; + return DayGridSlicer; + }(core.Slicer)); + + var DayGridView$1 = /** @class */ (function (_super) { + __extends(DayGridView, _super); + function DayGridView(_context, viewSpec, dateProfileGenerator, parentEl) { + var _this = _super.call(this, _context, viewSpec, dateProfileGenerator, parentEl) || this; + _this.buildDayTable = core.memoize(buildDayTable); + if (_this.opt('columnHeader')) { + _this.header = new core.DayHeader(_this.context, _this.el.querySelector('.fc-head-container')); + } + _this.simpleDayGrid = new SimpleDayGrid(_this.context, _this.dayGrid); + return _this; + } + DayGridView.prototype.destroy = function () { + _super.prototype.destroy.call(this); + if (this.header) { + this.header.destroy(); + } + this.simpleDayGrid.destroy(); + }; + DayGridView.prototype.render = function (props) { + _super.prototype.render.call(this, props); + var dateProfile = this.props.dateProfile; + var dayTable = this.dayTable = + this.buildDayTable(dateProfile, this.dateProfileGenerator); + if (this.header) { + this.header.receiveProps({ + dateProfile: dateProfile, + dates: dayTable.headerDates, + datesRepDistinctDays: dayTable.rowCnt === 1, + renderIntroHtml: this.renderHeadIntroHtml + }); + } + this.simpleDayGrid.receiveProps({ + dateProfile: dateProfile, + dayTable: dayTable, + businessHours: props.businessHours, + dateSelection: props.dateSelection, + eventStore: props.eventStore, + eventUiBases: props.eventUiBases, + eventSelection: props.eventSelection, + eventDrag: props.eventDrag, + eventResize: props.eventResize, + isRigid: this.hasRigidRows(), + nextDayThreshold: this.nextDayThreshold + }); + }; + return DayGridView; + }(DayGridView)); + function buildDayTable(dateProfile, dateProfileGenerator) { + var daySeries = new core.DaySeries(dateProfile.renderRange, dateProfileGenerator); + return new core.DayTable(daySeries, /year|month|week/.test(dateProfile.currentRangeUnit)); + } + + var main = core.createPlugin({ + defaultView: 'dayGridMonth', + views: { + dayGrid: DayGridView$1, + dayGridDay: { + type: 'dayGrid', + duration: { days: 1 } + }, + dayGridWeek: { + type: 'dayGrid', + duration: { weeks: 1 } + }, + dayGridMonth: { + type: 'dayGrid', + duration: { months: 1 }, + monthMode: true, + fixedWeekCount: true + } + } + }); + + exports.AbstractDayGridView = DayGridView; + exports.DayBgRow = DayBgRow; + exports.DayGrid = DayGrid; + exports.DayGridSlicer = DayGridSlicer; + exports.DayGridView = DayGridView$1; + exports.SimpleDayGrid = SimpleDayGrid; + exports.buildBasicDayTable = buildDayTable; + exports.default = main; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/library/fullcalendar/packages/daygrid/main.min.css b/library/fullcalendar/packages/daygrid/main.min.css new file mode 100644 index 000000000..75fd5cb88 --- /dev/null +++ b/library/fullcalendar/packages/daygrid/main.min.css @@ -0,0 +1,5 @@ +/*! +FullCalendar Day Grid Plugin v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/.fc-dayGridDay-view .fc-content-skeleton,.fc-dayGridWeek-view .fc-content-skeleton{padding-bottom:1em}.fc-dayGrid-view .fc-body .fc-row{min-height:4em}.fc-row.fc-rigid{overflow:hidden}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-day-top.fc-other-month{opacity:.3}.fc-dayGrid-view .fc-day-number,.fc-dayGrid-view .fc-week-number{padding:2px}.fc-dayGrid-view th.fc-day-number,.fc-dayGrid-view th.fc-week-number{padding:0 2px}.fc-ltr .fc-dayGrid-view .fc-day-top .fc-day-number{float:right}.fc-rtl .fc-dayGrid-view .fc-day-top .fc-day-number{float:left}.fc-ltr .fc-dayGrid-view .fc-day-top .fc-week-number{float:left;border-radius:0 0 3px}.fc-rtl .fc-dayGrid-view .fc-day-top .fc-week-number{float:right;border-radius:0 0 0 3px}.fc-dayGrid-view .fc-day-top .fc-week-number{min-width:1.5em;text-align:center;background-color:#f2f2f2;color:grey}.fc-dayGrid-view td.fc-week-number{text-align:center}.fc-dayGrid-view td.fc-week-number>*{display:inline-block;min-width:1.25em} \ No newline at end of file diff --git a/library/fullcalendar/packages/daygrid/main.min.js b/library/fullcalendar/packages/daygrid/main.min.js new file mode 100644 index 000000000..54b390012 --- /dev/null +++ b/library/fullcalendar/packages/daygrid/main.min.js @@ -0,0 +1,20 @@ +/*! +FullCalendar Day Grid Plugin v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@fullcalendar/core")):"function"==typeof define&&define.amd?define(["exports","@fullcalendar/core"],t):(e=e||self,t(e.FullCalendarDayGrid={},e.FullCalendar))}(this,function(e,t){"use strict";function r(e,t){function r(){this.constructor=e}l(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function n(e,t){var r,n;for(r=0;r=e.firstCol)return!0;return!1}function i(e,t){return e.leftCol-t.leftCol}function o(e,r,n,i){var o=n.dateEnv,s=n.theme,l=t.rangeContainsMarker(r.activeRange,e),a=t.getDayClasses(e,r,n);return a.unshift("fc-day",s.getClass("widgetContent")),'"}function s(e,r){var n=new t.DaySeries(e.renderRange,r);return new t.DayTable(n,/year|month|week/.test(e.currentRangeUnit))}/*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */ +var l=function(e,t){return(l=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},a=function(){return a=Object.assign||function(e){for(var t,r=1,n=arguments.length;r'+t.htmlEscape(n)+""),i=''+(t.htmlEscape(l.title||"")||" ")+"",'
'+("rtl"===o.dir?i+" "+g:g+" "+i)+"
"+(h?'
':"")+(p?'
':"")+"
"},n.prototype.computeEventTimeFormat=function(){return{hour:"numeric",minute:"2-digit",omitZeroMinute:!0,meridiem:"narrow"}},n.prototype.computeDisplayEventEnd=function(){return!1},n}(t.FgEventRenderer),p=function(e){function o(t){var r=e.call(this,t.context)||this;return r.dayGrid=t,r}return r(o,e),o.prototype.attachSegs=function(e,t){var r=this.rowStructs=this.renderSegRows(e);this.dayGrid.rowEls.forEach(function(e,t){e.querySelector(".fc-content-skeleton > table").appendChild(r[t].tbodyEl)}),t||this.dayGrid.removeSegPopover()},o.prototype.detachSegs=function(){for(var e,r=this.rowStructs||[];e=r.pop();)t.removeElement(e.tbodyEl);this.rowStructs=null},o.prototype.renderSegRows=function(e){var t,r,n=[];for(t=this.groupSegRows(e),r=0;r
');n&&n.row===r?o=n.el:(o=e.querySelector(".fc-content-skeleton tbody"))||(o=e.querySelector(".fc-content-skeleton table")),s=o.getBoundingClientRect().top-e.getBoundingClientRect().top,l.style.top=s+"px",l.querySelector("table").appendChild(i[r].tbodyEl),e.appendChild(l)})},n}(p),f=function(e){function n(t){var r=e.call(this,t.context)||this;return r.fillSegTag="td",r.dayGrid=t,r}return r(n,e),n.prototype.renderSegs=function(t,r){"bgEvent"===t&&(r=r.filter(function(e){return e.eventRange.def.allDay})),e.prototype.renderSegs.call(this,t,r)},n.prototype.attachSegs=function(e,t){var r,n,i,o=[];for(r=0;r
'),o=i.getElementsByTagName("tr")[0],h>0&&t.appendToElement(o,new Array(h+1).join("")),r.el.colSpan=p-h,o.appendChild(r.el),p"));var u=s.renderProps.renderIntroHtml();return u&&(s.isRtl?t.appendToElement(o,u):t.prependToElement(o,u)),i},n}(t.FillRenderer),g=function(e){function n(r,n){var i=e.call(this,r,n)||this,o=i.eventRenderer=new m(i),s=i.renderFrame=t.memoizeRendering(i._renderFrame);return i.renderFgEvents=t.memoizeRendering(o.renderSegs.bind(o),o.unrender.bind(o),[s]),i.renderEventSelection=t.memoizeRendering(o.selectByInstanceId.bind(o),o.unselectByInstanceId.bind(o),[i.renderFgEvents]),i.renderEventDrag=t.memoizeRendering(o.hideByHash.bind(o),o.showByHash.bind(o),[s]),i.renderEventResize=t.memoizeRendering(o.hideByHash.bind(o),o.showByHash.bind(o),[s]),r.calendar.registerInteractiveComponent(i,{el:i.el,useEventCenter:!1}),i}return r(n,e),n.prototype.render=function(e){this.renderFrame(e.date),this.renderFgEvents(e.fgSegs),this.renderEventSelection(e.eventSelection),this.renderEventDrag(e.eventDragInstances),this.renderEventResize(e.eventResizeInstances)},n.prototype.destroy=function(){e.prototype.destroy.call(this),this.renderFrame.unrender(),this.calendar.unregisterInteractiveComponent(this)},n.prototype._renderFrame=function(e){var r=this,n=r.theme,i=r.dateEnv,o=i.format(e,t.createFormatter(this.opt("dayPopoverFormat")));this.el.innerHTML='
'+t.htmlEscape(o)+'
',this.segContainerEl=this.el.querySelector(".fc-event-container")},n.prototype.queryHit=function(e,r,n,i){var o=this.props.date;if(e'),"rtl"===this.context.options.dir&&t.reverse(),""+t.join("")+""},e}(),v=t.createFormatter({day:"numeric"}),b=t.createFormatter({week:"numeric"}),w=function(e){function n(r,n,i){var o=e.call(this,r,n)||this;o.bottomCoordPadding=0,o.isCellSizesDirty=!1;var s=o.eventRenderer=new p(o),l=o.fillRenderer=new f(o);o.mirrorRenderer=new u(o);var a=o.renderCells=t.memoizeRendering(o._renderCells,o._unrenderCells);return o.renderBusinessHours=t.memoizeRendering(l.renderSegs.bind(l,"businessHours"),l.unrender.bind(l,"businessHours"),[a]),o.renderDateSelection=t.memoizeRendering(l.renderSegs.bind(l,"highlight"),l.unrender.bind(l,"highlight"),[a]),o.renderBgEvents=t.memoizeRendering(l.renderSegs.bind(l,"bgEvent"),l.unrender.bind(l,"bgEvent"),[a]),o.renderFgEvents=t.memoizeRendering(s.renderSegs.bind(s),s.unrender.bind(s),[a]),o.renderEventSelection=t.memoizeRendering(s.selectByInstanceId.bind(s),s.unselectByInstanceId.bind(s),[o.renderFgEvents]),o.renderEventDrag=t.memoizeRendering(o._renderEventDrag,o._unrenderEventDrag,[a]),o.renderEventResize=t.memoizeRendering(o._renderEventResize,o._unrenderEventResize,[a]),o.renderProps=i,o}return r(n,e),n.prototype.render=function(e){var t=e.cells;this.rowCnt=t.length,this.colCnt=t[0].length,this.renderCells(t,e.isRigid),this.renderBusinessHours(e.businessHourSegs),this.renderDateSelection(e.dateSelectionSegs),this.renderBgEvents(e.bgEventSegs),this.renderFgEvents(e.fgEventSegs),this.renderEventSelection(e.eventSelection),this.renderEventDrag(e.eventDrag),this.renderEventResize(e.eventResize),this.segPopoverTile&&this.updateSegPopoverTile()},n.prototype.destroy=function(){e.prototype.destroy.call(this),this.renderCells.unrender()},n.prototype.getCellRange=function(e,r){var n=this.props.cells[e][r].date;return{start:n,end:t.addDays(n,1)}},n.prototype.updateSegPopoverTile=function(e,t){var r=this.props;this.segPopoverTile.receiveProps({date:e||this.segPopoverTile.props.date,fgSegs:t||this.segPopoverTile.props.fgSegs,eventSelection:r.eventSelection,eventDragInstances:r.eventDrag?r.eventDrag.affectedInstances:null,eventResizeInstances:r.eventResize?r.eventResize.affectedInstances:null})},n.prototype._renderCells=function(e,r){var n,i,o=this,s=o.view,l=o.dateEnv,a=this,d=a.rowCnt,c=a.colCnt,h="";for(n=0;n
'+i.renderHtml({cells:this.props.cells[e],dateProfile:this.props.dateProfile,renderIntroHtml:this.renderProps.renderBgIntroHtml})+'
'+(this.getIsNumbersVisible()?""+this.renderNumberTrHtml(e)+"":"")+"
"},n.prototype.getIsNumbersVisible=function(){return this.getIsDayNumbersVisible()||this.renderProps.cellWeekNumbersVisible||this.renderProps.colWeekNumbersVisible},n.prototype.getIsDayNumbersVisible=function(){return this.rowCnt>1},n.prototype.renderNumberTrHtml=function(e){var t=this.renderProps.renderNumberIntroHtml(e,this);return""+(this.isRtl?"":t)+this.renderNumberCellsHtml(e)+(this.isRtl?t:"")+""},n.prototype.renderNumberCellsHtml=function(e){var t,r,n=[];for(t=0;t",this.renderProps.cellWeekNumbersVisible&&e.getUTCDay()===n&&(l+=t.buildGotoAnchorHtml(o,{date:e,type:"week"},{class:"fc-week-number"},s.format(e,b))),d&&(l+=t.buildGotoAnchorHtml(o,e,{class:"fc-day-number"},s.format(e,v))),l+=""):""},n.prototype.updateSize=function(e){var t=this,r=t.fillRenderer,n=t.eventRenderer,i=t.mirrorRenderer;(e||this.isCellSizesDirty)&&(this.buildColPositions(),this.buildRowPositions(),this.isCellSizesDirty=!1),r.computeSizes(e),n.computeSizes(e),i.computeSizes(e),r.assignSizes(e),n.assignSizes(e),i.assignSizes(e)},n.prototype.buildColPositions=function(){this.colPositions.build()},n.prototype.buildRowPositions=function(){this.rowPositions.build(),this.rowPositions.bottoms[this.rowCnt-1]+=this.bottomCoordPadding},n.prototype.positionToHit=function(e,t){var r=this,n=r.colPositions,i=r.rowPositions,o=n.leftToIndex(e),s=i.topToIndex(t);if(null!=s&&null!=o)return{row:s,col:o,dateSpan:{range:this.getCellRange(s,o),allDay:!0},dayEl:this.getCellEl(s,o),relativeRect:{left:n.lefts[o],right:n.rights[o],top:i.tops[s],bottom:i.bottoms[s]}}},n.prototype.getCellEl=function(e,t){return this.cellEls[e*this.colCnt+t]},n.prototype._renderEventDrag=function(e){e&&(this.eventRenderer.hideByHash(e.affectedInstances),this.fillRenderer.renderSegs("highlight",e.segs))},n.prototype._unrenderEventDrag=function(e){e&&(this.eventRenderer.showByHash(e.affectedInstances),this.fillRenderer.unrender("highlight"))},n.prototype._renderEventResize=function(e){e&&(this.eventRenderer.hideByHash(e.affectedInstances),this.fillRenderer.renderSegs("highlight",e.segs),this.mirrorRenderer.renderSegs(e.segs,{isResizing:!0,sourceSeg:e.sourceSeg}))},n.prototype._unrenderEventResize=function(e){e&&(this.eventRenderer.showByHash(e.affectedInstances),this.fillRenderer.unrender("highlight"),this.mirrorRenderer.unrender(e.segs,{isResizing:!0,sourceSeg:e.sourceSeg}))},n.prototype.removeSegPopover=function(){this.segPopover&&this.segPopover.hide()},n.prototype.limitRows=function(e){var t,r,n=this.eventRenderer.rowStructs||[];for(t=0;to)return r;return!1},n.prototype.limitRow=function(e,r){var n,i,o,s,l,a,d,c,h,p,u,f,g,m,y,v=this,b=this,w=b.colCnt,S=b.isRtl,C=this.eventRenderer.rowStructs[e],E=[],R=0,H=function(n){for(;R"+t.htmlEscape(s.opt("weekLabel"))+"":""},s.renderDayGridNumberIntroHtml=function(e,r){var n=s.dateEnv,i=r.props.cells[e][0].date;return s.colWeekNumbersVisible?'"+t.buildGotoAnchorHtml(s,{date:i,type:"week",forceOff:1===r.colCnt},n.format(i,S))+"":""},s.renderDayGridBgIntroHtml=function(){var e=s.theme;return s.colWeekNumbersVisible?'":""},s.renderDayGridIntroHtml=function(){return s.colWeekNumbersVisible?'":""},s.el.classList.add("fc-dayGrid-view"),s.el.innerHTML=s.renderSkeletonHtml(),s.scroller=new t.ScrollComponent("hidden","auto");var l=s.scroller.el;s.el.querySelector(".fc-body > tr > td").appendChild(l),l.classList.add("fc-day-grid-container");var a=t.createElement("div",{className:"fc-day-grid"});l.appendChild(a);var d;return s.opt("weekNumbers")?s.opt("weekNumbersWithinDays")?(d=!0,s.colWeekNumbersVisible=!1):(d=!1,s.colWeekNumbersVisible=!0):(s.colWeekNumbersVisible=!1,d=!1),s.dayGrid=new w(s.context,a,{renderNumberIntroHtml:s.renderDayGridNumberIntroHtml,renderBgIntroHtml:s.renderDayGridBgIntroHtml,renderIntroHtml:s.renderDayGridIntroHtml,colWeekNumbersVisible:s.colWeekNumbersVisible,cellWeekNumbersVisible:d}),s}return r(n,e),n.prototype.destroy=function(){e.prototype.destroy.call(this),this.dayGrid.destroy(),this.scroller.destroy()},n.prototype.renderSkeletonHtml=function(){var e=this.theme;return''+(this.opt("columnHeader")?'':"")+'
 
'},n.prototype.weekNumberStyleAttr=function(){return null!=this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},n.prototype.hasRigidRows=function(){var e=this.opt("eventLimit");return e&&"number"!=typeof e},n.prototype.updateSize=function(t,r,n){e.prototype.updateSize.call(this,t,r,n),this.dayGrid.updateSize(t)},n.prototype.updateBaseSize=function(e,r,n){var i,o,s=this.dayGrid,l=this.opt("eventLimit"),a=this.header?this.header.el:null;if(!s.rowEls)return void(n||(i=this.computeScrollerHeight(r),this.scroller.setHeight(i)));this.colWeekNumbersVisible&&(this.weekNumberWidth=t.matchCellWidths(t.findElements(this.el,".fc-week-number"))),this.scroller.clear(),a&&t.uncompensateScroll(a),s.removeSegPopover(),l&&"number"==typeof l&&s.limitRows(l),i=this.computeScrollerHeight(r),this.setGridHeight(i,n),l&&"number"!=typeof l&&s.limitRows(l),n||(this.scroller.setHeight(i),o=this.scroller.getScrollbarWidths(),(o.left||o.right)&&(a&&t.compensateScroll(a,o),i=this.computeScrollerHeight(r),this.scroller.setHeight(i)),this.scroller.lockOverflow(o))},n.prototype.computeScrollerHeight=function(e){return e-t.subtractInnerElHeight(this.el,this.scroller.el)},n.prototype.setGridHeight=function(e,r){this.opt("monthMode")?(r&&(e*=this.dayGrid.rowCnt/6),t.distributeHeight(this.dayGrid.rowEls,e,!r)):r?t.undistributeHeight(this.dayGrid.rowEls):t.distributeHeight(this.dayGrid.rowEls,e,!0)},n.prototype.computeInitialDateScroll=function(){return{top:0}},n.prototype.queryDateScroll=function(){return{top:this.scroller.getScrollTop()}},n.prototype.applyDateScroll=function(e){void 0!==e.top&&this.scroller.setScrollTop(e.top)},n}(t.View);C.prototype.dateProfileGeneratorClass=d;var E=function(e){function t(t,r){var n=e.call(this,t,r.el)||this;return n.slicer=new R,n.dayGrid=r,t.calendar.registerInteractiveComponent(n,{el:n.dayGrid.el}),n}return r(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this),this.calendar.unregisterInteractiveComponent(this)},t.prototype.render=function(e){var t=this.dayGrid,r=e.dateProfile,n=e.dayTable;t.receiveProps(a({},this.slicer.sliceProps(e,r,e.nextDayThreshold,t,n),{dateProfile:r,cells:n.cells,isRigid:e.isRigid}))},t.prototype.queryHit=function(e,t){var r=this.dayGrid.positionToHit(e,t);if(r)return{component:this.dayGrid,dateSpan:r.dateSpan,dayEl:r.dayEl,rect:{left:r.relativeRect.left,right:r.relativeRect.right,top:r.relativeRect.top,bottom:r.relativeRect.bottom},layer:0}},t}(t.DateComponent),R=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return r(t,e),t.prototype.sliceRange=function(e,t){return t.sliceRange(e)},t}(t.Slicer),H=function(e){function n(r,n,i,o){var l=e.call(this,r,n,i,o)||this;return l.buildDayTable=t.memoize(s),l.opt("columnHeader")&&(l.header=new t.DayHeader(l.context,l.el.querySelector(".fc-head-container"))),l.simpleDayGrid=new E(l.context,l.dayGrid),l}return r(n,e),n.prototype.destroy=function(){e.prototype.destroy.call(this),this.header&&this.header.destroy(),this.simpleDayGrid.destroy()},n.prototype.render=function(t){e.prototype.render.call(this,t);var r=this.props.dateProfile,n=this.dayTable=this.buildDayTable(r,this.dateProfileGenerator);this.header&&this.header.receiveProps({dateProfile:r,dates:n.headerDates,datesRepDistinctDays:1===n.rowCnt,renderIntroHtml:this.renderHeadIntroHtml}),this.simpleDayGrid.receiveProps({dateProfile:r,dayTable:n,businessHours:t.businessHours,dateSelection:t.dateSelection,eventStore:t.eventStore,eventUiBases:t.eventUiBases,eventSelection:t.eventSelection,eventDrag:t.eventDrag,eventResize:t.eventResize,isRigid:this.hasRigidRows(),nextDayThreshold:this.nextDayThreshold})},n}(C),D=t.createPlugin({defaultView:"dayGridMonth",views:{dayGrid:H,dayGridDay:{type:"dayGrid",duration:{days:1}},dayGridWeek:{type:"dayGrid",duration:{weeks:1}},dayGridMonth:{type:"dayGrid",duration:{months:1},monthMode:!0,fixedWeekCount:!0}}});e.AbstractDayGridView=C,e.DayBgRow=y,e.DayGrid=w,e.DayGridSlicer=R,e.DayGridView=H,e.SimpleDayGrid=E,e.buildBasicDayTable=s,e.default=D,Object.defineProperty(e,"__esModule",{value:!0})}); \ No newline at end of file diff --git a/library/fullcalendar/packages/google-calendar/main.js b/library/fullcalendar/packages/google-calendar/main.js new file mode 100644 index 000000000..89ce3d7a6 --- /dev/null +++ b/library/fullcalendar/packages/google-calendar/main.js @@ -0,0 +1,169 @@ +/*! +FullCalendar Google Calendar Plugin v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@fullcalendar/core')) : + typeof define === 'function' && define.amd ? define(['exports', '@fullcalendar/core'], factory) : + (global = global || self, factory(global.FullCalendarGoogleCalendar = {}, global.FullCalendar)); +}(this, function (exports, core) { 'use strict'; + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */ + + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + + // TODO: expose somehow + var API_BASE = 'https://www.googleapis.com/calendar/v3/calendars'; + var STANDARD_PROPS = { + url: String, + googleCalendarApiKey: String, + googleCalendarId: String, + data: null + }; + var eventSourceDef = { + parseMeta: function (raw) { + if (typeof raw === 'string') { + raw = { url: raw }; + } + if (typeof raw === 'object') { + var standardProps = core.refineProps(raw, STANDARD_PROPS); + if (!standardProps.googleCalendarId && standardProps.url) { + standardProps.googleCalendarId = parseGoogleCalendarId(standardProps.url); + } + delete standardProps.url; + if (standardProps.googleCalendarId) { + return standardProps; + } + } + return null; + }, + fetch: function (arg, onSuccess, onFailure) { + var calendar = arg.calendar; + var meta = arg.eventSource.meta; + var apiKey = meta.googleCalendarApiKey || calendar.opt('googleCalendarApiKey'); + if (!apiKey) { + onFailure({ + message: 'Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/' + }); + } + else { + var url = buildUrl(meta); + var requestParams_1 = buildRequestParams(arg.range, apiKey, meta.data, calendar.dateEnv); + core.requestJson('GET', url, requestParams_1, function (body, xhr) { + if (body.error) { + onFailure({ + message: 'Google Calendar API: ' + body.error.message, + errors: body.error.errors, + xhr: xhr + }); + } + else { + onSuccess({ + rawEvents: gcalItemsToRawEventDefs(body.items, requestParams_1.timeZone), + xhr: xhr + }); + } + }, function (message, xhr) { + onFailure({ message: message, xhr: xhr }); + }); + } + } + }; + function parseGoogleCalendarId(url) { + var match; + // detect if the ID was specified as a single string. + // will match calendars like "asdf1234@calendar.google.com" in addition to person email calendars. + if (/^[^\/]+@([^\/\.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) { + return url; + } + else if ((match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^\/]*)/.exec(url)) || + (match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^\/]*)/.exec(url))) { + return decodeURIComponent(match[1]); + } + } + function buildUrl(meta) { + return API_BASE + '/' + encodeURIComponent(meta.googleCalendarId) + '/events'; + } + function buildRequestParams(range, apiKey, extraParams, dateEnv) { + var params; + var startStr; + var endStr; + if (dateEnv.canComputeOffset) { + // strings will naturally have offsets, which GCal needs + startStr = dateEnv.formatIso(range.start); + endStr = dateEnv.formatIso(range.end); + } + else { + // when timezone isn't known, we don't know what the UTC offset should be, so ask for +/- 1 day + // from the UTC day-start to guarantee we're getting all the events + // (start/end will be UTC-coerced dates, so toISOString is okay) + startStr = core.addDays(range.start, -1).toISOString(); + endStr = core.addDays(range.end, 1).toISOString(); + } + params = __assign({}, (extraParams || {}), { key: apiKey, timeMin: startStr, timeMax: endStr, singleEvents: true, maxResults: 9999 }); + if (dateEnv.timeZone !== 'local') { + params.timeZone = dateEnv.timeZone; + } + return params; + } + function gcalItemsToRawEventDefs(items, gcalTimezone) { + return items.map(function (item) { + return gcalItemToRawEventDef(item, gcalTimezone); + }); + } + function gcalItemToRawEventDef(item, gcalTimezone) { + var url = item.htmlLink || null; + // make the URLs for each event show times in the correct timezone + if (url && gcalTimezone) { + url = injectQsComponent(url, 'ctz=' + gcalTimezone); + } + return { + id: item.id, + title: item.summary, + start: item.start.dateTime || item.start.date, + end: item.end.dateTime || item.end.date, + url: url, + location: item.location, + description: item.description + }; + } + // Injects a string like "arg=value" into the querystring of a URL + // TODO: move to a general util file? + function injectQsComponent(url, component) { + // inject it after the querystring but before the fragment + return url.replace(/(\?.*?)?(#|$)/, function (whole, qs, hash) { + return (qs ? qs + '&' : '?') + component + hash; + }); + } + var main = core.createPlugin({ + eventSourceDefs: [eventSourceDef] + }); + + exports.default = main; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/library/fullcalendar/packages/google-calendar/main.min.js b/library/fullcalendar/packages/google-calendar/main.min.js new file mode 100644 index 000000000..51947e659 --- /dev/null +++ b/library/fullcalendar/packages/google-calendar/main.min.js @@ -0,0 +1,20 @@ +/*! +FullCalendar Google Calendar Plugin v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/ +!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("@fullcalendar/core")):"function"==typeof define&&define.amd?define(["exports","@fullcalendar/core"],r):(e=e||self,r(e.FullCalendarGoogleCalendar={},e.FullCalendar))}(this,function(e,r){"use strict";function t(e){var r;return/^[^\/]+@([^\/\.]+\.)*(google|googlemail|gmail)\.com$/.test(e)?e:(r=/^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^\/]*)/.exec(e))||(r=/^https?:\/\/www.google.com\/calendar\/feeds\/([^\/]*)/.exec(e))?decodeURIComponent(r[1]):void 0}function n(e){return s+"/"+encodeURIComponent(e.googleCalendarId)+"/events"}function o(e,t,n,o){var a,l,i;return o.canComputeOffset?(l=o.formatIso(e.start),i=o.formatIso(e.end)):(l=r.addDays(e.start,-1).toISOString(),i=r.addDays(e.end,1).toISOString()),a=d({},n||{},{key:t,timeMin:l,timeMax:i,singleEvents:!0,maxResults:9999}),"local"!==o.timeZone&&(a.timeZone=o.timeZone),a}function a(e,r){return e.map(function(e){return l(e,r)})}function l(e,r){var t=e.htmlLink||null;return t&&r&&(t=i(t,"ctz="+r)),{id:e.id,title:e.summary,start:e.start.dateTime||e.start.date,end:e.end.dateTime||e.end.date,url:t,location:e.location,description:e.description}}function i(e,r){return e.replace(/(\?.*?)?(#|$)/,function(e,t,n){return(t?t+"&":"?")+r+n})}/*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */ +var d=function(){return d=Object.assign||function(e){for(var r,t=1,n=arguments.length;t 0) { + this.everMovedDown = true; + } + if (xDelta < 0) { + this.everMovedLeft = true; + } + else if (xDelta > 0) { + this.everMovedRight = true; + } + this.pointerScreenX = pointerScreenX; + this.pointerScreenY = pointerScreenY; + if (!this.isAnimating) { + this.isAnimating = true; + this.requestAnimation(getTime()); + } + } + }; + AutoScroller.prototype.stop = function () { + if (this.isEnabled) { + this.isAnimating = false; // will stop animation + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + scrollCache.destroy(); + } + this.scrollCaches = null; + } + }; + AutoScroller.prototype.requestAnimation = function (now) { + this.msSinceRequest = now; + requestAnimationFrame(this.animate); + }; + AutoScroller.prototype.handleSide = function (edge, seconds) { + var scrollCache = edge.scrollCache; + var edgeThreshold = this.edgeThreshold; + var invDistance = edgeThreshold - edge.distance; + var velocity = // the closer to the edge, the faster we scroll + (invDistance * invDistance) / (edgeThreshold * edgeThreshold) * // quadratic + this.maxVelocity * seconds; + var sign = 1; + switch (edge.name) { + case 'left': + sign = -1; + // falls through + case 'right': + scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign); + break; + case 'top': + sign = -1; + // falls through + case 'bottom': + scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign); + break; + } + }; + // left/top are relative to document topleft + AutoScroller.prototype.computeBestEdge = function (left, top) { + var edgeThreshold = this.edgeThreshold; + var bestSide = null; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + var rect = scrollCache.clientRect; + var leftDist = left - rect.left; + var rightDist = rect.right - left; + var topDist = top - rect.top; + var bottomDist = rect.bottom - top; + // completely within the rect? + if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) { + if (topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() && + (!bestSide || bestSide.distance > topDist)) { + bestSide = { scrollCache: scrollCache, name: 'top', distance: topDist }; + } + if (bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && + (!bestSide || bestSide.distance > bottomDist)) { + bestSide = { scrollCache: scrollCache, name: 'bottom', distance: bottomDist }; + } + if (leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && + (!bestSide || bestSide.distance > leftDist)) { + bestSide = { scrollCache: scrollCache, name: 'left', distance: leftDist }; + } + if (rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && + (!bestSide || bestSide.distance > rightDist)) { + bestSide = { scrollCache: scrollCache, name: 'right', distance: rightDist }; + } + } + } + return bestSide; + }; + AutoScroller.prototype.buildCaches = function () { + return this.queryScrollEls().map(function (el) { + if (el === window) { + return new WindowScrollGeomCache(false); // false = don't listen to user-generated scrolls + } + else { + return new ElementScrollGeomCache(el, false); // false = don't listen to user-generated scrolls + } + }); + }; + AutoScroller.prototype.queryScrollEls = function () { + var els = []; + for (var _i = 0, _a = this.scrollQuery; _i < _a.length; _i++) { + var query = _a[_i]; + if (typeof query === 'object') { + els.push(query); + } + else { + els.push.apply(els, Array.prototype.slice.call(document.querySelectorAll(query))); + } + } + return els; + }; + return AutoScroller; + }()); + + /* + Monitors dragging on an element. Has a number of high-level features: + - minimum distance required before dragging + - minimum wait time ("delay") before dragging + - a mirror element that follows the pointer + */ + var FeaturefulElementDragging = /** @class */ (function (_super) { + __extends(FeaturefulElementDragging, _super); + function FeaturefulElementDragging(containerEl) { + var _this = _super.call(this, containerEl) || this; + // options that can be directly set by caller + // the caller can also set the PointerDragging's options as well + _this.delay = null; + _this.minDistance = 0; + _this.touchScrollAllowed = true; // prevents drag from starting and blocks scrolling during drag + _this.mirrorNeedsRevert = false; + _this.isInteracting = false; // is the user validly moving the pointer? lasts until pointerup + _this.isDragging = false; // is it INTENTFULLY dragging? lasts until after revert animation + _this.isDelayEnded = false; + _this.isDistanceSurpassed = false; + _this.delayTimeoutId = null; + _this.onPointerDown = function (ev) { + if (!_this.isDragging) { // so new drag doesn't happen while revert animation is going + _this.isInteracting = true; + _this.isDelayEnded = false; + _this.isDistanceSurpassed = false; + core.preventSelection(document.body); + core.preventContextMenu(document.body); + // prevent links from being visited if there's an eventual drag. + // also prevents selection in older browsers (maybe?). + // not necessary for touch, besides, browser would complain about passiveness. + if (!ev.isTouch) { + ev.origEvent.preventDefault(); + } + _this.emitter.trigger('pointerdown', ev); + if (!_this.pointer.shouldIgnoreMove) { + // actions related to initiating dragstart+dragmove+dragend... + _this.mirror.setIsVisible(false); // reset. caller must set-visible + _this.mirror.start(ev.subjectEl, ev.pageX, ev.pageY); // must happen on first pointer down + _this.startDelay(ev); + if (!_this.minDistance) { + _this.handleDistanceSurpassed(ev); + } + } + } + }; + _this.onPointerMove = function (ev) { + if (_this.isInteracting) { // if false, still waiting for previous drag's revert + _this.emitter.trigger('pointermove', ev); + if (!_this.isDistanceSurpassed) { + var minDistance = _this.minDistance; + var distanceSq = void 0; // current distance from the origin, squared + var deltaX = ev.deltaX, deltaY = ev.deltaY; + distanceSq = deltaX * deltaX + deltaY * deltaY; + if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem + _this.handleDistanceSurpassed(ev); + } + } + if (_this.isDragging) { + // a real pointer move? (not one simulated by scrolling) + if (ev.origEvent.type !== 'scroll') { + _this.mirror.handleMove(ev.pageX, ev.pageY); + _this.autoScroller.handleMove(ev.pageX, ev.pageY); + } + _this.emitter.trigger('dragmove', ev); + } + } + }; + _this.onPointerUp = function (ev) { + if (_this.isInteracting) { // if false, still waiting for previous drag's revert + _this.isInteracting = false; + core.allowSelection(document.body); + core.allowContextMenu(document.body); + _this.emitter.trigger('pointerup', ev); // can potentially set mirrorNeedsRevert + if (_this.isDragging) { + _this.autoScroller.stop(); + _this.tryStopDrag(ev); // which will stop the mirror + } + if (_this.delayTimeoutId) { + clearTimeout(_this.delayTimeoutId); + _this.delayTimeoutId = null; + } + } + }; + var pointer = _this.pointer = new PointerDragging(containerEl); + pointer.emitter.on('pointerdown', _this.onPointerDown); + pointer.emitter.on('pointermove', _this.onPointerMove); + pointer.emitter.on('pointerup', _this.onPointerUp); + _this.mirror = new ElementMirror(); + _this.autoScroller = new AutoScroller(); + return _this; + } + FeaturefulElementDragging.prototype.destroy = function () { + this.pointer.destroy(); + }; + FeaturefulElementDragging.prototype.startDelay = function (ev) { + var _this = this; + if (typeof this.delay === 'number') { + this.delayTimeoutId = setTimeout(function () { + _this.delayTimeoutId = null; + _this.handleDelayEnd(ev); + }, this.delay); + } + else { + this.handleDelayEnd(ev); + } + }; + FeaturefulElementDragging.prototype.handleDelayEnd = function (ev) { + this.isDelayEnded = true; + this.tryStartDrag(ev); + }; + FeaturefulElementDragging.prototype.handleDistanceSurpassed = function (ev) { + this.isDistanceSurpassed = true; + this.tryStartDrag(ev); + }; + FeaturefulElementDragging.prototype.tryStartDrag = function (ev) { + if (this.isDelayEnded && this.isDistanceSurpassed) { + if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) { + this.isDragging = true; + this.mirrorNeedsRevert = false; + this.autoScroller.start(ev.pageX, ev.pageY); + this.emitter.trigger('dragstart', ev); + if (this.touchScrollAllowed === false) { + this.pointer.cancelTouchScroll(); + } + } + } + }; + FeaturefulElementDragging.prototype.tryStopDrag = function (ev) { + // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events + // that come from the document to fire beforehand. much more convenient this way. + this.mirror.stop(this.mirrorNeedsRevert, this.stopDrag.bind(this, ev) // bound with args + ); + }; + FeaturefulElementDragging.prototype.stopDrag = function (ev) { + this.isDragging = false; + this.emitter.trigger('dragend', ev); + }; + // fill in the implementations... + FeaturefulElementDragging.prototype.setIgnoreMove = function (bool) { + this.pointer.shouldIgnoreMove = bool; + }; + FeaturefulElementDragging.prototype.setMirrorIsVisible = function (bool) { + this.mirror.setIsVisible(bool); + }; + FeaturefulElementDragging.prototype.setMirrorNeedsRevert = function (bool) { + this.mirrorNeedsRevert = bool; + }; + FeaturefulElementDragging.prototype.setAutoScrollEnabled = function (bool) { + this.autoScroller.isEnabled = bool; + }; + return FeaturefulElementDragging; + }(core.ElementDragging)); + + /* + When this class is instantiated, it records the offset of an element (relative to the document topleft), + and continues to monitor scrolling, updating the cached coordinates if it needs to. + Does not access the DOM after instantiation, so highly performant. + + Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element + and an determine if a given point is inside the combined clipping rectangle. + */ + var OffsetTracker = /** @class */ (function () { + function OffsetTracker(el) { + this.origRect = core.computeRect(el); + // will work fine for divs that have overflow:hidden + this.scrollCaches = core.getClippingParents(el).map(function (el) { + return new ElementScrollGeomCache(el, true); // listen=true + }); + } + OffsetTracker.prototype.destroy = function () { + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + scrollCache.destroy(); + } + }; + OffsetTracker.prototype.computeLeft = function () { + var left = this.origRect.left; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + left += scrollCache.origScrollLeft - scrollCache.getScrollLeft(); + } + return left; + }; + OffsetTracker.prototype.computeTop = function () { + var top = this.origRect.top; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + top += scrollCache.origScrollTop - scrollCache.getScrollTop(); + } + return top; + }; + OffsetTracker.prototype.isWithinClipping = function (pageX, pageY) { + var point = { left: pageX, top: pageY }; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + if (!isIgnoredClipping(scrollCache.getEventTarget()) && + !core.pointInsideRect(point, scrollCache.clientRect)) { + return false; + } + } + return true; + }; + return OffsetTracker; + }()); + // certain clipping containers should never constrain interactions, like and + // https://github.com/fullcalendar/fullcalendar/issues/3615 + function isIgnoredClipping(node) { + var tagName = node.tagName; + return tagName === 'HTML' || tagName === 'BODY'; + } + + /* + Tracks movement over multiple droppable areas (aka "hits") + that exist in one or more DateComponents. + Relies on an existing draggable. + + emits: + - pointerdown + - dragstart + - hitchange - fires initially, even if not over a hit + - pointerup + - (hitchange - again, to null, if ended over a hit) + - dragend + */ + var HitDragging = /** @class */ (function () { + function HitDragging(dragging, droppableStore) { + var _this = this; + // options that can be set by caller + this.useSubjectCenter = false; + this.requireInitial = true; // if doesn't start out on a hit, won't emit any events + this.initialHit = null; + this.movingHit = null; + this.finalHit = null; // won't ever be populated if shouldIgnoreMove + this.handlePointerDown = function (ev) { + var dragging = _this.dragging; + _this.initialHit = null; + _this.movingHit = null; + _this.finalHit = null; + _this.prepareHits(); + _this.processFirstCoord(ev); + if (_this.initialHit || !_this.requireInitial) { + dragging.setIgnoreMove(false); + _this.emitter.trigger('pointerdown', ev); // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :( + } + else { + dragging.setIgnoreMove(true); + } + }; + this.handleDragStart = function (ev) { + _this.emitter.trigger('dragstart', ev); + _this.handleMove(ev, true); // force = fire even if initially null + }; + this.handleDragMove = function (ev) { + _this.emitter.trigger('dragmove', ev); + _this.handleMove(ev); + }; + this.handlePointerUp = function (ev) { + _this.releaseHits(); + _this.emitter.trigger('pointerup', ev); + }; + this.handleDragEnd = function (ev) { + if (_this.movingHit) { + _this.emitter.trigger('hitupdate', null, true, ev); + } + _this.finalHit = _this.movingHit; + _this.movingHit = null; + _this.emitter.trigger('dragend', ev); + }; + this.droppableStore = droppableStore; + dragging.emitter.on('pointerdown', this.handlePointerDown); + dragging.emitter.on('dragstart', this.handleDragStart); + dragging.emitter.on('dragmove', this.handleDragMove); + dragging.emitter.on('pointerup', this.handlePointerUp); + dragging.emitter.on('dragend', this.handleDragEnd); + this.dragging = dragging; + this.emitter = new core.EmitterMixin(); + } + // sets initialHit + // sets coordAdjust + HitDragging.prototype.processFirstCoord = function (ev) { + var origPoint = { left: ev.pageX, top: ev.pageY }; + var adjustedPoint = origPoint; + var subjectEl = ev.subjectEl; + var subjectRect; + if (subjectEl !== document) { + subjectRect = core.computeRect(subjectEl); + adjustedPoint = core.constrainPoint(adjustedPoint, subjectRect); + } + var initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top); + if (initialHit) { + if (this.useSubjectCenter && subjectRect) { + var slicedSubjectRect = core.intersectRects(subjectRect, initialHit.rect); + if (slicedSubjectRect) { + adjustedPoint = core.getRectCenter(slicedSubjectRect); + } + } + this.coordAdjust = core.diffPoints(adjustedPoint, origPoint); + } + else { + this.coordAdjust = { left: 0, top: 0 }; + } + }; + HitDragging.prototype.handleMove = function (ev, forceHandle) { + var hit = this.queryHitForOffset(ev.pageX + this.coordAdjust.left, ev.pageY + this.coordAdjust.top); + if (forceHandle || !isHitsEqual(this.movingHit, hit)) { + this.movingHit = hit; + this.emitter.trigger('hitupdate', hit, false, ev); + } + }; + HitDragging.prototype.prepareHits = function () { + this.offsetTrackers = core.mapHash(this.droppableStore, function (interactionSettings) { + return new OffsetTracker(interactionSettings.el); + }); + }; + HitDragging.prototype.releaseHits = function () { + var offsetTrackers = this.offsetTrackers; + for (var id in offsetTrackers) { + offsetTrackers[id].destroy(); + } + this.offsetTrackers = {}; + }; + HitDragging.prototype.queryHitForOffset = function (offsetLeft, offsetTop) { + var _a = this, droppableStore = _a.droppableStore, offsetTrackers = _a.offsetTrackers; + var bestHit = null; + for (var id in droppableStore) { + var component = droppableStore[id].component; + var offsetTracker = offsetTrackers[id]; + if (offsetTracker.isWithinClipping(offsetLeft, offsetTop)) { + var originLeft = offsetTracker.computeLeft(); + var originTop = offsetTracker.computeTop(); + var positionLeft = offsetLeft - originLeft; + var positionTop = offsetTop - originTop; + var origRect = offsetTracker.origRect; + var width = origRect.right - origRect.left; + var height = origRect.bottom - origRect.top; + if ( + // must be within the element's bounds + positionLeft >= 0 && positionLeft < width && + positionTop >= 0 && positionTop < height) { + var hit = component.queryHit(positionLeft, positionTop, width, height); + if (hit && + ( + // make sure the hit is within activeRange, meaning it's not a deal cell + !component.props.dateProfile || // hack for DayTile + core.rangeContainsRange(component.props.dateProfile.activeRange, hit.dateSpan.range)) && + (!bestHit || hit.layer > bestHit.layer)) { + // TODO: better way to re-orient rectangle + hit.rect.left += originLeft; + hit.rect.right += originLeft; + hit.rect.top += originTop; + hit.rect.bottom += originTop; + bestHit = hit; + } + } + } + } + return bestHit; + }; + return HitDragging; + }()); + function isHitsEqual(hit0, hit1) { + if (!hit0 && !hit1) { + return true; + } + if (Boolean(hit0) !== Boolean(hit1)) { + return false; + } + return core.isDateSpansEqual(hit0.dateSpan, hit1.dateSpan); + } + + /* + Monitors when the user clicks on a specific date/time of a component. + A pointerdown+pointerup on the same "hit" constitutes a click. + */ + var DateClicking = /** @class */ (function (_super) { + __extends(DateClicking, _super); + function DateClicking(settings) { + var _this = _super.call(this, settings) || this; + _this.handlePointerDown = function (ev) { + var dragging = _this.dragging; + // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired + dragging.setIgnoreMove(!_this.component.isValidDateDownEl(dragging.pointer.downEl)); + }; + // won't even fire if moving was ignored + _this.handleDragEnd = function (ev) { + var component = _this.component; + var pointer = _this.dragging.pointer; + if (!pointer.wasTouchScroll) { + var _a = _this.hitDragging, initialHit = _a.initialHit, finalHit = _a.finalHit; + if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { + component.calendar.triggerDateClick(initialHit.dateSpan, initialHit.dayEl, component.view, ev.origEvent); + } + } + }; + var component = settings.component; + // we DO want to watch pointer moves because otherwise finalHit won't get populated + _this.dragging = new FeaturefulElementDragging(component.el); + _this.dragging.autoScroller.isEnabled = false; + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, core.interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragend', _this.handleDragEnd); + return _this; + } + DateClicking.prototype.destroy = function () { + this.dragging.destroy(); + }; + return DateClicking; + }(core.Interaction)); + + /* + Tracks when the user selects a portion of time of a component, + constituted by a drag over date cells, with a possible delay at the beginning of the drag. + */ + var DateSelecting = /** @class */ (function (_super) { + __extends(DateSelecting, _super); + function DateSelecting(settings) { + var _this = _super.call(this, settings) || this; + _this.dragSelection = null; + _this.handlePointerDown = function (ev) { + var _a = _this, component = _a.component, dragging = _a.dragging; + var canSelect = component.opt('selectable') && + component.isValidDateDownEl(ev.origEvent.target); + // don't bother to watch expensive moves if component won't do selection + dragging.setIgnoreMove(!canSelect); + // if touch, require user to hold down + dragging.delay = ev.isTouch ? getComponentTouchDelay(component) : null; + }; + _this.handleDragStart = function (ev) { + _this.component.calendar.unselect(ev); // unselect previous selections + }; + _this.handleHitUpdate = function (hit, isFinal) { + var calendar = _this.component.calendar; + var dragSelection = null; + var isInvalid = false; + if (hit) { + dragSelection = joinHitsIntoSelection(_this.hitDragging.initialHit, hit, calendar.pluginSystem.hooks.dateSelectionTransformers); + if (!dragSelection || !_this.component.isDateSelectionValid(dragSelection)) { + isInvalid = true; + dragSelection = null; + } + } + if (dragSelection) { + calendar.dispatch({ type: 'SELECT_DATES', selection: dragSelection }); + } + else if (!isFinal) { // only unselect if moved away while dragging + calendar.dispatch({ type: 'UNSELECT_DATES' }); + } + if (!isInvalid) { + core.enableCursor(); + } + else { + core.disableCursor(); + } + if (!isFinal) { + _this.dragSelection = dragSelection; // only clear if moved away from all hits while dragging + } + }; + _this.handlePointerUp = function (pev) { + if (_this.dragSelection) { + // selection is already rendered, so just need to report selection + _this.component.calendar.triggerDateSelect(_this.dragSelection, pev); + _this.dragSelection = null; + } + }; + var component = settings.component; + var dragging = _this.dragging = new FeaturefulElementDragging(component.el); + dragging.touchScrollAllowed = false; + dragging.minDistance = component.opt('selectMinDistance') || 0; + dragging.autoScroller.isEnabled = component.opt('dragScroll'); + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, core.interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragstart', _this.handleDragStart); + hitDragging.emitter.on('hitupdate', _this.handleHitUpdate); + hitDragging.emitter.on('pointerup', _this.handlePointerUp); + return _this; + } + DateSelecting.prototype.destroy = function () { + this.dragging.destroy(); + }; + return DateSelecting; + }(core.Interaction)); + function getComponentTouchDelay(component) { + var delay = component.opt('selectLongPressDelay'); + if (delay == null) { + delay = component.opt('longPressDelay'); + } + return delay; + } + function joinHitsIntoSelection(hit0, hit1, dateSelectionTransformers) { + var dateSpan0 = hit0.dateSpan; + var dateSpan1 = hit1.dateSpan; + var ms = [ + dateSpan0.range.start, + dateSpan0.range.end, + dateSpan1.range.start, + dateSpan1.range.end + ]; + ms.sort(core.compareNumbers); + var props = {}; + for (var _i = 0, dateSelectionTransformers_1 = dateSelectionTransformers; _i < dateSelectionTransformers_1.length; _i++) { + var transformer = dateSelectionTransformers_1[_i]; + var res = transformer(hit0, hit1); + if (res === false) { + return null; + } + else if (res) { + __assign(props, res); + } + } + props.range = { start: ms[0], end: ms[3] }; + props.allDay = dateSpan0.allDay; + return props; + } + + var EventDragging = /** @class */ (function (_super) { + __extends(EventDragging, _super); + function EventDragging(settings) { + var _this = _super.call(this, settings) || this; + // internal state + _this.subjectSeg = null; // the seg being selected/dragged + _this.isDragging = false; + _this.eventRange = null; + _this.relevantEvents = null; // the events being dragged + _this.receivingCalendar = null; + _this.validMutation = null; + _this.mutatedRelevantEvents = null; + _this.handlePointerDown = function (ev) { + var origTarget = ev.origEvent.target; + var _a = _this, component = _a.component, dragging = _a.dragging; + var mirror = dragging.mirror; + var initialCalendar = component.calendar; + var subjectSeg = _this.subjectSeg = core.getElSeg(ev.subjectEl); + var eventRange = _this.eventRange = subjectSeg.eventRange; + var eventInstanceId = eventRange.instance.instanceId; + _this.relevantEvents = core.getRelevantEvents(initialCalendar.state.eventStore, eventInstanceId); + dragging.minDistance = ev.isTouch ? 0 : component.opt('eventDragMinDistance'); + dragging.delay = + // only do a touch delay if touch and this event hasn't been selected yet + (ev.isTouch && eventInstanceId !== component.props.eventSelection) ? + getComponentTouchDelay$1(component) : + null; + mirror.parentNode = initialCalendar.el; + mirror.revertDuration = component.opt('dragRevertDuration'); + var isValid = component.isValidSegDownEl(origTarget) && + !core.elementClosest(origTarget, '.fc-resizer'); + dragging.setIgnoreMove(!isValid); + // disable dragging for elements that are resizable (ie, selectable) + // but are not draggable + _this.isDragging = isValid && + ev.subjectEl.classList.contains('fc-draggable'); + }; + _this.handleDragStart = function (ev) { + var initialCalendar = _this.component.calendar; + var eventRange = _this.eventRange; + var eventInstanceId = eventRange.instance.instanceId; + if (ev.isTouch) { + // need to select a different event? + if (eventInstanceId !== _this.component.props.eventSelection) { + initialCalendar.dispatch({ type: 'SELECT_EVENT', eventInstanceId: eventInstanceId }); + } + } + else { + // if now using mouse, but was previous touch interaction, clear selected event + initialCalendar.dispatch({ type: 'UNSELECT_EVENT' }); + } + if (_this.isDragging) { + initialCalendar.unselect(ev); // unselect *date* selection + initialCalendar.publiclyTrigger('eventDragStart', [ + { + el: _this.subjectSeg.el, + event: new core.EventApi(initialCalendar, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent, + view: _this.component.view + } + ]); + } + }; + _this.handleHitUpdate = function (hit, isFinal) { + if (!_this.isDragging) { + return; + } + var relevantEvents = _this.relevantEvents; + var initialHit = _this.hitDragging.initialHit; + var initialCalendar = _this.component.calendar; + // states based on new hit + var receivingCalendar = null; + var mutation = null; + var mutatedRelevantEvents = null; + var isInvalid = false; + var interaction = { + affectedEvents: relevantEvents, + mutatedEvents: core.createEmptyEventStore(), + isEvent: true, + origSeg: _this.subjectSeg + }; + if (hit) { + var receivingComponent = hit.component; + receivingCalendar = receivingComponent.calendar; + if (initialCalendar === receivingCalendar || + receivingComponent.opt('editable') && receivingComponent.opt('droppable')) { + mutation = computeEventMutation(initialHit, hit, receivingCalendar.pluginSystem.hooks.eventDragMutationMassagers); + if (mutation) { + mutatedRelevantEvents = core.applyMutationToEventStore(relevantEvents, receivingCalendar.eventUiBases, mutation, receivingCalendar); + interaction.mutatedEvents = mutatedRelevantEvents; + if (!receivingComponent.isInteractionValid(interaction)) { + isInvalid = true; + mutation = null; + mutatedRelevantEvents = null; + interaction.mutatedEvents = core.createEmptyEventStore(); + } + } + } + else { + receivingCalendar = null; + } + } + _this.displayDrag(receivingCalendar, interaction); + if (!isInvalid) { + core.enableCursor(); + } + else { + core.disableCursor(); + } + if (!isFinal) { + if (initialCalendar === receivingCalendar && // TODO: write test for this + isHitsEqual(initialHit, hit)) { + mutation = null; + } + _this.dragging.setMirrorNeedsRevert(!mutation); + // render the mirror if no already-rendered mirror + // TODO: wish we could somehow wait for dispatch to guarantee render + _this.dragging.setMirrorIsVisible(!hit || !document.querySelector('.fc-mirror')); + // assign states based on new hit + _this.receivingCalendar = receivingCalendar; + _this.validMutation = mutation; + _this.mutatedRelevantEvents = mutatedRelevantEvents; + } + }; + _this.handlePointerUp = function () { + if (!_this.isDragging) { + _this.cleanup(); // because handleDragEnd won't fire + } + }; + _this.handleDragEnd = function (ev) { + if (_this.isDragging) { + var initialCalendar_1 = _this.component.calendar; + var initialView = _this.component.view; + var receivingCalendar = _this.receivingCalendar; + var eventDef = _this.eventRange.def; + var eventInstance = _this.eventRange.instance; + var eventApi = new core.EventApi(initialCalendar_1, eventDef, eventInstance); + var relevantEvents_1 = _this.relevantEvents; + var mutatedRelevantEvents = _this.mutatedRelevantEvents; + var finalHit = _this.hitDragging.finalHit; + _this.clearDrag(); // must happen after revert animation + initialCalendar_1.publiclyTrigger('eventDragStop', [ + { + el: _this.subjectSeg.el, + event: eventApi, + jsEvent: ev.origEvent, + view: initialView + } + ]); + if (_this.validMutation) { + // dropped within same calendar + if (receivingCalendar === initialCalendar_1) { + initialCalendar_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents + }); + var eventDropArg = {}; + for (var _i = 0, _a = initialCalendar_1.pluginSystem.hooks.eventDropTransformers; _i < _a.length; _i++) { + var transformer = _a[_i]; + __assign(eventDropArg, transformer(_this.validMutation, initialCalendar_1)); + } + __assign(eventDropArg, { + el: ev.subjectEl, + delta: _this.validMutation.startDelta, + oldEvent: eventApi, + event: new core.EventApi(// the data AFTER the mutation + initialCalendar_1, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null), + revert: function () { + initialCalendar_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents_1 + }); + }, + jsEvent: ev.origEvent, + view: initialView + }); + initialCalendar_1.publiclyTrigger('eventDrop', [eventDropArg]); + // dropped in different calendar + } + else if (receivingCalendar) { + initialCalendar_1.publiclyTrigger('eventLeave', [ + { + draggedEl: ev.subjectEl, + event: eventApi, + view: initialView + } + ]); + initialCalendar_1.dispatch({ + type: 'REMOVE_EVENT_INSTANCES', + instances: _this.mutatedRelevantEvents.instances + }); + receivingCalendar.dispatch({ + type: 'MERGE_EVENTS', + eventStore: _this.mutatedRelevantEvents + }); + if (ev.isTouch) { + receivingCalendar.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: eventInstance.instanceId + }); + } + var dropArg = receivingCalendar.buildDatePointApi(finalHit.dateSpan); + dropArg.draggedEl = ev.subjectEl; + dropArg.jsEvent = ev.origEvent; + dropArg.view = finalHit.component; // ? + receivingCalendar.publiclyTrigger('drop', [dropArg]); + receivingCalendar.publiclyTrigger('eventReceive', [ + { + draggedEl: ev.subjectEl, + event: new core.EventApi(// the data AFTER the mutation + receivingCalendar, mutatedRelevantEvents.defs[eventDef.defId], mutatedRelevantEvents.instances[eventInstance.instanceId]), + view: finalHit.component + } + ]); + } + } + else { + initialCalendar_1.publiclyTrigger('_noEventDrop'); + } + } + _this.cleanup(); + }; + var component = _this.component; + var dragging = _this.dragging = new FeaturefulElementDragging(component.el); + dragging.pointer.selector = EventDragging.SELECTOR; + dragging.touchScrollAllowed = false; + dragging.autoScroller.isEnabled = component.opt('dragScroll'); + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, core.interactionSettingsStore); + hitDragging.useSubjectCenter = settings.useEventCenter; + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragstart', _this.handleDragStart); + hitDragging.emitter.on('hitupdate', _this.handleHitUpdate); + hitDragging.emitter.on('pointerup', _this.handlePointerUp); + hitDragging.emitter.on('dragend', _this.handleDragEnd); + return _this; + } + EventDragging.prototype.destroy = function () { + this.dragging.destroy(); + }; + // render a drag state on the next receivingCalendar + EventDragging.prototype.displayDrag = function (nextCalendar, state) { + var initialCalendar = this.component.calendar; + var prevCalendar = this.receivingCalendar; + // does the previous calendar need to be cleared? + if (prevCalendar && prevCalendar !== nextCalendar) { + // does the initial calendar need to be cleared? + // if so, don't clear all the way. we still need to to hide the affectedEvents + if (prevCalendar === initialCalendar) { + prevCalendar.dispatch({ + type: 'SET_EVENT_DRAG', + state: { + affectedEvents: state.affectedEvents, + mutatedEvents: core.createEmptyEventStore(), + isEvent: true, + origSeg: state.origSeg + } + }); + // completely clear the old calendar if it wasn't the initial + } + else { + prevCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + } + if (nextCalendar) { + nextCalendar.dispatch({ type: 'SET_EVENT_DRAG', state: state }); + } + }; + EventDragging.prototype.clearDrag = function () { + var initialCalendar = this.component.calendar; + var receivingCalendar = this.receivingCalendar; + if (receivingCalendar) { + receivingCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + // the initial calendar might have an dummy drag state from displayDrag + if (initialCalendar !== receivingCalendar) { + initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + }; + EventDragging.prototype.cleanup = function () { + this.subjectSeg = null; + this.isDragging = false; + this.eventRange = null; + this.relevantEvents = null; + this.receivingCalendar = null; + this.validMutation = null; + this.mutatedRelevantEvents = null; + }; + EventDragging.SELECTOR = '.fc-draggable, .fc-resizable'; // TODO: test this in IE11 + return EventDragging; + }(core.Interaction)); + function computeEventMutation(hit0, hit1, massagers) { + var dateSpan0 = hit0.dateSpan; + var dateSpan1 = hit1.dateSpan; + var date0 = dateSpan0.range.start; + var date1 = dateSpan1.range.start; + var standardProps = {}; + if (dateSpan0.allDay !== dateSpan1.allDay) { + standardProps.allDay = dateSpan1.allDay; + standardProps.hasEnd = hit1.component.opt('allDayMaintainDuration'); + if (dateSpan1.allDay) { + // means date1 is already start-of-day, + // but date0 needs to be converted + date0 = core.startOfDay(date0); + } + } + var delta = core.diffDates(date0, date1, hit0.component.dateEnv, hit0.component === hit1.component ? + hit0.component.largeUnit : + null); + if (delta.milliseconds) { // has hours/minutes/seconds + standardProps.allDay = false; + } + var mutation = { + startDelta: delta, + endDelta: delta, + standardProps: standardProps + }; + for (var _i = 0, massagers_1 = massagers; _i < massagers_1.length; _i++) { + var massager = massagers_1[_i]; + massager(mutation, hit0, hit1); + } + return mutation; + } + function getComponentTouchDelay$1(component) { + var delay = component.opt('eventLongPressDelay'); + if (delay == null) { + delay = component.opt('longPressDelay'); + } + return delay; + } + + var EventDragging$1 = /** @class */ (function (_super) { + __extends(EventDragging, _super); + function EventDragging(settings) { + var _this = _super.call(this, settings) || this; + // internal state + _this.draggingSeg = null; // TODO: rename to resizingSeg? subjectSeg? + _this.eventRange = null; + _this.relevantEvents = null; + _this.validMutation = null; + _this.mutatedRelevantEvents = null; + _this.handlePointerDown = function (ev) { + var component = _this.component; + var seg = _this.querySeg(ev); + var eventRange = _this.eventRange = seg.eventRange; + _this.dragging.minDistance = component.opt('eventDragMinDistance'); + // if touch, need to be working with a selected event + _this.dragging.setIgnoreMove(!_this.component.isValidSegDownEl(ev.origEvent.target) || + (ev.isTouch && _this.component.props.eventSelection !== eventRange.instance.instanceId)); + }; + _this.handleDragStart = function (ev) { + var calendar = _this.component.calendar; + var eventRange = _this.eventRange; + _this.relevantEvents = core.getRelevantEvents(calendar.state.eventStore, _this.eventRange.instance.instanceId); + _this.draggingSeg = _this.querySeg(ev); + calendar.unselect(); + calendar.publiclyTrigger('eventResizeStart', [ + { + el: _this.draggingSeg.el, + event: new core.EventApi(calendar, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent, + view: _this.component.view + } + ]); + }; + _this.handleHitUpdate = function (hit, isFinal, ev) { + var calendar = _this.component.calendar; + var relevantEvents = _this.relevantEvents; + var initialHit = _this.hitDragging.initialHit; + var eventInstance = _this.eventRange.instance; + var mutation = null; + var mutatedRelevantEvents = null; + var isInvalid = false; + var interaction = { + affectedEvents: relevantEvents, + mutatedEvents: core.createEmptyEventStore(), + isEvent: true, + origSeg: _this.draggingSeg + }; + if (hit) { + mutation = computeMutation(initialHit, hit, ev.subjectEl.classList.contains('fc-start-resizer'), eventInstance.range, calendar.pluginSystem.hooks.eventResizeJoinTransforms); + } + if (mutation) { + mutatedRelevantEvents = core.applyMutationToEventStore(relevantEvents, calendar.eventUiBases, mutation, calendar); + interaction.mutatedEvents = mutatedRelevantEvents; + if (!_this.component.isInteractionValid(interaction)) { + isInvalid = true; + mutation = null; + mutatedRelevantEvents = null; + interaction.mutatedEvents = null; + } + } + if (mutatedRelevantEvents) { + calendar.dispatch({ + type: 'SET_EVENT_RESIZE', + state: interaction + }); + } + else { + calendar.dispatch({ type: 'UNSET_EVENT_RESIZE' }); + } + if (!isInvalid) { + core.enableCursor(); + } + else { + core.disableCursor(); + } + if (!isFinal) { + if (mutation && isHitsEqual(initialHit, hit)) { + mutation = null; + } + _this.validMutation = mutation; + _this.mutatedRelevantEvents = mutatedRelevantEvents; + } + }; + _this.handleDragEnd = function (ev) { + var calendar = _this.component.calendar; + var view = _this.component.view; + var eventDef = _this.eventRange.def; + var eventInstance = _this.eventRange.instance; + var eventApi = new core.EventApi(calendar, eventDef, eventInstance); + var relevantEvents = _this.relevantEvents; + var mutatedRelevantEvents = _this.mutatedRelevantEvents; + calendar.publiclyTrigger('eventResizeStop', [ + { + el: _this.draggingSeg.el, + event: eventApi, + jsEvent: ev.origEvent, + view: view + } + ]); + if (_this.validMutation) { + calendar.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents + }); + calendar.publiclyTrigger('eventResize', [ + { + el: _this.draggingSeg.el, + startDelta: _this.validMutation.startDelta || core.createDuration(0), + endDelta: _this.validMutation.endDelta || core.createDuration(0), + prevEvent: eventApi, + event: new core.EventApi(// the data AFTER the mutation + calendar, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null), + revert: function () { + calendar.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents + }); + }, + jsEvent: ev.origEvent, + view: view + } + ]); + } + else { + calendar.publiclyTrigger('_noEventResize'); + } + // reset all internal state + _this.draggingSeg = null; + _this.relevantEvents = null; + _this.validMutation = null; + // okay to keep eventInstance around. useful to set it in handlePointerDown + }; + var component = settings.component; + var dragging = _this.dragging = new FeaturefulElementDragging(component.el); + dragging.pointer.selector = '.fc-resizer'; + dragging.touchScrollAllowed = false; + dragging.autoScroller.isEnabled = component.opt('dragScroll'); + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, core.interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragstart', _this.handleDragStart); + hitDragging.emitter.on('hitupdate', _this.handleHitUpdate); + hitDragging.emitter.on('dragend', _this.handleDragEnd); + return _this; + } + EventDragging.prototype.destroy = function () { + this.dragging.destroy(); + }; + EventDragging.prototype.querySeg = function (ev) { + return core.getElSeg(core.elementClosest(ev.subjectEl, this.component.fgSegSelector)); + }; + return EventDragging; + }(core.Interaction)); + function computeMutation(hit0, hit1, isFromStart, instanceRange, transforms) { + var dateEnv = hit0.component.dateEnv; + var date0 = hit0.dateSpan.range.start; + var date1 = hit1.dateSpan.range.start; + var delta = core.diffDates(date0, date1, dateEnv, hit0.component.largeUnit); + var props = {}; + for (var _i = 0, transforms_1 = transforms; _i < transforms_1.length; _i++) { + var transform = transforms_1[_i]; + var res = transform(hit0, hit1); + if (res === false) { + return null; + } + else if (res) { + __assign(props, res); + } + } + if (isFromStart) { + if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) { + props.startDelta = delta; + return props; + } + } + else { + if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) { + props.endDelta = delta; + return props; + } + } + return null; + } + + var UnselectAuto = /** @class */ (function () { + function UnselectAuto(calendar) { + var _this = this; + this.isRecentPointerDateSelect = false; // wish we could use a selector to detect date selection, but uses hit system + this.onSelect = function (selectInfo) { + if (selectInfo.jsEvent) { + _this.isRecentPointerDateSelect = true; + } + }; + this.onDocumentPointerUp = function (pev) { + var _a = _this, calendar = _a.calendar, documentPointer = _a.documentPointer; + var state = calendar.state; + // touch-scrolling should never unfocus any type of selection + if (!documentPointer.wasTouchScroll) { + if (state.dateSelection && // an existing date selection? + !_this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp? + ) { + var unselectAuto = calendar.viewOpt('unselectAuto'); + var unselectCancel = calendar.viewOpt('unselectCancel'); + if (unselectAuto && (!unselectAuto || !core.elementClosest(documentPointer.downEl, unselectCancel))) { + calendar.unselect(pev); + } + } + if (state.eventSelection && // an existing event selected? + !core.elementClosest(documentPointer.downEl, EventDragging.SELECTOR) // interaction DIDN'T start on an event + ) { + calendar.dispatch({ type: 'UNSELECT_EVENT' }); + } + } + _this.isRecentPointerDateSelect = false; + }; + this.calendar = calendar; + var documentPointer = this.documentPointer = new PointerDragging(document); + documentPointer.shouldIgnoreMove = true; + documentPointer.shouldWatchScroll = false; + documentPointer.emitter.on('pointerup', this.onDocumentPointerUp); + /* + TODO: better way to know about whether there was a selection with the pointer + */ + calendar.on('select', this.onSelect); + } + UnselectAuto.prototype.destroy = function () { + this.calendar.off('select', this.onSelect); + this.documentPointer.destroy(); + }; + return UnselectAuto; + }()); + + /* + Given an already instantiated draggable object for one-or-more elements, + Interprets any dragging as an attempt to drag an events that lives outside + of a calendar onto a calendar. + */ + var ExternalElementDragging = /** @class */ (function () { + function ExternalElementDragging(dragging, suppliedDragMeta) { + var _this = this; + this.receivingCalendar = null; + this.droppableEvent = null; // will exist for all drags, even if create:false + this.suppliedDragMeta = null; + this.dragMeta = null; + this.handleDragStart = function (ev) { + _this.dragMeta = _this.buildDragMeta(ev.subjectEl); + }; + this.handleHitUpdate = function (hit, isFinal, ev) { + var dragging = _this.hitDragging.dragging; + var receivingCalendar = null; + var droppableEvent = null; + var isInvalid = false; + var interaction = { + affectedEvents: core.createEmptyEventStore(), + mutatedEvents: core.createEmptyEventStore(), + isEvent: _this.dragMeta.create, + origSeg: null + }; + if (hit) { + receivingCalendar = hit.component.calendar; + if (_this.canDropElOnCalendar(ev.subjectEl, receivingCalendar)) { + droppableEvent = computeEventForDateSpan(hit.dateSpan, _this.dragMeta, receivingCalendar); + interaction.mutatedEvents = core.eventTupleToStore(droppableEvent); + isInvalid = !core.isInteractionValid(interaction, receivingCalendar); + if (isInvalid) { + interaction.mutatedEvents = core.createEmptyEventStore(); + droppableEvent = null; + } + } + } + _this.displayDrag(receivingCalendar, interaction); + // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?) + // TODO: wish we could somehow wait for dispatch to guarantee render + dragging.setMirrorIsVisible(isFinal || !droppableEvent || !document.querySelector('.fc-mirror')); + if (!isInvalid) { + core.enableCursor(); + } + else { + core.disableCursor(); + } + if (!isFinal) { + dragging.setMirrorNeedsRevert(!droppableEvent); + _this.receivingCalendar = receivingCalendar; + _this.droppableEvent = droppableEvent; + } + }; + this.handleDragEnd = function (pev) { + var _a = _this, receivingCalendar = _a.receivingCalendar, droppableEvent = _a.droppableEvent; + _this.clearDrag(); + if (receivingCalendar && droppableEvent) { + var finalHit = _this.hitDragging.finalHit; + var finalView = finalHit.component.view; + var dragMeta = _this.dragMeta; + var arg = receivingCalendar.buildDatePointApi(finalHit.dateSpan); + arg.draggedEl = pev.subjectEl; + arg.jsEvent = pev.origEvent; + arg.view = finalView; + receivingCalendar.publiclyTrigger('drop', [arg]); + if (dragMeta.create) { + receivingCalendar.dispatch({ + type: 'MERGE_EVENTS', + eventStore: core.eventTupleToStore(droppableEvent) + }); + if (pev.isTouch) { + receivingCalendar.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: droppableEvent.instance.instanceId + }); + } + // signal that an external event landed + receivingCalendar.publiclyTrigger('eventReceive', [ + { + draggedEl: pev.subjectEl, + event: new core.EventApi(receivingCalendar, droppableEvent.def, droppableEvent.instance), + view: finalView + } + ]); + } + } + _this.receivingCalendar = null; + _this.droppableEvent = null; + }; + var hitDragging = this.hitDragging = new HitDragging(dragging, core.interactionSettingsStore); + hitDragging.requireInitial = false; // will start outside of a component + hitDragging.emitter.on('dragstart', this.handleDragStart); + hitDragging.emitter.on('hitupdate', this.handleHitUpdate); + hitDragging.emitter.on('dragend', this.handleDragEnd); + this.suppliedDragMeta = suppliedDragMeta; + } + ExternalElementDragging.prototype.buildDragMeta = function (subjectEl) { + if (typeof this.suppliedDragMeta === 'object') { + return core.parseDragMeta(this.suppliedDragMeta); + } + else if (typeof this.suppliedDragMeta === 'function') { + return core.parseDragMeta(this.suppliedDragMeta(subjectEl)); + } + else { + return getDragMetaFromEl(subjectEl); + } + }; + ExternalElementDragging.prototype.displayDrag = function (nextCalendar, state) { + var prevCalendar = this.receivingCalendar; + if (prevCalendar && prevCalendar !== nextCalendar) { + prevCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + if (nextCalendar) { + nextCalendar.dispatch({ type: 'SET_EVENT_DRAG', state: state }); + } + }; + ExternalElementDragging.prototype.clearDrag = function () { + if (this.receivingCalendar) { + this.receivingCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + }; + ExternalElementDragging.prototype.canDropElOnCalendar = function (el, receivingCalendar) { + var dropAccept = receivingCalendar.opt('dropAccept'); + if (typeof dropAccept === 'function') { + return dropAccept(el); + } + else if (typeof dropAccept === 'string' && dropAccept) { + return Boolean(core.elementMatches(el, dropAccept)); + } + return true; + }; + return ExternalElementDragging; + }()); + // Utils for computing event store from the DragMeta + // ---------------------------------------------------------------------------------------------------- + function computeEventForDateSpan(dateSpan, dragMeta, calendar) { + var defProps = __assign({}, dragMeta.leftoverProps); + for (var _i = 0, _a = calendar.pluginSystem.hooks.externalDefTransforms; _i < _a.length; _i++) { + var transform = _a[_i]; + __assign(defProps, transform(dateSpan, dragMeta)); + } + var def = core.parseEventDef(defProps, dragMeta.sourceId, dateSpan.allDay, calendar.opt('forceEventDuration') || Boolean(dragMeta.duration), // hasEnd + calendar); + var start = dateSpan.range.start; + // only rely on time info if drop zone is all-day, + // otherwise, we already know the time + if (dateSpan.allDay && dragMeta.startTime) { + start = calendar.dateEnv.add(start, dragMeta.startTime); + } + var end = dragMeta.duration ? + calendar.dateEnv.add(start, dragMeta.duration) : + calendar.getDefaultEventEnd(dateSpan.allDay, start); + var instance = core.createEventInstance(def.defId, { start: start, end: end }); + return { def: def, instance: instance }; + } + // Utils for extracting data from element + // ---------------------------------------------------------------------------------------------------- + function getDragMetaFromEl(el) { + var str = getEmbeddedElData(el, 'event'); + var obj = str ? + JSON.parse(str) : + { create: false }; // if no embedded data, assume no event creation + return core.parseDragMeta(obj); + } + core.config.dataAttrPrefix = ''; + function getEmbeddedElData(el, name) { + var prefix = core.config.dataAttrPrefix; + var prefixedName = (prefix ? prefix + '-' : '') + name; + return el.getAttribute('data-' + prefixedName) || ''; + } + + /* + Makes an element (that is *external* to any calendar) draggable. + Can pass in data that determines how an event will be created when dropped onto a calendar. + Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system. + */ + var ExternalDraggable = /** @class */ (function () { + function ExternalDraggable(el, settings) { + var _this = this; + if (settings === void 0) { settings = {}; } + this.handlePointerDown = function (ev) { + var dragging = _this.dragging; + var _a = _this.settings, minDistance = _a.minDistance, longPressDelay = _a.longPressDelay; + dragging.minDistance = + minDistance != null ? + minDistance : + (ev.isTouch ? 0 : core.globalDefaults.eventDragMinDistance); + dragging.delay = + ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv + (longPressDelay != null ? longPressDelay : core.globalDefaults.longPressDelay) : + 0; + }; + this.handleDragStart = function (ev) { + if (ev.isTouch && + _this.dragging.delay && + ev.subjectEl.classList.contains('fc-event')) { + _this.dragging.mirror.getMirrorEl().classList.add('fc-selected'); + } + }; + this.settings = settings; + var dragging = this.dragging = new FeaturefulElementDragging(el); + dragging.touchScrollAllowed = false; + if (settings.itemSelector != null) { + dragging.pointer.selector = settings.itemSelector; + } + if (settings.appendTo != null) { + dragging.mirror.parentNode = settings.appendTo; // TODO: write tests + } + dragging.emitter.on('pointerdown', this.handlePointerDown); + dragging.emitter.on('dragstart', this.handleDragStart); + new ExternalElementDragging(dragging, settings.eventData); + } + ExternalDraggable.prototype.destroy = function () { + this.dragging.destroy(); + }; + return ExternalDraggable; + }()); + + /* + Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements. + The third-party system is responsible for drawing the visuals effects of the drag. + This class simply monitors for pointer movements and fires events. + It also has the ability to hide the moving element (the "mirror") during the drag. + */ + var InferredElementDragging = /** @class */ (function (_super) { + __extends(InferredElementDragging, _super); + function InferredElementDragging(containerEl) { + var _this = _super.call(this, containerEl) || this; + _this.shouldIgnoreMove = false; + _this.mirrorSelector = ''; + _this.currentMirrorEl = null; + _this.handlePointerDown = function (ev) { + _this.emitter.trigger('pointerdown', ev); + if (!_this.shouldIgnoreMove) { + // fire dragstart right away. does not support delay or min-distance + _this.emitter.trigger('dragstart', ev); + } + }; + _this.handlePointerMove = function (ev) { + if (!_this.shouldIgnoreMove) { + _this.emitter.trigger('dragmove', ev); + } + }; + _this.handlePointerUp = function (ev) { + _this.emitter.trigger('pointerup', ev); + if (!_this.shouldIgnoreMove) { + // fire dragend right away. does not support a revert animation + _this.emitter.trigger('dragend', ev); + } + }; + var pointer = _this.pointer = new PointerDragging(containerEl); + pointer.emitter.on('pointerdown', _this.handlePointerDown); + pointer.emitter.on('pointermove', _this.handlePointerMove); + pointer.emitter.on('pointerup', _this.handlePointerUp); + return _this; + } + InferredElementDragging.prototype.destroy = function () { + this.pointer.destroy(); + }; + InferredElementDragging.prototype.setIgnoreMove = function (bool) { + this.shouldIgnoreMove = bool; + }; + InferredElementDragging.prototype.setMirrorIsVisible = function (bool) { + if (bool) { + // restore a previously hidden element. + // use the reference in case the selector class has already been removed. + if (this.currentMirrorEl) { + this.currentMirrorEl.style.visibility = ''; + this.currentMirrorEl = null; + } + } + else { + var mirrorEl = this.mirrorSelector ? + document.querySelector(this.mirrorSelector) : + null; + if (mirrorEl) { + this.currentMirrorEl = mirrorEl; + mirrorEl.style.visibility = 'hidden'; + } + } + }; + return InferredElementDragging; + }(core.ElementDragging)); + + /* + Bridges third-party drag-n-drop systems with FullCalendar. + Must be instantiated and destroyed by caller. + */ + var ThirdPartyDraggable = /** @class */ (function () { + function ThirdPartyDraggable(containerOrSettings, settings) { + var containerEl = document; + if ( + // wish we could just test instanceof EventTarget, but doesn't work in IE11 + containerOrSettings === document || + containerOrSettings instanceof Element) { + containerEl = containerOrSettings; + settings = settings || {}; + } + else { + settings = (containerOrSettings || {}); + } + var dragging = this.dragging = new InferredElementDragging(containerEl); + if (typeof settings.itemSelector === 'string') { + dragging.pointer.selector = settings.itemSelector; + } + else if (containerEl === document) { + dragging.pointer.selector = '[data-event]'; + } + if (typeof settings.mirrorSelector === 'string') { + dragging.mirrorSelector = settings.mirrorSelector; + } + new ExternalElementDragging(dragging, settings.eventData); + } + ThirdPartyDraggable.prototype.destroy = function () { + this.dragging.destroy(); + }; + return ThirdPartyDraggable; + }()); + + var main = core.createPlugin({ + componentInteractions: [DateClicking, DateSelecting, EventDragging, EventDragging$1], + calendarInteractions: [UnselectAuto], + elementDraggingImpl: FeaturefulElementDragging + }); + + exports.Draggable = ExternalDraggable; + exports.FeaturefulElementDragging = FeaturefulElementDragging; + exports.PointerDragging = PointerDragging; + exports.ThirdPartyDraggable = ThirdPartyDraggable; + exports.default = main; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/library/fullcalendar/packages/interaction/main.min.js b/library/fullcalendar/packages/interaction/main.min.js new file mode 100644 index 000000000..58c189c8a --- /dev/null +++ b/library/fullcalendar/packages/interaction/main.min.js @@ -0,0 +1,21 @@ +/*! +FullCalendar Interaction Plugin v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@fullcalendar/core")):"function"==typeof define&&define.amd?define(["exports","@fullcalendar/core"],t):(e=e||self,t(e.FullCalendarInteraction={},e.FullCalendar))}(this,function(e,t){"use strict";function n(e,t){function n(){this.constructor=e}m(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}function r(e){return 0===e.button&&!e.ctrlKey}function i(){y++,setTimeout(function(){y--},t.config.touchMouseIgnoreWait)}function o(){D++||window.addEventListener("touchmove",l,{passive:!1})}function a(){--D||window.removeEventListener("touchmove",l,{passive:!1})}function l(e){w&&e.preventDefault()}function s(e){var t=e.tagName;return"HTML"===t||"BODY"===t}function c(e,n){return!e&&!n||Boolean(e)===Boolean(n)&&t.isDateSpansEqual(e.dateSpan,n.dateSpan)}function d(e){var t=e.opt("selectLongPressDelay");return null==t&&(t=e.opt("longPressDelay")),t}function u(e,n,r){var i=e.dateSpan,o=n.dateSpan,a=[i.range.start,i.range.end,o.range.start,o.range.end];a.sort(t.compareNumbers);for(var l={},s=0,c=r;si.start)return d.endDelta=c,d;return null}function v(e,n,r){for(var i=S({},n.leftoverProps),o=0,a=r.pluginSystem.hooks.externalDefTransforms;o0&&(this.everMovedDown=!0),o<0?this.everMovedLeft=!0:o>0&&(this.everMovedRight=!0),this.pointerScreenX=n,this.pointerScreenY=r,this.isAnimating||(this.isAnimating=!0,this.requestAnimation(I()))}},e.prototype.stop=function(){if(this.isEnabled){this.isAnimating=!1;for(var e=0,t=this.scrollCaches;e=0&&c>=0&&d>=0&&u>=0&&(d<=n&&this.everMovedUp&&a.canScrollUp()&&(!r||r.distance>d)&&(r={scrollCache:a,name:"top",distance:d}),u<=n&&this.everMovedDown&&a.canScrollDown()&&(!r||r.distance>u)&&(r={scrollCache:a,name:"bottom",distance:u}),s<=n&&this.everMovedLeft&&a.canScrollLeft()&&(!r||r.distance>s)&&(r={scrollCache:a,name:"left",distance:s}),c<=n&&this.everMovedRight&&a.canScrollRight()&&(!r||r.distance>c)&&(r={scrollCache:a,name:"right",distance:c}))}return r},e.prototype.buildCaches=function(){return this.queryScrollEls().map(function(e){return e===window?new R(!1):new C(e,!1)})},e.prototype.queryScrollEls=function(){for(var e=[],t=0,n=this.scrollQuery;t=t*t&&r.handleDistanceSurpassed(e)}r.isDragging&&("scroll"!==e.origEvent.type&&(r.mirror.handleMove(e.pageX,e.pageY),r.autoScroller.handleMove(e.pageX,e.pageY)),r.emitter.trigger("dragmove",e))}},r.onPointerUp=function(e){r.isInteracting&&(r.isInteracting=!1,t.allowSelection(document.body),t.allowContextMenu(document.body),r.emitter.trigger("pointerup",e),r.isDragging&&(r.autoScroller.stop(),r.tryStopDrag(e)),r.delayTimeoutId&&(clearTimeout(r.delayTimeoutId),r.delayTimeoutId=null))};var i=r.pointer=new T(n);return i.emitter.on("pointerdown",r.onPointerDown),i.emitter.on("pointermove",r.onPointerMove),i.emitter.on("pointerup",r.onPointerUp),r.mirror=new M,r.autoScroller=new P,r}return n(r,e),r.prototype.destroy=function(){this.pointer.destroy()},r.prototype.startDelay=function(e){var t=this;"number"==typeof this.delay?this.delayTimeoutId=setTimeout(function(){t.delayTimeoutId=null,t.handleDelayEnd(e)},this.delay):this.handleDelayEnd(e)},r.prototype.handleDelayEnd=function(e){this.isDelayEnded=!0,this.tryStartDrag(e)},r.prototype.handleDistanceSurpassed=function(e){this.isDistanceSurpassed=!0,this.tryStartDrag(e)},r.prototype.tryStartDrag=function(e){this.isDelayEnded&&this.isDistanceSurpassed&&(this.pointer.wasTouchScroll&&!this.touchScrollAllowed||(this.isDragging=!0,this.mirrorNeedsRevert=!1,this.autoScroller.start(e.pageX,e.pageY),this.emitter.trigger("dragstart",e),!1===this.touchScrollAllowed&&this.pointer.cancelTouchScroll()))},r.prototype.tryStopDrag=function(e){this.mirror.stop(this.mirrorNeedsRevert,this.stopDrag.bind(this,e))},r.prototype.stopDrag=function(e){this.isDragging=!1,this.emitter.trigger("dragend",e)},r.prototype.setIgnoreMove=function(e){this.pointer.shouldIgnoreMove=e},r.prototype.setMirrorIsVisible=function(e){this.mirror.setIsVisible(e)},r.prototype.setMirrorNeedsRevert=function(e){this.mirrorNeedsRevert=e},r.prototype.setAutoScrollEnabled=function(e){this.autoScroller.isEnabled=e},r}(t.ElementDragging),j=function(){function e(e){this.origRect=t.computeRect(e),this.scrollCaches=t.getClippingParents(e).map(function(e){return new C(e,!0)})}return e.prototype.destroy=function(){for(var e=0,t=this.scrollCaches;e=0&&g=0&&ha.layer)||(E.rect.left+=d,E.rect.right+=d,E.rect.top+=u,E.rect.bottom+=u,a=E)}}}return a},e}(),H=function(e){function r(n){var r=e.call(this,n)||this;r.handlePointerDown=function(e){var t=r.dragging;t.setIgnoreMove(!r.component.isValidDateDownEl(t.pointer.downEl))},r.handleDragEnd=function(e){var t=r.component;if(!r.dragging.pointer.wasTouchScroll){var n=r.hitDragging,i=n.initialHit,o=n.finalHit;i&&o&&c(i,o)&&t.calendar.triggerDateClick(i.dateSpan,i.dayEl,t.view,e.origEvent)}};var i=n.component;r.dragging=new L(i.el),r.dragging.autoScroller.isEnabled=!1;var o=r.hitDragging=new A(r.dragging,t.interactionSettingsToStore(n));return o.emitter.on("pointerdown",r.handlePointerDown),o.emitter.on("dragend",r.handleDragEnd),r}return n(r,e),r.prototype.destroy=function(){this.dragging.destroy()},r}(t.Interaction),N=function(e){function r(n){var r=e.call(this,n)||this;r.dragSelection=null,r.handlePointerDown=function(e){var t=r,n=t.component,i=t.dragging,o=n.opt("selectable")&&n.isValidDateDownEl(e.origEvent.target);i.setIgnoreMove(!o),i.delay=e.isTouch?d(n):null},r.handleDragStart=function(e){r.component.calendar.unselect(e)},r.handleHitUpdate=function(e,n){var i=r.component.calendar,o=null,a=!1;e&&((o=u(r.hitDragging.initialHit,e,i.pluginSystem.hooks.dateSelectionTransformers))&&r.component.isDateSelectionValid(o)||(a=!0,o=null)),o?i.dispatch({type:"SELECT_DATES",selection:o}):n||i.dispatch({type:"UNSELECT_DATES"}),a?t.disableCursor():t.enableCursor(),n||(r.dragSelection=o)},r.handlePointerUp=function(e){r.dragSelection&&(r.component.calendar.triggerDateSelect(r.dragSelection,e),r.dragSelection=null)};var i=n.component,o=r.dragging=new L(i.el);o.touchScrollAllowed=!1,o.minDistance=i.opt("selectMinDistance")||0,o.autoScroller.isEnabled=i.opt("dragScroll");var a=r.hitDragging=new A(r.dragging,t.interactionSettingsToStore(n));return a.emitter.on("pointerdown",r.handlePointerDown),a.emitter.on("dragstart",r.handleDragStart),a.emitter.on("hitupdate",r.handleHitUpdate),a.emitter.on("pointerup",r.handlePointerUp),r}return n(r,e),r.prototype.destroy=function(){this.dragging.destroy()},r}(t.Interaction),V=function(e){function r(n){var i=e.call(this,n)||this;i.subjectSeg=null,i.isDragging=!1,i.eventRange=null,i.relevantEvents=null,i.receivingCalendar=null,i.validMutation=null,i.mutatedRelevantEvents=null,i.handlePointerDown=function(e){var n=e.origEvent.target,r=i,o=r.component,a=r.dragging,l=a.mirror,s=o.calendar,c=i.subjectSeg=t.getElSeg(e.subjectEl),d=i.eventRange=c.eventRange,u=d.instance.instanceId;i.relevantEvents=t.getRelevantEvents(s.state.eventStore,u),a.minDistance=e.isTouch?0:o.opt("eventDragMinDistance"),a.delay=e.isTouch&&u!==o.props.eventSelection?h(o):null,l.parentNode=s.el,l.revertDuration=o.opt("dragRevertDuration");var g=o.isValidSegDownEl(n)&&!t.elementClosest(n,".fc-resizer");a.setIgnoreMove(!g),i.isDragging=g&&e.subjectEl.classList.contains("fc-draggable")},i.handleDragStart=function(e){var n=i.component.calendar,r=i.eventRange,o=r.instance.instanceId;e.isTouch?o!==i.component.props.eventSelection&&n.dispatch({type:"SELECT_EVENT",eventInstanceId:o}):n.dispatch({type:"UNSELECT_EVENT"}),i.isDragging&&(n.unselect(e),n.publiclyTrigger("eventDragStart",[{el:i.subjectSeg.el,event:new t.EventApi(n,r.def,r.instance),jsEvent:e.origEvent,view:i.component.view}]))},i.handleHitUpdate=function(e,n){if(i.isDragging){var r=i.relevantEvents,o=i.hitDragging.initialHit,a=i.component.calendar,l=null,s=null,d=null,u=!1,h={affectedEvents:r,mutatedEvents:t.createEmptyEventStore(),isEvent:!0,origSeg:i.subjectSeg};if(e){var p=e.component;l=p.calendar,a===l||p.opt("editable")&&p.opt("droppable")?(s=g(o,e,l.pluginSystem.hooks.eventDragMutationMassagers))&&(d=t.applyMutationToEventStore(r,l.eventUiBases,s,l),h.mutatedEvents=d,p.isInteractionValid(h)||(u=!0,s=null,d=null,h.mutatedEvents=t.createEmptyEventStore())):l=null}i.displayDrag(l,h),u?t.disableCursor():t.enableCursor(),n||(a===l&&c(o,e)&&(s=null),i.dragging.setMirrorNeedsRevert(!s),i.dragging.setMirrorIsVisible(!e||!document.querySelector(".fc-mirror")),i.receivingCalendar=l,i.validMutation=s,i.mutatedRelevantEvents=d)}},i.handlePointerUp=function(){i.isDragging||i.cleanup()},i.handleDragEnd=function(e){if(i.isDragging){var n=i.component.calendar,r=i.component.view,o=i.receivingCalendar,a=i.eventRange.def,l=i.eventRange.instance,s=new t.EventApi(n,a,l),c=i.relevantEvents,d=i.mutatedRelevantEvents,u=i.hitDragging.finalHit;if(i.clearDrag(),n.publiclyTrigger("eventDragStop",[{el:i.subjectSeg.el,event:s,jsEvent:e.origEvent,view:r}]),i.validMutation){if(o===n){n.dispatch({type:"MERGE_EVENTS",eventStore:d});for(var g={},h=0,p=n.pluginSystem.hooks.eventDropTransformers;h tag */ + text-decoration: none; + color: inherit; } + +.fc-list-item-title a[href]:hover { + /* hover effect only on titles with hrefs */ + text-decoration: underline; } + +/* message when no events */ +.fc-list-empty-wrap2 { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; } + +.fc-list-empty-wrap1 { + width: 100%; + height: 100%; + display: table; } + +.fc-list-empty { + display: table-cell; + vertical-align: middle; + text-align: center; } + +.fc-unthemed .fc-list-empty { + /* theme will provide own background */ + background-color: #eee; } diff --git a/library/fullcalendar/packages/list/main.js b/library/fullcalendar/packages/list/main.js new file mode 100644 index 000000000..abd7c1192 --- /dev/null +++ b/library/fullcalendar/packages/list/main.js @@ -0,0 +1,341 @@ +/*! +FullCalendar List View Plugin v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@fullcalendar/core')) : + typeof define === 'function' && define.amd ? define(['exports', '@fullcalendar/core'], factory) : + (global = global || self, factory(global.FullCalendarList = {}, global.FullCalendar)); +}(this, function (exports, core) { 'use strict'; + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */ + /* global Reflect, Promise */ + + var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + + function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var ListEventRenderer = /** @class */ (function (_super) { + __extends(ListEventRenderer, _super); + function ListEventRenderer(listView) { + var _this = _super.call(this, listView.context) || this; + _this.listView = listView; + return _this; + } + ListEventRenderer.prototype.attachSegs = function (segs) { + if (!segs.length) { + this.listView.renderEmptyMessage(); + } + else { + this.listView.renderSegList(segs); + } + }; + ListEventRenderer.prototype.detachSegs = function () { + }; + // generates the HTML for a single event row + ListEventRenderer.prototype.renderSegHtml = function (seg) { + var _a = this.context, view = _a.view, theme = _a.theme; + var eventRange = seg.eventRange; + var eventDef = eventRange.def; + var eventInstance = eventRange.instance; + var eventUi = eventRange.ui; + var url = eventDef.url; + var classes = ['fc-list-item'].concat(eventUi.classNames); + var bgColor = eventUi.backgroundColor; + var timeHtml; + if (eventDef.allDay) { + timeHtml = core.getAllDayHtml(view); + } + else if (core.isMultiDayRange(eventRange.range)) { + if (seg.isStart) { + timeHtml = core.htmlEscape(this._getTimeText(eventInstance.range.start, seg.end, false // allDay + )); + } + else if (seg.isEnd) { + timeHtml = core.htmlEscape(this._getTimeText(seg.start, eventInstance.range.end, false // allDay + )); + } + else { // inner segment that lasts the whole day + timeHtml = core.getAllDayHtml(view); + } + } + else { + // Display the normal time text for the *event's* times + timeHtml = core.htmlEscape(this.getTimeText(eventRange)); + } + if (url) { + classes.push('fc-has-url'); + } + return '' + + (this.displayEventTime ? + '' + + (timeHtml || '') + + '' : + '') + + '' + + '' + + '' + + '' + + '' + + core.htmlEscape(eventDef.title || '') + + '' + + '' + + ''; + }; + // like "4:00am" + ListEventRenderer.prototype.computeEventTimeFormat = function () { + return { + hour: 'numeric', + minute: '2-digit', + meridiem: 'short' + }; + }; + return ListEventRenderer; + }(core.FgEventRenderer)); + + /* + Responsible for the scroller, and forwarding event-related actions into the "grid". + */ + var ListView = /** @class */ (function (_super) { + __extends(ListView, _super); + function ListView(context, viewSpec, dateProfileGenerator, parentEl) { + var _this = _super.call(this, context, viewSpec, dateProfileGenerator, parentEl) || this; + _this.computeDateVars = core.memoize(computeDateVars); + _this.eventStoreToSegs = core.memoize(_this._eventStoreToSegs); + var eventRenderer = _this.eventRenderer = new ListEventRenderer(_this); + _this.renderContent = core.memoizeRendering(eventRenderer.renderSegs.bind(eventRenderer), eventRenderer.unrender.bind(eventRenderer)); + _this.el.classList.add('fc-list-view'); + var listViewClassNames = (_this.theme.getClass('listView') || '').split(' '); // wish we didn't have to do this + for (var _i = 0, listViewClassNames_1 = listViewClassNames; _i < listViewClassNames_1.length; _i++) { + var listViewClassName = listViewClassNames_1[_i]; + if (listViewClassName) { // in case input was empty string + _this.el.classList.add(listViewClassName); + } + } + _this.scroller = new core.ScrollComponent('hidden', // overflow x + 'auto' // overflow y + ); + _this.el.appendChild(_this.scroller.el); + _this.contentEl = _this.scroller.el; // shortcut + context.calendar.registerInteractiveComponent(_this, { + el: _this.el + // TODO: make aware that it doesn't do Hits + }); + return _this; + } + ListView.prototype.render = function (props) { + var _a = this.computeDateVars(props.dateProfile), dayDates = _a.dayDates, dayRanges = _a.dayRanges; + this.dayDates = dayDates; + this.renderContent(this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges)); + }; + ListView.prototype.destroy = function () { + _super.prototype.destroy.call(this); + this.scroller.destroy(); // will remove the Grid too + this.calendar.unregisterInteractiveComponent(this); + }; + ListView.prototype.updateSize = function (isResize, viewHeight, isAuto) { + _super.prototype.updateSize.call(this, isResize, viewHeight, isAuto); + this.eventRenderer.computeSizes(isResize); + this.eventRenderer.assignSizes(isResize); + this.scroller.clear(); // sets height to 'auto' and clears overflow + if (!isAuto) { + this.scroller.setHeight(this.computeScrollerHeight(viewHeight)); + } + }; + ListView.prototype.computeScrollerHeight = function (viewHeight) { + return viewHeight - + core.subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller + }; + ListView.prototype._eventStoreToSegs = function (eventStore, eventUiBases, dayRanges) { + return this.eventRangesToSegs(core.sliceEventStore(eventStore, eventUiBases, this.props.dateProfile.activeRange, this.nextDayThreshold).fg, dayRanges); + }; + ListView.prototype.eventRangesToSegs = function (eventRanges, dayRanges) { + var segs = []; + for (var _i = 0, eventRanges_1 = eventRanges; _i < eventRanges_1.length; _i++) { + var eventRange = eventRanges_1[_i]; + segs.push.apply(segs, this.eventRangeToSegs(eventRange, dayRanges)); + } + return segs; + }; + ListView.prototype.eventRangeToSegs = function (eventRange, dayRanges) { + var _a = this, dateEnv = _a.dateEnv, nextDayThreshold = _a.nextDayThreshold; + var range = eventRange.range; + var allDay = eventRange.def.allDay; + var dayIndex; + var segRange; + var seg; + var segs = []; + for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex++) { + segRange = core.intersectRanges(range, dayRanges[dayIndex]); + if (segRange) { + seg = { + component: this, + eventRange: eventRange, + start: segRange.start, + end: segRange.end, + isStart: eventRange.isStart && segRange.start.valueOf() === range.start.valueOf(), + isEnd: eventRange.isEnd && segRange.end.valueOf() === range.end.valueOf(), + dayIndex: dayIndex + }; + segs.push(seg); + // detect when range won't go fully into the next day, + // and mutate the latest seg to the be the end. + if (!seg.isEnd && !allDay && + dayIndex + 1 < dayRanges.length && + range.end < + dateEnv.add(dayRanges[dayIndex + 1].start, nextDayThreshold)) { + seg.end = range.end; + seg.isEnd = true; + break; + } + } + } + return segs; + }; + ListView.prototype.renderEmptyMessage = function () { + this.contentEl.innerHTML = + '
' + // TODO: try less wraps + '
' + + '
' + + core.htmlEscape(this.opt('noEventsMessage')) + + '
' + + '
' + + '
'; + }; + // called by ListEventRenderer + ListView.prototype.renderSegList = function (allSegs) { + var segsByDay = this.groupSegsByDay(allSegs); // sparse array + var dayIndex; + var daySegs; + var i; + var tableEl = core.htmlToElement('
'); + var tbodyEl = tableEl.querySelector('tbody'); + for (dayIndex = 0; dayIndex < segsByDay.length; dayIndex++) { + daySegs = segsByDay[dayIndex]; + if (daySegs) { // sparse array, so might be undefined + // append a day header + tbodyEl.appendChild(this.buildDayHeaderRow(this.dayDates[dayIndex])); + daySegs = this.eventRenderer.sortEventSegs(daySegs); + for (i = 0; i < daySegs.length; i++) { + tbodyEl.appendChild(daySegs[i].el); // append event row + } + } + } + this.contentEl.innerHTML = ''; + this.contentEl.appendChild(tableEl); + }; + // Returns a sparse array of arrays, segs grouped by their dayIndex + ListView.prototype.groupSegsByDay = function (segs) { + var segsByDay = []; // sparse array + var i; + var seg; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + (segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = [])) + .push(seg); + } + return segsByDay; + }; + // generates the HTML for the day headers that live amongst the event rows + ListView.prototype.buildDayHeaderRow = function (dayDate) { + var dateEnv = this.dateEnv; + var mainFormat = core.createFormatter(this.opt('listDayFormat')); // TODO: cache + var altFormat = core.createFormatter(this.opt('listDayAltFormat')); // TODO: cache + return core.createElement('tr', { + className: 'fc-list-heading', + 'data-date': dateEnv.formatIso(dayDate, { omitTime: true }) + }, '' + + (mainFormat ? + core.buildGotoAnchorHtml(this, dayDate, { 'class': 'fc-list-heading-main' }, core.htmlEscape(dateEnv.format(dayDate, mainFormat)) // inner HTML + ) : + '') + + (altFormat ? + core.buildGotoAnchorHtml(this, dayDate, { 'class': 'fc-list-heading-alt' }, core.htmlEscape(dateEnv.format(dayDate, altFormat)) // inner HTML + ) : + '') + + ''); + }; + return ListView; + }(core.View)); + ListView.prototype.fgSegSelector = '.fc-list-item'; // which elements accept event actions + function computeDateVars(dateProfile) { + var dayStart = core.startOfDay(dateProfile.renderRange.start); + var viewEnd = dateProfile.renderRange.end; + var dayDates = []; + var dayRanges = []; + while (dayStart < viewEnd) { + dayDates.push(dayStart); + dayRanges.push({ + start: dayStart, + end: core.addDays(dayStart, 1) + }); + dayStart = core.addDays(dayStart, 1); + } + return { dayDates: dayDates, dayRanges: dayRanges }; + } + + var main = core.createPlugin({ + views: { + list: { + class: ListView, + buttonTextKey: 'list', + listDayFormat: { month: 'long', day: 'numeric', year: 'numeric' } // like "January 1, 2016" + }, + listDay: { + type: 'list', + duration: { days: 1 }, + listDayFormat: { weekday: 'long' } // day-of-week is all we need. full date is probably in header + }, + listWeek: { + type: 'list', + duration: { weeks: 1 }, + listDayFormat: { weekday: 'long' }, + listDayAltFormat: { month: 'long', day: 'numeric', year: 'numeric' } + }, + listMonth: { + type: 'list', + duration: { month: 1 }, + listDayAltFormat: { weekday: 'long' } // day-of-week is nice-to-have + }, + listYear: { + type: 'list', + duration: { year: 1 }, + listDayAltFormat: { weekday: 'long' } // day-of-week is nice-to-have + } + } + }); + + exports.ListView = ListView; + exports.default = main; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/library/fullcalendar/packages/list/main.min.css b/library/fullcalendar/packages/list/main.min.css new file mode 100644 index 000000000..6a9c9101d --- /dev/null +++ b/library/fullcalendar/packages/list/main.min.css @@ -0,0 +1,5 @@ +/*! +FullCalendar List View Plugin v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/.fc-event-dot{display:inline-block;width:10px;height:10px;border-radius:5px}.fc-rtl .fc-list-view{direction:rtl}.fc-list-view{border-width:1px;border-style:solid}.fc .fc-list-table{table-layout:auto}.fc-list-table td{border-width:1px 0 0;padding:8px 14px}.fc-list-table tr:first-child td{border-top-width:0}.fc-list-heading{border-bottom-width:1px}.fc-list-heading td{font-weight:700}.fc-ltr .fc-list-heading-main{float:left}.fc-ltr .fc-list-heading-alt,.fc-rtl .fc-list-heading-main{float:right}.fc-rtl .fc-list-heading-alt{float:left}.fc-list-item.fc-has-url{cursor:pointer}.fc-list-item-marker,.fc-list-item-time{white-space:nowrap;width:1px}.fc-ltr .fc-list-item-marker{padding-right:0}.fc-rtl .fc-list-item-marker{padding-left:0}.fc-list-item-title a{text-decoration:none;color:inherit}.fc-list-item-title a[href]:hover{text-decoration:underline}.fc-list-empty-wrap2{position:absolute;top:0;left:0;right:0;bottom:0}.fc-list-empty-wrap1{width:100%;height:100%;display:table}.fc-list-empty{display:table-cell;vertical-align:middle;text-align:center}.fc-unthemed .fc-list-empty{background-color:#eee} \ No newline at end of file diff --git a/library/fullcalendar/packages/list/main.min.js b/library/fullcalendar/packages/list/main.min.js new file mode 100644 index 000000000..3310229fa --- /dev/null +++ b/library/fullcalendar/packages/list/main.min.js @@ -0,0 +1,20 @@ +/*! +FullCalendar List View Plugin v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@fullcalendar/core")):"function"==typeof define&&define.amd?define(["exports","@fullcalendar/core"],t):(e=e||self,t(e.FullCalendarList={},e.FullCalendar))}(this,function(e,t){"use strict";function n(e,t){function n(){this.constructor=e}s(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}function r(e){for(var n=t.startOfDay(e.renderRange.start),r=e.renderRange.end,s=[],a=[];n'+(this.displayEventTime?''+(n||"")+"":"")+'"+t.htmlEscape(o.title||"")+""},r.prototype.computeEventTimeFormat=function(){return{hour:"numeric",minute:"2-digit",meridiem:"short"}},r}(t.FgEventRenderer),i=function(e){function s(n,s,i,o){var l=e.call(this,n,s,i,o)||this;l.computeDateVars=t.memoize(r),l.eventStoreToSegs=t.memoize(l._eventStoreToSegs);var d=l.eventRenderer=new a(l);l.renderContent=t.memoizeRendering(d.renderSegs.bind(d),d.unrender.bind(d)),l.el.classList.add("fc-list-view");for(var c=(l.theme.getClass("listView")||"").split(" "),p=0,h=c;p
'+t.htmlEscape(this.opt("noEventsMessage"))+"
"},s.prototype.renderSegList=function(e){var n,r,s,a=this.groupSegsByDay(e),i=t.htmlToElement('
'),o=i.querySelector("tbody");for(n=0;n'+(r?t.buildGotoAnchorHtml(this,e,{class:"fc-list-heading-main"},t.htmlEscape(n.format(e,r))):"")+(s?t.buildGotoAnchorHtml(this,e,{class:"fc-list-heading-alt"},t.htmlEscape(n.format(e,s))):"")+"")},s}(t.View);i.prototype.fgSegSelector=".fc-list-item";var o=t.createPlugin({views:{list:{class:i,buttonTextKey:"list",listDayFormat:{month:"long",day:"numeric",year:"numeric"}},listDay:{type:"list",duration:{days:1},listDayFormat:{weekday:"long"}},listWeek:{type:"list",duration:{weeks:1},listDayFormat:{weekday:"long"},listDayAltFormat:{month:"long",day:"numeric",year:"numeric"}},listMonth:{type:"list",duration:{month:1},listDayAltFormat:{weekday:"long"}},listYear:{type:"list",duration:{year:1},listDayAltFormat:{weekday:"long"}}}});e.ListView=i,e.default=o,Object.defineProperty(e,"__esModule",{value:!0})}); \ No newline at end of file diff --git a/library/fullcalendar/packages/luxon/main.js b/library/fullcalendar/packages/luxon/main.js new file mode 100644 index 000000000..ff5fc19a1 --- /dev/null +++ b/library/fullcalendar/packages/luxon/main.js @@ -0,0 +1,162 @@ +/*! +FullCalendar Luxon Plugin v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('luxon'), require('@fullcalendar/core')) : + typeof define === 'function' && define.amd ? define(['exports', 'luxon', '@fullcalendar/core'], factory) : + (global = global || self, factory(global.FullCalendarLuxon = {}, global.luxon, global.FullCalendar)); +}(this, function (exports, luxon, core) { 'use strict'; + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */ + /* global Reflect, Promise */ + + var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + + function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + + function toDateTime(date, calendar) { + if (!(calendar instanceof core.Calendar)) { + throw new Error('must supply a Calendar instance'); + } + return luxon.DateTime.fromJSDate(date, { + zone: calendar.dateEnv.timeZone, + locale: calendar.dateEnv.locale.codes[0] + }); + } + function toDuration(duration, calendar) { + if (!(calendar instanceof core.Calendar)) { + throw new Error('must supply a Calendar instance'); + } + return luxon.Duration.fromObject(__assign({}, duration, { locale: calendar.dateEnv.locale.codes[0] })); + } + var LuxonNamedTimeZone = /** @class */ (function (_super) { + __extends(LuxonNamedTimeZone, _super); + function LuxonNamedTimeZone() { + return _super !== null && _super.apply(this, arguments) || this; + } + LuxonNamedTimeZone.prototype.offsetForArray = function (a) { + return arrayToLuxon(a, this.timeZoneName).offset; + }; + LuxonNamedTimeZone.prototype.timestampToArray = function (ms) { + return luxonToArray(luxon.DateTime.fromMillis(ms, { + zone: this.timeZoneName + })); + }; + return LuxonNamedTimeZone; + }(core.NamedTimeZoneImpl)); + function formatWithCmdStr(cmdStr, arg) { + var cmd = parseCmdStr(cmdStr); + if (arg.end) { + var start = arrayToLuxon(arg.start.array, arg.timeZone, arg.localeCodes[0]); + var end = arrayToLuxon(arg.end.array, arg.timeZone, arg.localeCodes[0]); + return formatRange(cmd, start.toFormat.bind(start), end.toFormat.bind(end), arg.separator); + } + return arrayToLuxon(arg.date.array, arg.timeZone, arg.localeCodes[0]).toFormat(cmd.whole); + } + var main = core.createPlugin({ + cmdFormatter: formatWithCmdStr, + namedTimeZonedImpl: LuxonNamedTimeZone + }); + function luxonToArray(datetime) { + return [ + datetime.year, + datetime.month - 1, + datetime.day, + datetime.hour, + datetime.minute, + datetime.second, + datetime.millisecond + ]; + } + function arrayToLuxon(arr, timeZone, locale) { + return luxon.DateTime.fromObject({ + zone: timeZone, + locale: locale, + year: arr[0], + month: arr[1] + 1, + day: arr[2], + hour: arr[3], + minute: arr[4], + second: arr[5], + millisecond: arr[6] + }); + } + function parseCmdStr(cmdStr) { + var parts = cmdStr.match(/^(.*?)\{(.*)\}(.*)$/); // TODO: lookbehinds for escape characters + if (parts) { + var middle = parseCmdStr(parts[2]); + return { + head: parts[1], + middle: middle, + tail: parts[3], + whole: parts[1] + middle.whole + parts[3] + }; + } + else { + return { + head: null, + middle: null, + tail: null, + whole: cmdStr + }; + } + } + function formatRange(cmd, formatStart, formatEnd, separator) { + if (cmd.middle) { + var startHead = formatStart(cmd.head); + var startMiddle = formatRange(cmd.middle, formatStart, formatEnd, separator); + var startTail = formatStart(cmd.tail); + var endHead = formatEnd(cmd.head); + var endMiddle = formatRange(cmd.middle, formatStart, formatEnd, separator); + var endTail = formatEnd(cmd.tail); + if (startHead === endHead && startTail === endTail) { + return startHead + + (startMiddle === endMiddle ? startMiddle : startMiddle + separator + endMiddle) + + startTail; + } + } + return formatStart(cmd.whole) + separator + formatEnd(cmd.whole); + } + + exports.default = main; + exports.toDateTime = toDateTime; + exports.toDuration = toDuration; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/library/fullcalendar/packages/luxon/main.min.js b/library/fullcalendar/packages/luxon/main.min.js new file mode 100644 index 000000000..6267f8bbe --- /dev/null +++ b/library/fullcalendar/packages/luxon/main.min.js @@ -0,0 +1,20 @@ +/*! +FullCalendar Luxon Plugin v4.0.2 +Docs & License: https://fullcalendar.io/ +(c) 2019 Adam Shaw +*/ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("luxon"),require("@fullcalendar/core")):"function"==typeof define&&define.amd?define(["exports","luxon","@fullcalendar/core"],t):(e=e||self,t(e.FullCalendarLuxon={},e.luxon,e.FullCalendar))}(this,function(e,t,n){"use strict";function o(e,t){function n(){this.constructor=e}f(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}function r(e,o){if(!(o instanceof n.Calendar))throw new Error("must supply a Calendar instance");return t.DateTime.fromJSDate(e,{zone:o.dateEnv.timeZone,locale:o.dateEnv.locale.codes[0]})}function a(e,o){if(!(o instanceof n.Calendar))throw new Error("must supply a Calendar instance");return t.Duration.fromObject(m({},e,{locale:o.dateEnv.locale.codes[0]}))}function i(e,t){var n=c(e);if(t.end){var o=u(t.start.array,t.timeZone,t.localeCodes[0]),r=u(t.end.array,t.timeZone,t.localeCodes[0]);return d(n,o.toFormat.bind(o),r.toFormat.bind(r),t.separator)}return u(t.date.array,t.timeZone,t.localeCodes[0]).toFormat(n.whole)}function l(e){return[e.year,e.month-1,e.day,e.hour,e.minute,e.second,e.millisecond]}function u(e,n,o){return t.DateTime.fromObject({zone:n,locale:o,year:e[0],month:e[1]+1,day:e[2],hour:e[3],minute:e[4],second:e[5],millisecond:e[6]})}function c(e){var t=e.match(/^(.*?)\{(.*)\}(.*)$/);if(t){var n=c(t[2]);return{head:t[1],middle:n,tail:t[3],whole:t[1]+n.whole+t[3]}}return{head:null,middle:null,tail:null,whole:e}}function d(e,t,n,o){if(e.middle){var r=t(e.head),a=d(e.middle,t,n,o),i=t(e.tail),l=n(e.head),u=d(e.middle,t,n,o),c=n(e.tail);if(r===l&&i===c)return r+(a===u?a:a+o+u)+i}return t(e.whole)+o+n(e.whole)}/*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */ +var f=function(e,t){return(f=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},m=function(){return m=Object.assign||function(e){for(var t,n=1,o=arguments.length;n