Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion root/static/scripts/alias/AliasEditForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import isBlank from '../common/utility/isBlank.js';
import DateRangeFieldset, {
type ActionT as DateRangeFieldsetActionT,
runReducer as runDateRangeFieldsetReducer,
setInitialStateOnForm as setInitialDateRangeFieldsetStateOnForm,
} from '../edit/components/DateRangeFieldset.js';
import EnterEdit from '../edit/components/EnterEdit.js';
import EnterEditNote from '../edit/components/EnterEditNote.js';
Expand Down Expand Up @@ -96,7 +97,7 @@ const blankDatePeriod = {

function createInitialState(form: AliasEditFormT, searchHintType: number) {
return {
form,
form: setInitialDateRangeFieldsetStateOnForm(form),
guessCaseOptions: createGuessCaseOptionsState(),
isEnded: form.field.period.field.ended.value,
isGuessCaseOptionsOpen: false,
Expand Down
20 changes: 12 additions & 8 deletions root/static/scripts/common/utility/formatDate.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,34 @@ import ko from 'knockout';

import {fixedWidthInteger} from './strings.js';

function formatDate(date: ?PartialDateT): string {
function formatDate(date: ?{
+day?: ?StrOrNum,
+month?: ?StrOrNum,
+year?: ?StrOrNum,
}): string {
if (!date) {
return '';
}

const y: number | null = ko.unwrap(date.year ?? null);
const m: number | null = ko.unwrap(date.month ?? null);
const d: number | null = ko.unwrap(date.day ?? null);
const y: StrOrNum | null = ko.unwrap(date.year ?? null);
const m: StrOrNum | null = ko.unwrap(date.month ?? null);
const d: StrOrNum | null = ko.unwrap(date.day ?? null);

let result = '';

if (nonEmpty(y)) {
result += fixedWidthInteger(y, 4);
} else if (m != null || d != null) {
} else if (nonEmpty(m) || nonEmpty(d)) {
result = '????';
}

if (m != null) {
if (nonEmpty(m)) {
result += '-' + fixedWidthInteger(m, 2);
} else if (d != null) {
} else if (nonEmpty(d)) {
result += '-??';
}

if (d != null) {
if (nonEmpty(d)) {
result += '-' + fixedWidthInteger(d, 2);
}

Expand Down
18 changes: 18 additions & 0 deletions root/static/scripts/common/utility/isDateEmpty.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@
* later version: http://www.gnu.org/licenses/gpl-2.0.txt
*/

import {type Observable as KnockoutObservable} from 'knockout';

declare type PartialDateObservablesT = {
+day: KnockoutObservable<string | null>,
+month: KnockoutObservable<string | null>,
+year: KnockoutObservable<string | null>,
};

export function isDateObservableEmpty(
date: PartialDateObservablesT,
): boolean {
return !(
nonEmpty(date.year()) ||
nonEmpty(date.month()) ||
nonEmpty(date.day())
);
}

export function isDateNonEmpty(
date: ?PartialDateT | ?PartialDateStringsT,
): implies date is PartialDateT | PartialDateStringsT {
Expand Down
4 changes: 2 additions & 2 deletions root/static/scripts/common/utility/parseDate.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ const dateRegex = /^(\d{4}|\?{4}|-)(?:-(\d{2}|\?{2}|-)(?:-(\d{2}|\?{2}|-))?)?$/;

function parseDate(str: string): PartialDateT {
const match = str.match(dateRegex) || [];
/* eslint-disable sort-keys */
return {
/* eslint-disable sort-keys */
year: parseIntegerOrNull(match[1]),
month: parseIntegerOrNull(match[2]),
day: parseIntegerOrNull(match[3]),
/* eslint-enable sort-keys */
};
/* eslint-enable sort-keys */
}

export default parseDate;
82 changes: 82 additions & 0 deletions root/static/scripts/common/utility/parseNaturalDate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* @flow strict
* Copyright (C) 2022 MetaBrainz Foundation
*
* This file is part of MusicBrainz, the open internet music database,
* and is licensed under the GPL version 2, or (at your option) any
* later version: http://www.gnu.org/licenses/gpl-2.0.txt
*/


const ymdRegex = /^[^\w?]*([0-9]{4}|\?{4})(?:[^\w?]+(0?[1-9]|1[0-2]|\?{2})(?:[^\w?]+(0?[1-9]|[12][0-9]|3[01]|\?{2}))?)?[^\w?]*$/;
const cjkRegex = /^[^\w?]*([0-9]{2}|[0-9]{4})(?:(?:\u5E74|\uB144[^\w?]?)(0?[1-9]|1[0-2])(?:(?:\u6708|\uC6D4[^\w?]*?)(0?[1-9]|[12][0-9]|3[01])(?:\u65E5|\uC77C)?)?)?[^\w?]*$/;

function cleanDateString(
str: string,
): string {
let cleanedString = str;

// Clean fullwidth digits to standard digits
cleanedString = cleanedString.replace(
/[0-9-]/g,
function (fullwidthDigit) {
return String.fromCharCode(
fullwidthDigit.charCodeAt(0) -
('0'.charCodeAt(0) - '0'.charCodeAt(0)),
);
},
);

// See https://web.archive.org/web/20220330023649/https://reference.discogs.com/wiki/japanese-release-dates
const japaneseYearCodes = {
/* eslint-disable sort-keys */
N: '1984',
I: '1985',
H: '1986',
O: '1987',
R: '1988',
E: '1989',
C: '1990',
D: '1991',
K: '1992',
L: '1993',
/* eslint-enable sort-keys */
};
cleanedString = cleanedString.replace(
/([NIHORECDKL])-([0-9]{1,2}-[0-9]{1,2})/,
function (match, year, date) {
return japaneseYearCodes[year] + '-' + date;
},
);

// RoC year numbering - http://en.wikipedia.org/wiki/Minguo_calendar
cleanedString = cleanedString.replace(
/民國([0-9]{1,3})/,
function (match, year) {
return String(parseInt(year, 10) + 1911);
},
);

return cleanedString;
}

export default function parseNaturalDate(
str: string,
): PartialDateStringsT {
const cleanedString = cleanDateString(str);

const match = cleanedString.match(cjkRegex) ||
cleanedString.match(ymdRegex) ||
[];

const year = match[1] === '????' ? '' : match[1];
const month = match[2] === '??' ? '' : match[2];
const day = match[3] === '??' ? '' : match[3];
return {
/* eslint-disable sort-keys */
year: year || '',
month: month || '',
day: day || '',
/* eslint-enable sort-keys */
};
}
35 changes: 35 additions & 0 deletions root/static/scripts/edit/components/DateRangeFieldset.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import FormRowPartialDate, {
type ActionT as FormRowPartialDateActionT,
runReducer as runFormRowPartialDateReducer,
} from './FormRowPartialDate.js';
import {
createInitialState as createPartialDateInputState,
} from './PartialDateInput.js';

/* eslint-disable ft-flow/sort-keys */
export type ActionT =
Expand All @@ -33,6 +36,11 @@ export type ActionT =

export type StateT = DatePeriodFieldT;

type FormWithDateRangeT = FormT<{
+period: DatePeriodFieldT,
...
}>;

export function partialDateFromField(
compoundField: PartialDateFieldT,
): PartialDateT {
Expand All @@ -44,6 +52,26 @@ export function partialDateFromField(
};
}

export function setInitialStateOnForm<T: FormWithDateRangeT>(form: T): T {
const formCtx = mutate(form);
// $FlowExpectedError[incompatible-type]
const periodCtx = formCtx.get('field', 'period');
periodCtx.set(createInitialState(periodCtx.read()));
return formCtx.final();
}


export function createInitialState(
field: StateT,
): StateT {
const fieldCtx = mutate(field);
const beginDateField = fieldCtx.get('field', 'begin_date');
beginDateField.set(createPartialDateInputState(beginDateField.read()));
const endDateField = fieldCtx.get('field', 'end_date');
endDateField.set(createPartialDateInputState(endDateField.read()));
return fieldCtx.final();
}

function validateDatePeriod(stateCtx: CowContext<StateT>) {
const state = stateCtx.read();
const beginDateField = state.field.begin_date;
Expand Down Expand Up @@ -77,6 +105,9 @@ function runDateFieldReducer(
{type: 'set-date', ...} => {
validateDatePeriod(state);
}
{type: 'set-parsed-date', ...} => {
// Do nothing
}
{type: 'show-pending-errors'} => {
/*
* Changing the begin date may produce on an error on the end date
Expand Down Expand Up @@ -173,6 +204,10 @@ component _DateRangeFieldset(
Partial dates such as YYYY-MM or just YYYY are OK,
or you can omit the date entirely.`)}
</p>
<p>
{l(`You can also enter a full date string into the parsing field
rather than entering year, month and day separately.`)}
</p>
<FormRowPartialDate
disabled={disabled}
dispatch={hooks.beginDateDispatch}
Expand Down
43 changes: 27 additions & 16 deletions root/static/scripts/edit/components/ExternalLinkAttributeDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import DateRangeFieldset, {
partialDateFromField,
runReducer as runDateRangeFieldsetReducer,
} from './DateRangeFieldset.js';
import {formatParserDate} from './PartialDateInput.js';
import UrlRelationshipCreditFieldset
from './UrlRelationshipCreditFieldset.js';

Expand Down Expand Up @@ -66,25 +67,35 @@ const createInitialState = (
: null,
);

const beginDateField = createCompoundFieldFromObject(
'period.begin_date',
{
day: beginDate?.day ?? null,
month: beginDate?.month ?? null,
year: beginDate?.year ?? null,
},
);

const endDateField = createCompoundFieldFromObject(
'period.end_date',
{
day: endDate?.day ?? null,
month: endDate?.month ?? null,
year: endDate?.year ?? null,
},
);

const datePeriodField = {
errors: [],
field: {
begin_date: createCompoundFieldFromObject(
'period.begin_date',
{
day: beginDate?.day ?? null,
month: beginDate?.month ?? null,
year: beginDate?.year ?? null,
},
),
end_date: createCompoundFieldFromObject(
'period.end_date',
{
day: endDate?.day ?? null,
month: endDate?.month ?? null,
year: endDate?.year ?? null,
},
),
begin_date: {
...beginDateField,
formattedDate: formatParserDate(beginDateField),
},
end_date: {
...endDateField,
formattedDate: formatParserDate(endDateField),
},
ended: createField('period.ended', relationship.ended),
},
has_errors: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

import * as React from 'react';

import DateRangeFieldset, {reducer} from './DateRangeFieldset.js';
import DateRangeFieldset, {createInitialState, reducer}
from './DateRangeFieldset.js';

type PropsT = {
+children?: React.Node,
Expand All @@ -24,7 +25,11 @@ component _HydratedDateRangeFieldset(
endedLabel?: string,
initialField: DatePeriodFieldT,
) {
const [field, dispatch] = React.useReducer(reducer, initialField);
const [field, dispatch] = React.useReducer(
reducer,
initialField,
createInitialState,
);
return (
<DateRangeFieldset
disabled={disabled}
Expand Down
Loading
Loading