Skip to content

Commit a6da15f

Browse files
committed
Experimental: expose license detection edit operations.
1 parent 67c0600 commit a6da15f

File tree

4 files changed

+122
-4
lines changed

4 files changed

+122
-4
lines changed

app/lib/frontend/handlers/experimental.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const _publicFlags = <PublicFlag>{
1414

1515
final _allFlags = <String>{
1616
'dark-as-default',
17+
'license',
1718
..._publicFlags.map((x) => x.name),
1819
};
1920

@@ -88,6 +89,8 @@ class ExperimentalFlags {
8889

8990
bool get isDarkModeDefault => isEnabled('dark-as-default');
9091

92+
late final isLicenseEnabled = isEnabled('license');
93+
9194
String encodedAsCookie() => _enabled.join(':');
9295

9396
@override

app/lib/frontend/templates/package.dart

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import 'package:_pub_shared/data/page_data.dart';
66
import 'package:_pub_shared/search/tags.dart';
77
import 'package:collection/collection.dart' show IterableExtension;
8+
import 'package:pana/pana.dart';
9+
import 'package:pub_dev/frontend/request_context.dart';
810
import 'package:pub_dev/frontend/templates/views/pkg/liked_package_list.dart';
911

1012
import '../../package/models.dart';
@@ -378,13 +380,91 @@ Tab _installTab(PackagePageData data) {
378380
}
379381

380382
Tab _licenseTab(PackagePageData data) {
381-
final license = data.hasLicense
382-
? renderFile(data.asset!, urlResolverFn: data.urlResolverFn)
383-
: d.text('No license file found.');
383+
final licenses = data.scoreCard.panaReport?.licenses;
384+
final hasEditOpData =
385+
licenses != null &&
386+
licenses.isNotEmpty &&
387+
licenses.any((l) => l.operations?.isNotEmpty ?? false);
388+
late d.Node content;
389+
if (!data.hasLicense) {
390+
content = d.text('No license file found.');
391+
} else if (hasEditOpData &&
392+
requestContext.experimentalFlags.isLicenseEnabled) {
393+
final text = data.asset!.textContent!;
394+
final opAndLicensePairs =
395+
licenses
396+
.expand((l) => (l.operations ?? []).map((op) => (op, l)))
397+
.toList()
398+
..sort((a, b) => a.$1.start.compareTo(b.$1.start));
399+
final nodes = <d.Node>[];
400+
var offset = 0;
401+
for (final (op, _) in opAndLicensePairs) {
402+
if (offset < op.start) {
403+
nodes.add(
404+
d.span(
405+
classes: ['license-op-insert'],
406+
text: text.substring(offset, op.start),
407+
),
408+
);
409+
offset = op.start;
410+
}
411+
switch (op.type) {
412+
case TextOpType.delete:
413+
nodes.add(
414+
d.span(
415+
classes: ['license-op-delete', 'license-op-delete-hidden'],
416+
children: [
417+
d.span(
418+
classes: ['license-op-delete-icon'],
419+
text: '✄',
420+
attributes: {'tabindex': '-1'},
421+
),
422+
d.span(
423+
classes: ['license-op-delete-content'],
424+
text: op.content,
425+
),
426+
],
427+
),
428+
);
429+
break;
430+
case TextOpType.insert:
431+
final end = op.start + op.length;
432+
nodes.add(
433+
d.span(
434+
classes: ['license-op-insert'],
435+
text: text.substring(op.start, end),
436+
),
437+
);
438+
offset = end;
439+
break;
440+
case TextOpType.match:
441+
final end = op.start + op.length;
442+
nodes.add(
443+
d.span(
444+
classes: ['license-op-match'],
445+
text: text.substring(op.start, end),
446+
),
447+
);
448+
offset = end;
449+
break;
450+
}
451+
}
452+
if (offset < text.length) {
453+
nodes.add(
454+
d.span(classes: ['license-op-insert'], text: text.substring(offset)),
455+
);
456+
}
457+
content = d.div(
458+
classes: ['highlight'],
459+
child: d.pre(children: nodes),
460+
);
461+
} else {
462+
content = renderFile(data.asset!, urlResolverFn: data.urlResolverFn);
463+
}
384464
return Tab.withContent(
385465
id: 'license',
386466
title: 'License',
387-
contentNode: d.fragment([d.h2(text: 'License'), license]),
467+
contentNode: d.fragment([d.h2(text: 'License'), content]),
388468
isMarkdown: true,
389469
);
390470
}

pkg/web_app/lib/src/foldable.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'web_util.dart';
1212
void setupFoldable() {
1313
_setEventForFoldable();
1414
_setEventForCheckboxToggle();
15+
_setEventForLicenseDeleteIcons();
1516
}
1617

1718
/// Elements with the `foldable` class provide a folding content:
@@ -106,3 +107,15 @@ void _setEventForCheckboxToggle() {
106107
});
107108
}
108109
}
110+
111+
/// Setup a toggle event for the delete operation icons in licenses.
112+
void _setEventForLicenseDeleteIcons() {
113+
final icons = document.body!
114+
.querySelectorAll('.license-op-delete-icon')
115+
.toElementList();
116+
for (final icon in icons) {
117+
icon.onClick.listen((event) {
118+
icon.parentElement!.classList.toggle('license-op-delete-hidden');
119+
});
120+
}
121+
}

pkg/web_css/lib/src/_pkg.scss

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,3 +582,25 @@
582582
}
583583
}
584584
}
585+
586+
.license-op-delete {
587+
background: rgba(255, 0, 0, 0.2);
588+
589+
&.license-op-delete-hidden {
590+
.license-op-delete-content {
591+
display: none;
592+
}
593+
}
594+
595+
.license-op-delete-icon {
596+
cursor: pointer;
597+
}
598+
}
599+
600+
.license-op-insert {
601+
background: rgba(255, 255, 0, 0.2);
602+
}
603+
604+
.license-op-match {
605+
background: rgba(0, 255, 0, 0.2);
606+
}

0 commit comments

Comments
 (0)