1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-28 00:10:57 +01:00

Merge branch 'master' into redesign-2015

This commit is contained in:
epriestley 2015-06-28 07:41:30 -07:00
commit 1e3c49086e
26 changed files with 185 additions and 214 deletions

View file

@ -341,7 +341,7 @@ return array(
'rsrc/js/application/conpherence/behavior-menu.js' => 'd3782c93',
'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861',
'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3',
'rsrc/js/application/conpherence/behavior-widget-pane.js' => 'cafc59ab',
'rsrc/js/application/conpherence/behavior-widget-pane.js' => 'a8458711',
'rsrc/js/application/countdown/timer.js' => 'e4cc26b3',
'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => 'edf8a145',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e',
@ -444,7 +444,7 @@ return array(
'rsrc/js/core/behavior-device.js' => 'a205cf28',
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e',
'rsrc/js/core/behavior-error-log.js' => '6882e80a',
'rsrc/js/core/behavior-fancy-datepicker.js' => 'ea5cec5d',
'rsrc/js/core/behavior-fancy-datepicker.js' => '665cf6ac',
'rsrc/js/core/behavior-file-tree.js' => '88236f00',
'rsrc/js/core/behavior-form.js' => '5c54cbf3',
'rsrc/js/core/behavior-gesture.js' => '3ab51e2c',
@ -548,7 +548,7 @@ return array(
'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a',
'javelin-behavior-conpherence-menu' => 'd3782c93',
'javelin-behavior-conpherence-pontificate' => '21ba5861',
'javelin-behavior-conpherence-widget-pane' => 'cafc59ab',
'javelin-behavior-conpherence-widget-pane' => 'a8458711',
'javelin-behavior-countdown-timer' => 'e4cc26b3',
'javelin-behavior-dark-console' => 'f411b6ae',
'javelin-behavior-dashboard-async-panel' => '469c0d9e',
@ -577,7 +577,7 @@ return array(
'javelin-behavior-durable-column' => 'c72aa091',
'javelin-behavior-error-log' => '6882e80a',
'javelin-behavior-event-all-day' => '38dcf3c8',
'javelin-behavior-fancy-datepicker' => 'ea5cec5d',
'javelin-behavior-fancy-datepicker' => '665cf6ac',
'javelin-behavior-global-drag-and-drop' => 'c8e57404',
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
'javelin-behavior-high-security-warning' => 'a464fe03',
@ -1296,6 +1296,13 @@ return array(
'javelin-workflow',
'javelin-dom',
),
'665cf6ac' => array(
'javelin-behavior',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
'6882e80a' => array(
'javelin-dom',
),
@ -1586,6 +1593,19 @@ return array(
'javelin-stratcom',
'javelin-dom',
),
'a8458711' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'phabricator-notification',
'javelin-behavior-device',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'conpherence-thread-manager',
),
'a8d8459d' => array(
'javelin-behavior',
'javelin-dom',
@ -1758,19 +1778,6 @@ return array(
'javelin-stratcom',
'phabricator-phtize',
),
'cafc59ab' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'phabricator-notification',
'javelin-behavior-device',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'conpherence-thread-manager',
),
'ccf1cbf8' => array(
'javelin-install',
'javelin-dom',
@ -1912,13 +1919,6 @@ return array(
'javelin-dom',
'phabricator-draggable-list',
),
'ea5cec5d' => array(
'javelin-behavior',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
'ea681761' => array(
'javelin-behavior',
'javelin-aphlict',

View file

@ -1,29 +1,6 @@
<?php
$table = new PhabricatorRepositorySymbol();
$conn_w = $table->establishConnection('w');
$projects = queryfx_all(
$conn_w,
'SELECT * FROM %T',
'repository_arcanistproject');
foreach ($projects as $project) {
$repo = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs(array($project['repositoryID']))
->executeOne();
if (!$repo) {
continue;
}
echo pht("Migrating symbols for '%s' project...\n", $project['name']);
queryfx(
$conn_w,
'UPDATE %T SET repositoryPHID = %s WHERE arcanistProjectID = %d',
$table->getTableName(),
$repo->getPHID(),
$project['id']);
}
// NOTE: This migration moved existing symbols from Arcanist Projects to
// Repositories. It stopped running cleanly about two months later, after
// Spaces were introduced. Since this data is not important and can be
// trivially regenerated, just stop running the migration. See T8691.

View file

@ -1,82 +1,7 @@
<?php
$table_w = new PhabricatorRepository();
$conn_w = $table_w->establishConnection('w');
// Repository and Project share a database.
$conn_r = $table_w->establishConnection('r');
$projects_table = 'repository_arcanistproject';
$raw_projects_data = queryfx_all($conn_r, 'SELECT * FROM %T', $projects_table);
$raw_projects_data = ipull($raw_projects_data, null, 'id');
$repository_ids = ipull($raw_projects_data, 'repositoryID');
if (!$repository_ids) {
return;
}
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs($repository_ids)
->execute();
$projects_to_repo_ids_map = ipull($raw_projects_data, 'repositoryID', 'phid');
$projects_to_repos_map = array();
foreach ($projects_to_repo_ids_map as $projectPHID => $repositoryID) {
$repo = idx($repositories, $repositoryID);
if ($repo) {
$projects_to_repos_map[$projectPHID] = $repo->getPHID();
}
}
foreach ($raw_projects_data as $project_row) {
$repositoryID = idx($project_row, 'repositoryID');
$repo = idx($repositories, $repositoryID);
if (!$repo) {
continue;
}
echo pht(
"Migrating symbols configuration for '%s' project...\n",
idx($project_row, 'name', '???'));
$symbol_index_projects = $project_row['symbolIndexProjects'];
$symbol_index_projects = nonempty($symbol_index_projects, '[]');
try {
$symbol_index_projects = phutil_json_decode($symbol_index_projects);
} catch (PhutilJSONParserException $ex) {
continue;
}
$sources = $repo->getDetail('symbol-sources', array());
foreach ($symbol_index_projects as $index_project) {
$sources[] = idx($projects_to_repos_map, $index_project);
}
$sources = array_filter($sources);
$sources = array_unique($sources);
$repo->setDetail('symbol-sources', $sources);
$languages = $project_row['symbolIndexLanguages'];
$languages = nonempty($languages, '[]');
try {
$languages = phutil_json_decode($languages);
} catch (PhutilJSONParserException $ex) {
continue;
}
$languages = array_merge(
$repo->getDetail('symbol-languages', array()),
$languages);
$languages = array_unique($languages);
$repo->setDetail('symbol-languages', $languages);
queryfx(
$conn_w,
'UPDATE %T SET details = %s WHERE id = %d',
$table_w->getTableName(),
json_encode($repo->getDetails()),
$repo->getID());
}
// NOTE: This migration moved existing symbol configuration from Arcanist
// Projects to Repositories. Like "20150503.repositorysymbols.2.php", it stopped
// running cleanly about two months later, after Spaces were introduced. Since
// this data is easy to rebuild, just stop running the migration. See T8691 and
// T8697.

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
ADD spacePHID VARBINARY(64);

View file

@ -5131,6 +5131,7 @@ phutil_register_library_map(array(
'PhabricatorDestructibleInterface',
'PhabricatorMentionableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorSpacesInterface',
),
'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController',

View file

@ -194,11 +194,11 @@ final class AlmanacServiceQuery
);
}
protected function getValueMap($cursor, array $keys) {
protected function getPagingValueMap($cursor, array $keys) {
$service = $this->loadCursorObject($cursor);
return array(
'id' => $service->getID(),
'name' => $service->getServiceName(),
'name' => $service->getName(),
);
}

View file

@ -449,6 +449,10 @@ abstract class PhabricatorApplication
$class,
PhabricatorUser $viewer) {
if ($viewer->isOmnipotent()) {
return true;
}
$cache = PhabricatorCaches::getRequestCache();
$viewer_phid = $viewer->getPHID();
$key = 'app.'.$class.'.installed.'.$viewer_phid;

View file

@ -61,6 +61,8 @@ abstract class PhabricatorCalendarController extends PhabricatorController {
->setID(null)
->setPHID(null)
->removeViewerTimezone($viewer)
->setViewPolicy($event->getViewPolicy())
->setEditPolicy($event->getEditPolicy())
->save();
$ghost_invitees = array();
foreach ($invitees as $invitee) {

View file

@ -156,11 +156,9 @@ final class PhabricatorCalendarEventEditController
$is_parent = $event->getIsRecurrenceParent();
$frequency = idx($event->getRecurrenceFrequency(), 'rule');
$icon = $event->getIcon();
$current_policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($event)
->execute();
$edit_policy = $event->getEditPolicy();
$view_policy = $event->getViewPolicy();
$space = $event->getSpacePHID();
if ($request->isFormPost()) {
$xactions = array();
@ -181,6 +179,7 @@ final class PhabricatorCalendarEventEditController
$subscribers = $request->getArr('subscribers');
$edit_policy = $request->getStr('editPolicy');
$view_policy = $request->getStr('viewPolicy');
$space = $request->getStr('spacePHID');
$is_recurring = $request->getStr('isRecurring') ? 1 : 0;
$frequency = $request->getStr('frequency');
$is_all_day = $request->getStr('isAllDay');
@ -266,6 +265,10 @@ final class PhabricatorCalendarEventEditController
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($request->getStr('editPolicy'));
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_SPACE)
->setNewValue($space);
$editor = id(new PhabricatorCalendarEventEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
@ -306,9 +309,6 @@ final class PhabricatorCalendarEventEditController
PhabricatorCalendarEventTransaction::TYPE_END_DATE);
$error_recurrence_end_date = $ex->getShortMessage(
PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE);
$event->setViewPolicy($view_policy);
$event->setEditPolicy($edit_policy);
}
}
@ -322,6 +322,11 @@ final class PhabricatorCalendarEventEditController
$recurring_date_edit_label = null;
$current_policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($event)
->execute();
$name = id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
@ -468,12 +473,15 @@ final class PhabricatorCalendarEventEditController
$view_policies = id(new AphrontFormPolicyControl())
->setUser($viewer)
->setValue($view_policy)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($event)
->setPolicies($current_policies)
->setSpacePHID($space)
->setName('viewPolicy');
$edit_policies = id(new AphrontFormPolicyControl())
->setUser($viewer)
->setValue($edit_policy)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($event)
->setPolicies($current_policies)

View file

@ -288,6 +288,8 @@ final class PhabricatorCalendarEventSearchEngine
}
$item = id(new PHUIObjectItemView())
->setUser($viewer)
->setObject($event)
->setHeader($viewer->renderHandle($event->getPHID())->render())
->addAttribute($event_date_info)
->addAttribute($attendees)

View file

@ -9,7 +9,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
PhabricatorTokenReceiverInterface,
PhabricatorDestructibleInterface,
PhabricatorMentionableInterface,
PhabricatorFlaggableInterface {
PhabricatorFlaggableInterface,
PhabricatorSpacesInterface {
protected $name;
protected $userPHID;
@ -32,6 +33,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
protected $viewPolicy;
protected $editPolicy;
protected $spacePHID;
const DEFAULT_ICON = 'fa-calendar';
private $parentEvent = self::ATTACHABLE;
@ -71,6 +74,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
->setIcon(self::DEFAULT_ICON)
->setViewPolicy($view_policy)
->setEditPolicy($actor->getPHID())
->setSpacePHID($actor->getDefaultSpacePHID())
->attachInvitees(array())
->applyViewerTimezone($actor);
}
@ -551,4 +555,11 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
$this->delete();
$this->saveTransaction();
}
/* -( PhabricatorSpacesInterface )----------------------------------------- */
public function getSpacePHID() {
return $this->spacePHID;
}
}

View file

@ -15,7 +15,7 @@ final class PhabricatorTimezoneSetupCheck extends PhabricatorSetupCheck {
if (!$ok) {
$message = pht(
'Your PHP configuration configuration selects an invalid timezone. '.
'Your PHP configuration selects an invalid timezone. '.
'Select a valid timezone.');
$this

View file

@ -48,11 +48,17 @@ final class ConpherenceWidgetConfigConstants extends ConpherenceConstants {
),
),
'widgets-settings' => array(
'name' => pht('Settings'),
'name' => pht('Notifications'),
'icon' => 'fa-wrench',
'deviceOnly' => false,
'hasCreate' => false,
),
'widgets-edit' => array(
'name' => pht('Edit Room'),
'icon' => 'fa-pencil',
'deviceOnly' => false,
'hasCreate' => false,
),
);
}

View file

@ -83,8 +83,7 @@ abstract class ConpherenceController extends PhabricatorController {
$crumbs->addCrumb(
id(new PHUICrumbView())
->setName($data['title'])
->setHref($this->getApplicationURI('update/'.$conpherence->getID().'/'))
->setWorkflow(true));
->setHref('/'.$conpherence->getMonogram()));
return hsprintf(
'%s',

View file

@ -119,6 +119,13 @@ final class ConpherenceWidgetController extends ConpherenceController {
'style' => 'display: none',
),
$this->renderSettingsWidgetPaneContent());
$widgets[] = phutil_tag(
'div',
array(
'class' => 'widgets-body',
'id' => 'widgets-edit',
'style' => 'display: none',
));
// without this implosion we get "," between each element in our widgets
// array
@ -149,12 +156,15 @@ final class ConpherenceWidgetController extends ConpherenceController {
$conpherence,
PhabricatorPolicyCapability::CAN_JOIN);
if ($can_join) {
$text = pht('Settings are available after joining the room.');
$text = pht(
'Notification settings are available after joining the room.');
} else if ($viewer->isLoggedIn()) {
$text = pht('Settings not applicable to rooms you can not join.');
$text = pht(
'Notification settings not applicable to rooms you can not join.');
} else {
$text = pht(
'Settings are available after logging in and joining the room.');
'Notification settings are available after logging in and joining '.
'the room.');
}
return phutil_tag(
'div',

View file

@ -11,56 +11,46 @@ final class ConpherenceThreadSearchEngine
return 'PhabricatorConpherenceApplication';
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
$saved->setParameter(
'participantPHIDs',
$this->readUsersFromRequest($request, 'participants'));
$saved->setParameter('fulltext', $request->getStr('fulltext'));
return $saved;
public function newQuery() {
return id(new ConpherenceThreadQuery())
->needParticipantCache(true);
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new ConpherenceThreadQuery())
->needParticipantCache(true);
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchUsersField())
->setLabel(pht('Participants'))
->setKey('participants')
->setAliases(array('participant')),
id(new PhabricatorSearchTextField())
->setLabel(pht('Contains Words'))
->setKey('fulltext'),
);
}
$participant_phids = $saved->getParameter('participantPHIDs', array());
if ($participant_phids && is_array($participant_phids)) {
$query->withParticipantPHIDs($participant_phids);
protected function getDefaultFieldOrder() {
return array(
'participants',
'...',
);
}
protected function shouldShowOrderField() {
return false;
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['participants']) {
$query->withParticipantPHIDs($map['participants']);
}
$fulltext = $saved->getParameter('fulltext');
if (strlen($fulltext)) {
$query->withFulltext($fulltext);
if ($map['fulltext']) {
$query->withFulltext($map['fulltext']);
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
$participant_phids = $saved->getParameter('participantPHIDs', array());
$fulltext = $saved->getParameter('fulltext');
$form
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorPeopleDatasource())
->setName('participants')
->setLabel(pht('Participants'))
->setValue($participant_phids))
->appendControl(
id(new AphrontFormTextControl())
->setName('fulltext')
->setLabel(pht('Contains Words'))
->setValue($fulltext));
}
protected function getURI($path) {
return '/conpherence/search/'.$path;
}
@ -68,14 +58,12 @@ final class ConpherenceThreadSearchEngine
protected function getBuiltinQueryNames() {
$names = array();
$names = array(
'all' => pht('All Rooms'),
);
if ($this->requireViewer()->isLoggedIn()) {
$names['participant'] = pht('Joined Rooms');
}
$names['all'] = pht('All Rooms');
return $names;
}
@ -89,7 +77,7 @@ final class ConpherenceThreadSearchEngine
return $query;
case 'participant':
return $query->setParameter(
'participantPHIDs',
'participants',
array($this->requireViewer()->getPHID()));
}

View file

@ -332,13 +332,13 @@ final class ConpherenceDurableColumnView extends AphrontTagView {
));
$item = id(new PHUIListItemView())
->setName(pht('Settings'))
->setName(pht('Room Actions'))
->setIcon('fa-bars')
->addClass('core-menu-item')
->addSigil('conpherence-settings-menu')
->setID($bubble_id)
->setHref('#')
->setAural(pht('Settings'))
->setAural(pht('Room Actions'))
->setOrder(300);
$settings_button = id(new PHUIListView())
->addMenuItem($item)

View file

@ -150,7 +150,7 @@ final class DivinerLiveSymbol extends DivinerDAO
public function save() {
// NOTE: The identity hash is just a sanity check because the unique tuple
// on this table is way way too long to fit into a normal `UNIQUE KEY`.
// on this table is way too long to fit into a normal `UNIQUE KEY`.
// We don't use it directly, but its existence prevents duplicate records.
if (!$this->identityHash) {

View file

@ -185,7 +185,7 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase {
$second_file = PhabricatorFile::newFromFileData($data, $params);
// Test that the the second file uses the same storage handle as
// Test that the second file uses the same storage handle as
// the first file.
$handle = $first_file->getStorageHandle();
$second_handle = $second_file->getStorageHandle();

View file

@ -52,7 +52,7 @@ final class HarbormasterBuildUnitMessage
}
$coverage = idx($dict, 'coverage');
if (strlen($coverage)) {
if ($coverage) {
$obj->setProperty('coverage', $coverage);
}

View file

@ -8,7 +8,7 @@ final class MetaMTAMailSentGarbageCollector
$mails = id(new PhabricatorMetaMTAMail())->loadAllWhere(
'dateCreated < %d LIMIT 100',
PhabricatorTime::getNow());
PhabricatorTime::getNow() - $ttl);
foreach ($mails as $mail) {
$mail->delete();

View file

@ -313,7 +313,7 @@ final class PhabricatorRepositoryGraphCache extends Phobject {
// Find all the Git and Mercurial commits in the block which have completed
// change import. We can't fill the cache accurately for commits which have
// not completed change import, so just pretend we don't know about them.
// In these cases, we will will ultimately fall back to VCS queries.
// In these cases, we will ultimately fall back to VCS queries.
$commit_rows = queryfx_all(
$conn_r,

View file

@ -960,7 +960,7 @@ abstract class PhabricatorApplicationTransactionEditor
// Maybe this makes more sense to move into the search index itself? For
// now I'm putting it here since I think we might end up with things that
// need it to be up to date once the next page loads, but if we don't go
// there we we could move it into search once search moves to the daemons.
// there we could move it into search once search moves to the daemons.
// It now happens in the search indexer as well, but the search indexer is
// always daemonized, so the logic above still potentially holds. We could

View file

@ -259,8 +259,13 @@ final class AphrontFormDateControl extends AphrontFormControl {
),
$time_sel);
$preferences = $this->user->loadPreferences();
$pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY;
$week_start = $preferences->getPreference($pref_week_start, 0);
Javelin::initBehavior('fancy-datepicker', array(
'format' => $this->getDateFormat(),
'weekStart' => $week_start,
));
$classes = array();

View file

@ -81,16 +81,34 @@ JX.behavior('conpherence-widget-pane', function(config) {
continue;
}
var handler;
var href;
if (widget == 'widgets-edit') {
var threadManager = JX.ConpherenceThreadManager.getInstance();
handler = function(e) {
e.prevent();
menu.close();
threadManager.runUpdateWorkflowFromLink(
e.getTarget(),
{
action : 'metadata',
force_ajax : true,
stage : 'submit'
});
};
href = threadManager._getUpdateURI();
} else {
handler = JX.bind(null, function(widget, e) {
toggleWidget({widget: widget});
e.prevent();
menu.close();
}, widget);
}
var item = new JX.PHUIXActionView()
.setIcon(widget_data.icon || 'none')
.setName(widget_data.name)
.setHandler(
JX.bind(null, function(widget, e) {
toggleWidget({widget: widget});
e.prevent();
menu.close();
}, widget));
.setHref(href)
.setHandler(handler);
map[widget_data.name] = item;
list.addItem(item);
}

View file

@ -46,6 +46,16 @@ JX.behavior('fancy-datepicker', function(config, statics) {
return format;
};
var get_week_start = function() {
var week_start = config.weekStart;
if (week_start === null) {
week_start = 0;
}
return week_start;
};
var onopen = function(e) {
e.kill();
@ -277,9 +287,12 @@ JX.behavior('fancy-datepicker', function(config, statics) {
// First, render the weekday names.
var weekdays = 'SMTWTFS';
var weekday_names = [];
var ii;
for (ii = 0; ii < weekdays.length; ii++) {
weekday_names.push(cell(weekdays.charAt(ii), null, false, 'day-name'));
var week_start = parseInt(get_week_start(), 10);
var week_end = weekdays.length + week_start;
for (var ii = week_start; ii < week_end; ii++) {
var index = ii%7;
weekday_names.push(cell(weekdays.charAt(index), null, false, 'day-name'));
}
weeks.push(JX.$N('tr', {}, weekday_names));
@ -290,7 +303,7 @@ JX.behavior('fancy-datepicker', function(config, statics) {
var start = new Date(
valid_date.getYear() + 1900,
valid_date.getMonth(),
1).getDay();
1).getDay() - week_start;
while (start--) {
days.push(cell('', null, false, 'day-placeholder'));