Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
hillelcoren committed Jan 3, 2024
2 parents 1e29930 + 0104bde commit 8238404
Show file tree
Hide file tree
Showing 16 changed files with 623 additions and 88 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/flatpak.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ jobs:
draft: false
prerelease: false
title: "Latest Release"
automatic_release_tag: "v5.0.146"
automatic_release_tag: "v5.0.147"
files: |
${{ github.workspace }}/artifacts/Invoice-Ninja-Archive
${{ github.workspace }}/artifacts/Invoice-Ninja-Hash
Expand Down
1 change: 1 addition & 0 deletions flatpak/com.invoiceninja.InvoiceNinja.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
</screenshots>
<content_rating type="oars-1.1"/>
<releases>
<release version="5.0.147" date="2024-01-02"/>
<release version="5.0.146" date="2023-12-20"/>
<release version="5.0.145" date="2023-12-03"/>
<release version="5.0.144" date="2023-12-01"/>
Expand Down
2 changes: 1 addition & 1 deletion lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Constants {
}

// TODO remove version once #46609 is fixed
const String kClientVersion = '5.0.146';
const String kClientVersion = '5.0.147';
const String kMinServerVersion = '5.0.4';

const String kAppName = 'Invoice Ninja';
Expand Down
10 changes: 5 additions & 5 deletions lib/data/repositories/project_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ class ProjectRepository {
}

Future<BuiltList<ProjectEntity>> loadList(
Credentials credentials, int createdAt, bool filterDeleted) async {
String url = credentials.url+ '/projects?created_at=$createdAt';
Credentials credentials, bool filterDeleted) async {
String url = credentials.url + '/projects?';

if (filterDeleted) {
url += '&filter_deleted_clients=true';
Expand All @@ -60,7 +60,7 @@ class ProjectRepository {
}

final url =
credentials.url+ '/projects/bulk?per_page=$kMaxEntitiesPerBulkAction';
credentials.url + '/projects/bulk?per_page=$kMaxEntitiesPerBulkAction';
final dynamic response = await webClient.post(url, credentials.token,
data: json.encode({'ids': ids, 'action': action.toApiParam()}));

Expand All @@ -77,10 +77,10 @@ class ProjectRepository {

if (project.isNew) {
response = await webClient.post(
credentials.url+ '/projects', credentials.token,
credentials.url + '/projects', credentials.token,
data: json.encode(data));
} else {
final url = credentials.url+ '/projects/${project.id}';
final url = credentials.url + '/projects/${project.id}';
response =
await webClient.put(url, credentials.token, data: json.encode(data));
}
Expand Down
1 change: 0 additions & 1 deletion lib/redux/project/project_middleware.dart
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@ Middleware<AppState> _loadProjects(ProjectRepository repository) {
repository
.loadList(
state.credentials,
state.createdAtLimit,
state.filterDeletedClients,
)
.then((data) {
Expand Down
77 changes: 38 additions & 39 deletions lib/ui/app/invoice/invoice_email_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -297,24 +297,18 @@ class _InvoiceEmailViewState extends State<InvoiceEmailView>
return Container(
color: Colors.white,
height: double.infinity,
child: Stack(
children: [
if (_isLoading) LinearProgressIndicator(),
if (supportsInlineBrowser())
EmailPreview(
child: (supportsInlineBrowser())
? EmailPreview(
isLoading: _isLoading,
subject: _subjectPreview,
body: _emailPreview,
)
else
IgnorePointer(
: IgnorePointer(
child: ExampleEditor(
value: '### $_subjectPreview\n\n\n' +
html2md.convert(_bodyPreview),
),
)
],
),
),
);
}

Expand Down Expand Up @@ -372,36 +366,41 @@ class _InvoiceEmailViewState extends State<InvoiceEmailView>
),
),
),
if (state.company.markdownEmailEnabled)
Expanded(
child: ColoredBox(
color: Colors.white,
child: IgnorePointer(
ignoring: !enableCustomEmail,
child: ExampleEditor(
value: _rawBodyPreview,
onChanged: (value) {
if (value.trim() != _bodyController.text.trim()) {
_bodyController.text = value;
_onChanged();
}
},
Expanded(
child: Stack(
children: [
if (state.company.markdownEmailEnabled)
ColoredBox(
color: Colors.white,
child: IgnorePointer(
ignoring: !enableCustomEmail,
child: ExampleEditor(
value: _rawBodyPreview,
onChanged: (value) {
if (value.trim() != _bodyController.text.trim()) {
_bodyController.text = value;
_onChanged();
}
},
),
),
)
else
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: DecoratedFormField(
controller: _bodyController,
label: localization.body,
maxLines: enableCustomEmail ? 6 : 2,
keyboardType: TextInputType.multiline,
onChanged: (_) => _onChanged(),
enabled: enableCustomEmail,
),
),
),
),
)
else
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: DecoratedFormField(
controller: _bodyController,
label: localization.body,
maxLines: enableCustomEmail ? 6 : 2,
keyboardType: TextInputType.multiline,
onChanged: (_) => _onChanged(),
enabled: enableCustomEmail,
),
)
if (_isLoading) LinearProgressIndicator(),
],
),
),
],
);
}
Expand Down
13 changes: 9 additions & 4 deletions lib/ui/app/menu_drawer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class _MenuDrawerState extends State<MenuDrawer> {
// Fix for CORS error using 'object' subdomain
return CachedImage(
width: MenuDrawer.LOGO_WIDTH,
url: state.credentials.url+ '/companies/' + company.id + '/logo',
url: state.credentials.url + '/companies/' + company.id + '/logo',
apiToken: state.userCompanyStates
.firstWhere((userCompanyState) =>
userCompanyState.company.id == company.id)
Expand Down Expand Up @@ -967,9 +967,14 @@ class _DrawerTileState extends State<DrawerTile> {
? iconWidget
: isLoading
? Padding(
padding: const EdgeInsets.only(left: 10),
padding: const EdgeInsets.only(
left: 10,
right: 8,
),
child: SizedBox(
child: CircularProgressIndicator(),
child: CircularProgressIndicator(
color: state.accentColor,
),
width: 22,
height: 22,
),
Expand Down Expand Up @@ -1680,7 +1685,7 @@ class _ContactUsDialogState extends State<ContactUsDialog> {

setState(() => _isSaving = true);
WebClient()
.post(state.credentials.url+ '/support/messages/send',
.post(state.credentials.url + '/support/messages/send',
state.credentials.token,
data: json.encode({
'message': _message,
Expand Down
5 changes: 5 additions & 0 deletions lib/ui/invoice/view/invoice_view_overview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@ class InvoiceOverview extends StatelessWidget {
widgets.add(EntityListTile(entity: project, isFilter: isFilter));
}

if ((invoice.invoiceId ?? '').isNotEmpty) {
final linkedInvoice = state.invoiceState.get(invoice.invoiceId!);
widgets.add(EntityListTile(entity: linkedInvoice, isFilter: isFilter));
}

if (invoice.expenseId.isNotEmpty) {
final expense = state.vendorState.get(invoice.expenseId);
widgets.add(EntityListTile(entity: expense, isFilter: isFilter));
Expand Down
69 changes: 69 additions & 0 deletions lib/utils/markdown.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,75 @@ MutableDocument deserializeMarkdownToDocument(String markdown) {
return MutableDocument(nodes: nodeVisitor.content);
}

String serializeDocumentToMarkdown(Document doc) {
final StringBuffer buffer = StringBuffer();

bool isFirstLine = true;
for (int i = 0; i < doc.nodes.length; ++i) {
final node = doc.nodes[i];

if (!isFirstLine) {
// Create a new line to encode the given node.
buffer.writeln('');
} else {
isFirstLine = false;
}

if (node is ImageNode) {
buffer.write('![${node.altText}](${node.imageUrl})');
} else if (node is HorizontalRuleNode) {
buffer.write('---');
} else if (node is ListItemNode) {
final indent = List.generate(node.indent + 1, (index) => ' ').join('');
final symbol = node.type == ListItemType.unordered ? '*' : '1.';

buffer.write('$indent$symbol ${node.text.toMarkdown()}');

final nodeBelow = i < doc.nodes.length - 1 ? doc.nodes[i + 1] : null;
if (nodeBelow != null && (nodeBelow is! ListItemNode)) {
// This list item is the last item in the list. Add an extra
// blank line after it.
buffer.writeln('');
}
} else if (node is ParagraphNode) {
final Attribution blockType = node.getMetadataValue('blockType');

if (blockType == header1Attribution) {
buffer.write('# ${node.text.toMarkdown()}');
} else if (blockType == header2Attribution) {
buffer.write('## ${node.text.toMarkdown()}');
} else if (blockType == header3Attribution) {
buffer.write('### ${node.text.toMarkdown()}');
} else if (blockType == header4Attribution) {
buffer.write('#### ${node.text.toMarkdown()}');
} else if (blockType == header5Attribution) {
buffer.write('##### ${node.text.toMarkdown()}');
} else if (blockType == header6Attribution) {
buffer.write('###### ${node.text.toMarkdown()}');
} else if (blockType == blockquoteAttribution) {
// TODO: handle multiline
buffer.write('> ${node.text.toMarkdown()}');
} else if (blockType == codeAttribution) {
buffer //
..writeln('```') //
..writeln(node.text.toMarkdown()) //
..write('```');
} else {
buffer.write(node.text.toMarkdown());
}

// Separates paragraphs with blank lines.
// If we are at the last node we don't add a trailing
// blank line.
if (i != doc.nodes.length - 1) {
buffer.writeln();
}
}
}

return buffer.toString();
}

/// Converts structured markdown to a list of [DocumentNode]s.
///
/// To use [_MarkdownToDocument], obtain a series of markdown
Expand Down
34 changes: 33 additions & 1 deletion lib/utils/super_editor/super_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class _ExampleEditorState extends State<ExampleEditor> {
@override
void initState() {
super.initState();

// Fix for <p> tags cutting off text
var markdown = widget.value;
markdown = markdown.replaceAll('<p/>', '\n');
Expand All @@ -71,7 +72,6 @@ class _ExampleEditorState extends State<ExampleEditor> {
markdown = markdown.replaceAll('</p>', '');
markdown = markdown.replaceAll('</div>', '');

// _doc = createInitialDocument()..addListener(_onDocumentChange);
_doc = deserializeMarkdownToDocument(markdown)
..addListener(_onDocumentChange);
_composer = MutableDocumentComposer();
Expand All @@ -91,8 +91,35 @@ class _ExampleEditorState extends State<ExampleEditor> {
_iosControlsController = SuperEditorIosControlsController();
}

@override
void didUpdateWidget(ExampleEditor oldWidget) {
super.didUpdateWidget(oldWidget);

if (widget.value != oldWidget.value) {
_setValue(widget.value);
}
}

void _setValue(String value) {
// Fix for <p> tags cutting off text
var markdown = widget.value;
markdown = markdown.replaceAll('<p/>', '\n');
markdown = markdown.replaceAll('<p>', '\n');
markdown = markdown.replaceAll('<div>', '\n');
markdown = markdown.replaceAll('</p>', '');
markdown = markdown.replaceAll('</div>', '');

_doc.removeListener(_onDocumentChange);
_doc = deserializeMarkdownToDocument(markdown)
..addListener(_onDocumentChange);

_docEditor =
createDefaultDocumentEditor(document: _doc, composer: _composer);
}

@override
void dispose() {
_doc.removeListener(_onDocumentChange);
_iosControlsController.dispose();
_scrollController.dispose();
_editorFocusNode.dispose();
Expand All @@ -103,6 +130,11 @@ class _ExampleEditorState extends State<ExampleEditor> {
void _onDocumentChange(_) {
_hideOrShowToolbar();
_docChangeSignal.notifyListeners();

if (widget.onChanged != null) {
final value = serializeDocumentToMarkdown(_doc);
widget.onChanged!(value);
}
}

void _hideOrShowToolbar() {
Expand Down
Loading

0 comments on commit 8238404

Please sign in to comment.