Compare commits

...

11 Commits

Author SHA1 Message Date
Ivan
d3e9e0eadd B: Fix react-flow attribution styling
Some checks are pending
Backend CI / build (3.12) (push) Waiting to run
Frontend CI / build (22.x) (push) Waiting to run
2024-09-11 21:25:48 +03:00
Ivan
296e740cc7 M: Improve layout for canvas - remove clipping top 2024-09-11 21:15:28 +03:00
Ivan
5d00a10a58 npm update 2024-09-11 20:08:59 +03:00
Ivan
fe678de8e4 R: Refactor Cst order attribute
0-indexed used for storage only
2024-09-11 20:06:01 +03:00
Ivan
6d84b89b14 Update README.md 2024-09-11 13:49:03 +03:00
Ivan
5ca7f4d057 M: Cleanup todos 2024-09-11 13:24:29 +03:00
Ivan
6e40aa06de M: Optimize dialogs height for small screen notebooks 2024-09-10 17:40:30 +03:00
Ivan
1b662ad39f B: Fix placeholder styling 2024-09-09 20:38:43 +03:00
Ivan
0302a99344 M: Fix text wrap for long filenames 2024-09-07 19:12:41 +03:00
Ivan
1078a271fb M: Small UI fixes 2024-09-07 16:26:16 +03:00
Ivan
7100738706 M: Implement projection swap on ctrl + space 2024-09-06 15:59:09 +03:00
56 changed files with 906 additions and 829 deletions

View File

@ -9,6 +9,7 @@
[![Backend CI](https://github.com/IRBorisov/ConceptPortal/actions/workflows/backend.yml/badge.svg?branch=main)](https://github.com/IRBorisov/ConceptPortal/actions/workflows/backend.yml)
[![Frontend CI](https://github.com/IRBorisov/ConceptPortal/actions/workflows/frontend.yml/badge.svg?branch=main)](https://github.com/IRBorisov/ConceptPortal/actions/workflows/frontend.yml)
[![Uptime Robot status](https://img.shields.io/uptimerobot/status/m797659312-8ab26c72de49d8d92eccc06e?label=Live%20Server)](https://portal.acconcept.ru)
React + Django based web portal for editing RSForm schemas.
This readme file is used mostly to document project dependencies and conventions.

View File

@ -9,7 +9,9 @@ For more specific TODOs see comments in code
- Design first user experience
- Demo sandbox for anonymous users
- User profile: Settings + settings persistency
- User profile: Settings + settings server persistency
- Profile pictures
- Integrate socials and feedback
- Custom LibraryItem lists
- Custom user filters and sharing filters
@ -19,6 +21,8 @@ For more specific TODOs see comments in code
- Focus on codemirror editor when label is clicked (need React 19 ref for clean code solution)
- Draggable rows in constituents table
- M-graph visualization for typification and RSForm in general
- replace reagraph with react-flow in TermGraph and FormulaGraph
- Search functionality for Help Manuals
- Export PDF (Items list, Graph)
@ -31,6 +35,8 @@ For more specific TODOs see comments in code
- Content based search in Library
- Home page (user specific)
- Private projects. Consider cooperative editing
- OSS: synthesis table: auto substitution for diamond synthesis
[Tech]
- duplicate syntax parsing and type info calculations to client. Consider moving backend to Nodejs or embedding c++ lib

View File

@ -84,7 +84,7 @@ class TestVersionViews(EndpointTester):
alias='A1',
cst_type='axiom',
definition_formal='X1=X1',
order=2
order=1
)
version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
a1.definition_formal = 'X1=X2'
@ -163,7 +163,7 @@ class TestVersionViews(EndpointTester):
x1.convention = 'Test2'
x1.term_raw = 'Test'
x1.save()
x3.order = 1
x3.order = 0
x3.save()
self.executeNotFound(version=invalid_id)
@ -172,10 +172,10 @@ class TestVersionViews(EndpointTester):
x1.refresh_from_db()
x2.refresh_from_db()
self.assertEqual(len(response.data['items']), 3)
self.assertEqual(x1.order, 1)
self.assertEqual(x1.order, 0)
self.assertEqual(x1.convention, 'testStart')
self.assertEqual(x1.term_raw, '')
self.assertEqual(x2.order, 2)
self.assertEqual(x2.order, 1)
self.assertEqual(response.data['items'][2]['alias'], 'D1')
self.assertEqual(response.data['items'][2]['term_raw'], 'TestTerm')

View File

@ -28,8 +28,6 @@ from .. import serializers as s
class LibraryViewSet(viewsets.ModelViewSet):
''' Endpoint: Library operations. '''
queryset = m.LibraryItem.objects.all()
# TODO: consider using .only() for performance
ordering = '-time_update'
def get_serializer_class(self):

View File

@ -244,7 +244,6 @@ class OperationSchema:
original = children[sub.original.pk]
replacement = children[sub.substitution.pk]
translated_substitutions.append((original, replacement))
# TODO: add auto substitutes for diamond
receiver.substitute(translated_substitutions)
for cst in receiver.constituents().order_by('order'):
@ -258,6 +257,7 @@ class OperationSchema:
receiver.restore_order()
receiver.reset_aliases()
receiver.resolve_all_text()
if len(self.cache.graph.outputs[operation.pk]) > 0:
self.after_create_cst(receiver, list(receiver.constituents().order_by('order')))
@ -366,12 +366,10 @@ class OperationSchema:
if destination is None:
return
# TODO: update substitutions for diamond synthesis (if needed)
self.cache.ensure_loaded()
new_mapping = self._transform_mapping(mapping, operation, destination)
alias_mapping = OperationSchema._produce_alias_mapping(new_mapping)
insert_where = self._determine_insert_position(items[0], operation, source, destination)
insert_where = self._determine_insert_position(items[0].pk, operation, source, destination)
new_cst_list = destination.insert_copy(items, insert_where, alias_mapping)
for index, cst in enumerate(new_cst_list):
new_inheritance = Inheritance.objects.create(
@ -529,20 +527,23 @@ class OperationSchema:
return result
def _determine_insert_position(
self, prototype: Constituenta,
self, prototype_id: int,
operation: Operation,
source: RSForm,
destination: RSForm
) -> int:
''' Determine insert_after for new constituenta. '''
if prototype.order == 1:
return 1
prev_cst = source.cache.constituents[prototype.order - 2]
prototype = source.cache.by_id[prototype_id]
prototype_index = source.cache.constituents.index(prototype)
if prototype_index == 0:
return 0
prev_cst = source.cache.constituents[prototype_index - 1]
inherited_prev_id = self.cache.get_successor(prev_cst.pk, operation.pk)
if inherited_prev_id is None:
return INSERT_LAST
prev_cst = destination.cache.by_id[inherited_prev_id]
return cast(int, prev_cst.order) + 1
prev_index = destination.cache.constituents.index(prev_cst)
return prev_index + 1
def _extract_data_references(self, data: dict, old_data: dict) -> set[str]:
result: set[str] = set()

View File

@ -70,7 +70,7 @@ class TestChangeConstituents(EndpointTester):
self.assertEqual(self.ks1.constituents().count(), 3)
self.assertEqual(self.ks3.constituents().count(), 5)
self.assertEqual(inherited_cst.alias, 'X4')
self.assertEqual(inherited_cst.order, 3)
self.assertEqual(inherited_cst.order, 2)
self.assertEqual(inherited_cst.definition_formal, 'X1 = X2')
@decl_endpoint('/api/rsforms/{schema}/rename-cst', method='patch')
@ -133,5 +133,5 @@ class TestChangeConstituents(EndpointTester):
d2.refresh_from_db()
self.assertEqual(self.ks1.constituents().count(), 1)
self.assertEqual(self.ks3.constituents().count(), 4)
self.assertEqual(self.ks1X2.order, 1)
self.assertEqual(self.ks1X2.order, 0)
self.assertEqual(d2.definition_formal, r'X2\X2\X3')

View File

@ -367,7 +367,6 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
@action(detail=False, methods=['post'], url_path='get-predecessor')
def get_predecessor(self, request: Request) -> HttpResponse:
''' Get predecessor. '''
# TODO: add tests for this method
serializer = CstTargetSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
cst = cast(Constituenta, serializer.validated_data['target'])

View File

@ -7,7 +7,8 @@ from . import models
class ConstituentaAdmin(admin.ModelAdmin):
''' Admin model: Constituenta. '''
ordering = ['schema', 'order']
list_display = ['schema', 'alias', 'term_resolved', 'definition_resolved']
list_display = ['schema', 'order', 'alias', 'term_resolved', 'definition_resolved']
search_fields = ['term_resolved', 'definition_resolved']
admin.site.register(models.Constituenta, ConstituentaAdmin)

View File

@ -0,0 +1,26 @@
# Generated by Django 5.1 on 2024-09-11 16:37
from django.db import migrations, models
def migrate_order_from_1_to_0(apps, schema_editor):
Constituenta = apps.get_model('rsform', 'Constituenta')
for cst in Constituenta.objects.all():
cst.order = max(0, cst.order - 1)
cst.save()
class Migration(migrations.Migration):
dependencies = [
('rsform', '0002_alter_constituenta_order'),
]
operations = [
migrations.AlterField(
model_name='constituenta',
name='order',
field=models.PositiveIntegerField(default=0, verbose_name='Позиция'),
),
migrations.RunPython(migrate_order_from_1_to_0),
]

View File

@ -2,7 +2,6 @@
import re
from cctext import extract_entities
from django.core.validators import MinValueValidator
from django.db.models import (
CASCADE,
CharField,
@ -57,8 +56,7 @@ class Constituenta(Model):
)
order: PositiveIntegerField = PositiveIntegerField(
verbose_name='Позиция',
validators=[MinValueValidator(1)],
default=1,
default=0,
)
alias: CharField = CharField(
verbose_name='Имя',

View File

@ -140,7 +140,8 @@ class RSForm:
if insert_after is None:
position = INSERT_LAST
else:
position = insert_after.order + 1
self.cache.ensure_loaded()
position = self.cache.constituents.index(self.cache.by_id[insert_after.pk]) + 1
result = self.insert_new(data['alias'], data['cst_type'], position)
result.convention = data.get('convention', '')
result.definition_formal = data.get('definition_formal', '')
@ -170,8 +171,7 @@ class RSForm:
position: int = INSERT_LAST,
**kwargs
) -> Constituenta:
''' Insert new constituenta at given position.
All following constituents order is shifted by 1 position. '''
''' Insert new constituenta at given position. '''
if self.constituents().filter(alias=alias).exists():
raise ValidationError(msg.aliasTaken(alias))
position = self._get_insert_position(position)
@ -298,8 +298,8 @@ class RSForm:
if cst in target:
cst.order = destination + count_moved
count_moved += 1
elif count_top + 1 < destination:
cst.order = count_top + 1
elif count_top < destination:
cst.order = count_top
count_top += 1
else:
cst.order = destination + size + count_bot
@ -417,8 +417,8 @@ class RSForm:
if count_new == 0:
return []
position = target.order + 1
self.cache.ensure_loaded()
position = self.cache.constituents.index(self.cache.by_id[target.id]) + 1
self._shift_positions(position, count_new)
result = []
cst_type = CstType.TERM if len(parse['args']) == 0 else CstType.FUNCTION
@ -456,29 +456,23 @@ class RSForm:
def _shift_positions(self, start: int, shift: int) -> None:
if shift == 0:
return
update_list: Iterable[Constituenta] = []
if not self.cache.is_loaded:
update_list = Constituenta.objects \
.only('order') \
.filter(schema=self.model, order__gte=start)
else:
update_list = [cst for cst in self.cache.constituents if cst.order >= start]
self.cache.ensure_loaded()
update_list = self.cache.constituents[start:]
for cst in update_list:
cst.order += shift
Constituenta.objects.bulk_update(update_list, ['order'])
def _get_insert_position(self, position: int) -> int:
if position <= 0 and position != INSERT_LAST:
if position < 0 and position != INSERT_LAST:
raise ValidationError(msg.invalidPosition())
lastPosition = self.constituents().count()
if position == INSERT_LAST:
position = lastPosition + 1
return lastPosition
else:
position = max(1, min(position, lastPosition + 1))
return position
return max(0, min(position, lastPosition))
def _reset_order(self) -> None:
order = 1
order = 0
changed: list[Constituenta] = []
cst_list: Iterable[Constituenta] = []
if not self.cache.is_loaded:
@ -569,14 +563,14 @@ class RSFormCache:
def insert(self, cst: Constituenta) -> None:
if self.is_loaded:
self.constituents.insert(cst.order - 1, cst)
self.constituents.insert(cst.order, cst)
self.by_id[cst.pk] = cst
self.by_alias[cst.alias] = cst
def insert_multi(self, items: Iterable[Constituenta]) -> None:
if self.is_loaded:
for cst in items:
self.constituents.insert(cst.order - 1, cst)
self.constituents.insert(cst.order, cst)
self.by_id[cst.pk] = cst
self.by_alias[cst.alias] = cst
@ -770,7 +764,7 @@ class _OrderManager:
self._items = result
def _save_order(self) -> None:
order = 1
order = 0
for cst in self._items:
cst.order = order
order += 1

View File

@ -26,7 +26,7 @@ class CstBaseSerializer(serializers.ModelSerializer):
class Meta:
''' serializer metadata. '''
model = Constituenta
fields = '__all__'
exclude = ('order',)
read_only_fields = ('id',)
@ -35,8 +35,8 @@ class CstSerializer(serializers.ModelSerializer):
class Meta:
''' serializer metadata. '''
model = Constituenta
fields = '__all__'
read_only_fields = ('id', 'schema', 'order', 'alias', 'cst_type', 'definition_resolved', 'term_resolved')
exclude = ('order',)
read_only_fields = ('id', 'schema', 'alias', 'cst_type', 'definition_resolved', 'term_resolved')
class CstUpdateSerializer(serializers.Serializer):
@ -71,7 +71,7 @@ class CstDetailsSerializer(serializers.ModelSerializer):
class Meta:
''' serializer metadata. '''
model = Constituenta
fields = '__all__'
exclude = ('order',)
class CstCreateSerializer(serializers.ModelSerializer):
@ -126,7 +126,7 @@ class RSFormSerializer(serializers.ModelSerializer):
result['items'] = []
result['oss'] = []
result['inheritance'] = []
for cst in RSForm(instance).constituents().order_by('order'):
for cst in RSForm(instance).constituents().defer('order').order_by('order'):
result['items'].append(CstSerializer(cst).data)
for oss in LibraryItem.objects.filter(operations__result=instance).only('alias'):
result['oss'].append({
@ -171,6 +171,7 @@ class RSFormSerializer(serializers.ModelSerializer):
cst_data = next(x for x in items if x['id'] == cst.pk)
new_cst = CstBaseSerializer(data=cst_data)
new_cst.is_valid(raise_exception=True)
new_cst.validated_data['order'] = ids.index(cst.pk)
new_cst.update(
instance=cst,
validated_data=new_cst.validated_data
@ -180,9 +181,11 @@ class RSFormSerializer(serializers.ModelSerializer):
for cst_data in items:
if cst_data['id'] not in processed:
cst = schema.insert_new(cst_data['alias'])
old_id = cst_data['id']
cst_data['id'] = cst.pk
new_cst = CstBaseSerializer(data=cst_data)
new_cst.is_valid(raise_exception=True)
new_cst.validated_data['order'] = ids.index(old_id)
new_cst.update(
instance=cst,
validated_data=new_cst.validated_data

View File

@ -151,7 +151,7 @@ class RSFormTRSSerializer(serializers.Serializer):
location=validated_data['location']
)
self.instance.save()
order = 1
order = 0
for cst_data in validated_data['items']:
cst = Constituenta(
alias=cst_data['alias'],
@ -174,7 +174,7 @@ class RSFormTRSSerializer(serializers.Serializer):
if 'comment' in validated_data:
instance.model.comment = validated_data['comment']
order = 1
order = 0
prev_constituents = instance.constituents()
loaded_ids = set()
for cst_data in validated_data['items']:

View File

@ -16,7 +16,7 @@ class TestConstituenta(TestCase):
def test_str(self):
testStr = 'X1'
cst = Constituenta.objects.create(alias=testStr, schema=self.schema1.model, order=1, convention='Test')
cst = Constituenta.objects.create(alias=testStr, schema=self.schema1.model, order=0, convention='Test')
self.assertEqual(str(cst), testStr)
@ -25,25 +25,18 @@ class TestConstituenta(TestCase):
Constituenta.objects.create(alias='X1', schema=self.schema1.model, order=-1)
def test_order_min_value(self):
with self.assertRaises(ValidationError):
cst = Constituenta.objects.create(alias='X1', schema=self.schema1.model, order=0)
cst.full_clean()
def test_schema_not_null(self):
with self.assertRaises(IntegrityError):
Constituenta.objects.create(alias='X1', order=1)
Constituenta.objects.create(alias='X1', order=0)
def test_create_default(self):
cst = Constituenta.objects.create(
alias='X1',
schema=self.schema1.model,
order=1
schema=self.schema1.model
)
self.assertEqual(cst.schema, self.schema1.model)
self.assertEqual(cst.order, 1)
self.assertEqual(cst.order, 0)
self.assertEqual(cst.alias, 'X1')
self.assertEqual(cst.cst_type, CstType.BASE)
self.assertEqual(cst.convention, '')
@ -57,7 +50,6 @@ class TestConstituenta(TestCase):
def test_extract_references(self):
cst = Constituenta.objects.create(
alias='X1',
order=1,
schema=self.schema1.model,
definition_formal='X1 X2',
term_raw='@{X3|sing} is a @{X4|sing}',
@ -68,7 +60,6 @@ class TestConstituenta(TestCase):
def text_apply_mapping(self):
cst = Constituenta.objects.create(
alias='X1',
order=1,
schema=self.schema1.model,
definition_formal='X1 = X2',
term_raw='@{X1|sing}',

View File

@ -23,8 +23,8 @@ class TestRSForm(DBTester):
self.assertFalse(schema1.constituents().exists())
self.assertFalse(schema2.constituents().exists())
Constituenta.objects.create(alias='X1', schema=schema1.model, order=1)
Constituenta.objects.create(alias='X2', schema=schema1.model, order=2)
Constituenta.objects.create(alias='X1', schema=schema1.model, order=0)
Constituenta.objects.create(alias='X2', schema=schema1.model, order=1)
self.assertTrue(schema1.constituents().exists())
self.assertFalse(schema2.constituents().exists())
self.assertEqual(schema1.constituents().count(), 2)
@ -32,8 +32,8 @@ class TestRSForm(DBTester):
def test_get_max_index(self):
schema1 = RSForm.create(title='Test1')
Constituenta.objects.create(alias='X1', schema=schema1.model, order=1)
Constituenta.objects.create(alias='D2', cst_type=CstType.TERM, schema=schema1.model, order=2)
Constituenta.objects.create(alias='X1', schema=schema1.model, order=0)
Constituenta.objects.create(alias='D2', cst_type=CstType.TERM, schema=schema1.model, order=1)
self.assertEqual(schema1.get_max_index(CstType.BASE), 1)
self.assertEqual(schema1.get_max_index(CstType.TERM), 2)
self.assertEqual(schema1.get_max_index(CstType.AXIOM), 0)
@ -42,37 +42,37 @@ class TestRSForm(DBTester):
def test_insert_at(self):
schema = RSForm.create(title='Test')
x1 = schema.insert_new('X1')
self.assertEqual(x1.order, 1)
self.assertEqual(x1.order, 0)
self.assertEqual(x1.schema, schema.model)
x2 = schema.insert_new('X2', position=1)
x2 = schema.insert_new('X2', position=0)
x1.refresh_from_db()
self.assertEqual(x2.order, 1)
self.assertEqual(x2.order, 0)
self.assertEqual(x2.schema, schema.model)
self.assertEqual(x1.order, 2)
self.assertEqual(x1.order, 1)
x3 = schema.insert_new('X3', position=4)
x3 = schema.insert_new('X3', position=3)
x2.refresh_from_db()
x1.refresh_from_db()
self.assertEqual(x3.order, 3)
self.assertEqual(x3.order, 2)
self.assertEqual(x3.schema, schema.model)
self.assertEqual(x2.order, 1)
self.assertEqual(x1.order, 2)
self.assertEqual(x2.order, 0)
self.assertEqual(x1.order, 1)
x4 = schema.insert_new('X4', position=3)
x4 = schema.insert_new('X4', position=2)
x3.refresh_from_db()
x2.refresh_from_db()
x1.refresh_from_db()
self.assertEqual(x4.order, 3)
self.assertEqual(x4.order, 2)
self.assertEqual(x4.schema, schema.model)
self.assertEqual(x3.order, 4)
self.assertEqual(x2.order, 1)
self.assertEqual(x1.order, 2)
self.assertEqual(x3.order, 3)
self.assertEqual(x2.order, 0)
self.assertEqual(x1.order, 1)
def test_insert_at_invalid_position(self):
with self.assertRaises(ValidationError):
self.schema.insert_new('X5', position=0)
self.schema.insert_new('X5', position=-2)
def test_insert_at_invalid_alias(self):
@ -84,24 +84,24 @@ class TestRSForm(DBTester):
def test_insert_at_reorder(self):
self.schema.insert_new('X1')
d1 = self.schema.insert_new('D1')
d2 = self.schema.insert_new('D2', position=1)
d2 = self.schema.insert_new('D2', position=0)
d1.refresh_from_db()
self.assertEqual(d1.order, 3)
self.assertEqual(d2.order, 1)
self.assertEqual(d1.order, 2)
self.assertEqual(d2.order, 0)
x2 = self.schema.insert_new('X2', position=4)
self.assertEqual(x2.order, 4)
x2 = self.schema.insert_new('X2', position=3)
self.assertEqual(x2.order, 3)
def test_insert_last(self):
x1 = self.schema.insert_new('X1')
self.assertEqual(x1.order, 1)
self.assertEqual(x1.order, 0)
self.assertEqual(x1.schema, self.schema.model)
x2 = self.schema.insert_new('X2')
self.assertEqual(x2.order, 2)
self.assertEqual(x2.order, 1)
self.assertEqual(x2.schema, self.schema.model)
self.assertEqual(x1.order, 1)
self.assertEqual(x1.order, 0)
def test_create_cst(self):
data = {
@ -120,8 +120,8 @@ class TestRSForm(DBTester):
self.assertEqual(x3.alias, data['alias'])
self.assertEqual(x3.term_raw, data['term_raw'])
self.assertEqual(x3.definition_raw, data['definition_raw'])
self.assertEqual(x2.order, 3)
self.assertEqual(x3.order, 2)
self.assertEqual(x2.order, 2)
self.assertEqual(x3.order, 1)
def test_create_cst_resolve(self):
@ -154,20 +154,20 @@ class TestRSForm(DBTester):
definition_raw='@{X10|plur}'
)
result = self.schema.insert_copy([s1, x1], 2)
result = self.schema.insert_copy([s1, x1], 1)
self.assertEqual(len(result), 2)
s1.refresh_from_db()
self.assertEqual(s1.order, 4)
self.assertEqual(s1.order, 3)
x2 = result[1]
self.assertEqual(x2.order, 3)
self.assertEqual(x2.order, 2)
self.assertEqual(x2.alias, 'X11')
self.assertEqual(x2.cst_type, CstType.BASE)
self.assertEqual(x2.convention, x1.convention)
s2 = result[0]
self.assertEqual(s2.order, 2)
self.assertEqual(s2.order, 1)
self.assertEqual(s2.alias, 'S12')
self.assertEqual(s2.cst_type, CstType.STRUCTURED)
self.assertEqual(s2.definition_formal, x2.alias)
@ -188,8 +188,8 @@ class TestRSForm(DBTester):
x2.refresh_from_db()
d1.refresh_from_db()
self.assertEqual(self.schema.constituents().count(), 2)
self.assertEqual(x2.order, 1)
self.assertEqual(d1.order, 2)
self.assertEqual(x2.order, 0)
self.assertEqual(d1.order, 1)
self.assertEqual(d1.definition_formal, 'DEL = X2')
self.assertEqual(d1.definition_raw, '@{DEL|sing}')
self.assertEqual(d1.term_raw, '@{X2|plur}')
@ -240,25 +240,25 @@ class TestRSForm(DBTester):
x2 = self.schema.insert_new('X2')
d1 = self.schema.insert_new('D1')
d2 = self.schema.insert_new('D2')
self.schema.move_cst([x2, d2], 1)
self.schema.move_cst([x2, d2], 0)
x1.refresh_from_db()
x2.refresh_from_db()
d1.refresh_from_db()
d2.refresh_from_db()
self.assertEqual(x1.order, 3)
self.assertEqual(x2.order, 1)
self.assertEqual(d1.order, 4)
self.assertEqual(d2.order, 2)
self.assertEqual(x1.order, 2)
self.assertEqual(x2.order, 0)
self.assertEqual(d1.order, 3)
self.assertEqual(d2.order, 1)
def test_move_cst_down(self):
x1 = self.schema.insert_new('X1')
x2 = self.schema.insert_new('X2')
self.schema.move_cst([x1], 2)
self.schema.move_cst([x1], 1)
x1.refresh_from_db()
x2.refresh_from_db()
self.assertEqual(x1.order, 2)
self.assertEqual(x2.order, 1)
self.assertEqual(x1.order, 1)
self.assertEqual(x2.order, 0)
def test_restore_order(self):
@ -316,18 +316,18 @@ class TestRSForm(DBTester):
f2.refresh_from_db()
a1.refresh_from_db()
self.assertEqual(x1.order, 1)
self.assertEqual(x2.order, 2)
self.assertEqual(c1.order, 3)
self.assertEqual(s1.order, 4)
self.assertEqual(d1.order, 5)
self.assertEqual(s2.order, 6)
self.assertEqual(d3.order, 7)
self.assertEqual(a1.order, 8)
self.assertEqual(d4.order, 9)
self.assertEqual(d2.order, 10)
self.assertEqual(f1.order, 11)
self.assertEqual(f2.order, 12)
self.assertEqual(x1.order, 0)
self.assertEqual(x2.order, 1)
self.assertEqual(c1.order, 2)
self.assertEqual(s1.order, 3)
self.assertEqual(d1.order, 4)
self.assertEqual(s2.order, 5)
self.assertEqual(d3.order, 6)
self.assertEqual(a1.order, 7)
self.assertEqual(d4.order, 8)
self.assertEqual(d2.order, 9)
self.assertEqual(f1.order, 10)
self.assertEqual(f2.order, 11)
def test_reset_aliases(self):

View File

@ -198,7 +198,7 @@ class TestRSFormViewset(EndpointTester):
response = self.executeCreated(data=data, item=self.owned_id)
self.assertEqual(response.data['new_cst']['alias'], 'X3')
x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.assertEqual(x3.order, 3)
self.assertEqual(x3.order, 2)
data = {
'alias': 'X4',
@ -210,7 +210,7 @@ class TestRSFormViewset(EndpointTester):
response = self.executeCreated(data=data, item=self.owned_id)
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.assertEqual(x4.order, 3)
self.assertEqual(x4.order, 2)
self.assertEqual(x4.term_raw, data['term_raw'])
self.assertEqual(x4.term_forms, data['term_forms'])
@ -257,7 +257,7 @@ class TestRSFormViewset(EndpointTester):
term_raw='@{X1|plur}',
definition_formal='X1'
)
self.assertEqual(x1.order, 1)
self.assertEqual(x1.order, 0)
self.assertEqual(x1.alias, 'X1')
self.assertEqual(x1.cst_type, CstType.BASE)
@ -269,7 +269,7 @@ class TestRSFormViewset(EndpointTester):
x1.refresh_from_db()
self.assertEqual(d1.term_resolved, '')
self.assertEqual(d1.term_raw, '@{D2|plur}')
self.assertEqual(x1.order, 1)
self.assertEqual(x1.order, 0)
self.assertEqual(x1.alias, 'D2')
self.assertEqual(x1.cst_type, CstType.TERM)
@ -354,7 +354,7 @@ class TestRSFormViewset(EndpointTester):
self.assertEqual(len(response.data['items']), 1)
self.assertEqual(self.owned.constituents().count(), 1)
self.assertEqual(x2.alias, 'X2')
self.assertEqual(x2.order, 1)
self.assertEqual(x2.order, 0)
x3 = self.unowned.insert_new('X1')
data = {'items': [x3.pk]}
@ -365,22 +365,22 @@ class TestRSFormViewset(EndpointTester):
def test_move_constituenta(self):
self.set_params(item=self.owned_id)
data = {'items': [1337], 'move_to': 1}
data = {'items': [1337], 'move_to': 0}
self.executeBadData(data=data)
x1 = self.owned.insert_new('X1')
x2 = self.owned.insert_new('X2')
data = {'items': [x2.pk], 'move_to': 1}
data = {'items': [x2.pk], 'move_to': 0}
response = self.executeOK(data=data)
x1.refresh_from_db()
x2.refresh_from_db()
self.assertEqual(response.data['id'], self.owned_id)
self.assertEqual(x1.order, 2)
self.assertEqual(x2.order, 1)
self.assertEqual(x1.order, 1)
self.assertEqual(x2.order, 0)
x3 = self.unowned.insert_new('X1')
data = {'items': [x3.pk], 'move_to': 1}
data = {'items': [x3.pk], 'move_to': 0}
self.executeBadData(data=data)
@ -399,11 +399,11 @@ class TestRSFormViewset(EndpointTester):
x1.refresh_from_db()
x2.refresh_from_db()
d11.refresh_from_db()
self.assertEqual(x2.order, 1)
self.assertEqual(x2.order, 0)
self.assertEqual(x2.alias, 'X1')
self.assertEqual(x1.order, 2)
self.assertEqual(x1.order, 1)
self.assertEqual(x1.alias, 'X2')
self.assertEqual(d11.order, 3)
self.assertEqual(d11.order, 2)
self.assertEqual(d11.alias, 'D1')
self.executeOK()
@ -462,9 +462,7 @@ class TestRSFormViewset(EndpointTester):
result = response.data['schema']
items = [item for item in result['items'] if item['id'] in response.data['cst_list']]
self.assertEqual(len(items), 2)
self.assertEqual(items[0]['order'], s1.order + 1)
self.assertEqual(items[0]['definition_formal'], 'Pr1(S1)')
self.assertEqual(items[1]['order'], s1.order + 2)
self.assertEqual(items[1]['definition_formal'], 'Pr2(S1)')
# Testing complex structure
@ -473,7 +471,6 @@ class TestRSFormViewset(EndpointTester):
result = response.data['schema']
items = [item for item in result['items'] if item['id'] in response.data['cst_list']]
self.assertEqual(len(items), 8)
self.assertEqual(items[0]['order'], s3.order + 1)
self.assertEqual(items[0]['definition_formal'], 'pr1(S3)')
# Testing function
@ -482,7 +479,6 @@ class TestRSFormViewset(EndpointTester):
result = response.data['schema']
items = [item for item in result['items'] if item['id'] in response.data['cst_list']]
self.assertEqual(len(items), 2)
self.assertEqual(items[0]['order'], f1.order + 1)
self.assertEqual(items[0]['definition_formal'], '[α∈X1, β∈X1] Pr1(F10[α,β])')
@ -497,7 +493,7 @@ class TestConstituentaAPI(EndpointTester):
alias='X1',
cst_type=CstType.BASE,
schema=self.rsform_owned.model,
order=1,
order=0,
convention='Test',
term_raw='Test1',
term_resolved='Test1R',
@ -506,7 +502,7 @@ class TestConstituentaAPI(EndpointTester):
alias='X2',
cst_type=CstType.BASE,
schema=self.rsform_unowned.model,
order=1,
order=0,
convention='Test1',
term_raw='Test2',
term_resolved='Test2R'
@ -514,7 +510,7 @@ class TestConstituentaAPI(EndpointTester):
self.cst3 = Constituenta.objects.create(
alias='X3',
schema=self.rsform_owned.model,
order=2,
order=1,
term_raw='Test3',
term_resolved='Test3',
definition_raw='Test1',
@ -594,14 +590,12 @@ class TestConstituentaAPI(EndpointTester):
data = {
'target': self.cst1.pk,
'item_data': {
'alias': 'X33',
'order': 10
'alias': 'X33'
}
}
response = self.executeOK(data=data, schema=self.rsform_owned.model.pk)
self.assertEqual(response.data['alias'], 'X1')
self.assertEqual(response.data['alias'], self.cst1.alias)
self.assertEqual(response.data['order'], self.cst1.order)
class TestInlineSynthesis(EndpointTester):
@ -669,8 +663,6 @@ class TestInlineSynthesis(EndpointTester):
response = self.executeOK(data=data)
result = {item['alias']: item for item in response.data['items']}
self.assertEqual(len(result), 6)
self.assertEqual(result['X2']['order'], 1)
self.assertEqual(result['X4']['order'], 2)
self.assertEqual(result['S1']['definition_formal'], 'X2')
self.assertEqual(result['S2']['definition_formal'], 'X4×X4')
self.assertEqual(result['D1']['definition_formal'], r'S1\S2\X2')

File diff suppressed because it is too large Load Diff

View File

@ -10,11 +10,11 @@
"dependencies": {
"@lezer/lr": "^1.4.2",
"@tanstack/react-table": "^8.20.5",
"@uiw/codemirror-themes": "^4.23.0",
"@uiw/react-codemirror": "^4.23.0",
"@uiw/codemirror-themes": "^4.23.2",
"@uiw/react-codemirror": "^4.23.2",
"axios": "^1.7.7",
"clsx": "^2.1.1",
"framer-motion": "^11.5.1",
"framer-motion": "^11.5.4",
"html-to-image": "^1.11.11",
"js-file-download": "^0.4.12",
"react": "^18.3.1",
@ -23,7 +23,7 @@
"react-icons": "^5.3.0",
"react-intl": "^6.6.8",
"react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.26.1",
"react-router-dom": "^6.26.2",
"react-select": "^5.8.0",
"react-tabs": "^6.0.2",
"react-toastify": "^10.0.5",
@ -36,14 +36,14 @@
"devDependencies": {
"@lezer/generator": "^1.7.1",
"@types/jest": "^29.5.12",
"@types/node": "^22.5.3",
"@types/node": "^22.5.4",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.1",
"eslint": "^9.10.0",
"eslint-plugin-react": "^7.35.2",
"eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.9.0",
@ -51,9 +51,9 @@
"postcss": "^8.4.45",
"tailwindcss": "^3.4.10",
"ts-jest": "^29.2.5",
"typescript": "^5.5.4",
"typescript-eslint": "^8.4.0",
"vite": "^5.4.3"
"typescript": "^5.6.2",
"typescript-eslint": "^8.5.0",
"vite": "^5.4.4"
}
},
"node_modules/@alloc/quick-lru": {
@ -1431,9 +1431,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.9.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz",
"integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==",
"version": "9.10.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
"dev": true,
"license": "MIT",
"engines": {
@ -1450,6 +1450,19 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"levn": "^0.4.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz",
@ -1616,9 +1629,9 @@
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"license": "MIT",
"engines": {
@ -2875,9 +2888,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz",
"integrity": "sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==",
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz",
"integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@ -3549,9 +3562,9 @@
}
},
"node_modules/@types/node": {
"version": "22.5.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.3.tgz",
"integrity": "sha512-njripolh85IA9SQGTAqbmnNZTdxv7X/4OYGPz8tgy5JDr8MP+uDBa921GpYEoDDnwm0Hmn5ZPeJgiiSTPoOzkQ==",
"version": "22.5.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -3672,17 +3685,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.4.0.tgz",
"integrity": "sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz",
"integrity": "sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.4.0",
"@typescript-eslint/type-utils": "8.4.0",
"@typescript-eslint/utils": "8.4.0",
"@typescript-eslint/visitor-keys": "8.4.0",
"@typescript-eslint/scope-manager": "8.5.0",
"@typescript-eslint/type-utils": "8.5.0",
"@typescript-eslint/utils": "8.5.0",
"@typescript-eslint/visitor-keys": "8.5.0",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@ -3706,16 +3719,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.4.0.tgz",
"integrity": "sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz",
"integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/scope-manager": "8.4.0",
"@typescript-eslint/types": "8.4.0",
"@typescript-eslint/typescript-estree": "8.4.0",
"@typescript-eslint/visitor-keys": "8.4.0",
"@typescript-eslint/scope-manager": "8.5.0",
"@typescript-eslint/types": "8.5.0",
"@typescript-eslint/typescript-estree": "8.5.0",
"@typescript-eslint/visitor-keys": "8.5.0",
"debug": "^4.3.4"
},
"engines": {
@ -3735,14 +3748,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz",
"integrity": "sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz",
"integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.4.0",
"@typescript-eslint/visitor-keys": "8.4.0"
"@typescript-eslint/types": "8.5.0",
"@typescript-eslint/visitor-keys": "8.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -3753,14 +3766,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.4.0.tgz",
"integrity": "sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz",
"integrity": "sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "8.4.0",
"@typescript-eslint/utils": "8.4.0",
"@typescript-eslint/typescript-estree": "8.5.0",
"@typescript-eslint/utils": "8.5.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@ -3778,9 +3791,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz",
"integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz",
"integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==",
"dev": true,
"license": "MIT",
"engines": {
@ -3792,14 +3805,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz",
"integrity": "sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz",
"integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/types": "8.4.0",
"@typescript-eslint/visitor-keys": "8.4.0",
"@typescript-eslint/types": "8.5.0",
"@typescript-eslint/visitor-keys": "8.5.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@ -3821,16 +3834,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.4.0.tgz",
"integrity": "sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz",
"integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.4.0",
"@typescript-eslint/types": "8.4.0",
"@typescript-eslint/typescript-estree": "8.4.0"
"@typescript-eslint/scope-manager": "8.5.0",
"@typescript-eslint/types": "8.5.0",
"@typescript-eslint/typescript-estree": "8.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -3844,13 +3857,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz",
"integrity": "sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz",
"integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.4.0",
"@typescript-eslint/types": "8.5.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
@ -3862,9 +3875,9 @@
}
},
"node_modules/@uiw/codemirror-extensions-basic-setup": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.0.tgz",
"integrity": "sha512-+k5nkRpUWGaHr1JWT8jcKsVewlXw5qBgSopm9LW8fZ6KnSNZBycz8kHxh0+WSvckmXEESGptkIsb7dlkmJT/hQ==",
"version": "4.23.2",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.2.tgz",
"integrity": "sha512-eacivkj7wzskl2HBYs4rfN0CbYlsSQh5ADtOYWTpc8Txm4ONw8RTi4/rxF6Ks2vdaovizewU5QaHximbxoNTrw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
@ -3889,9 +3902,9 @@
}
},
"node_modules/@uiw/codemirror-themes": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.23.0.tgz",
"integrity": "sha512-9fiji9xooZyBQozR1i6iTr56YP7j/Dr/VgsNWbqf5Szv+g+4WM1iZuiDGwNXmFMWX8gbkDzp6ASE21VCPSofWw==",
"version": "4.23.2",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.23.2.tgz",
"integrity": "sha512-g8x+oPqgbzxXSkHhRf7e1AM1mI9/Nl3URReS89pHitRKv8MZNrE+ey+HE8ycfNXRUatrb6zTSRV3M75uoZwNYw==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
@ -3908,16 +3921,16 @@
}
},
"node_modules/@uiw/react-codemirror": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.23.0.tgz",
"integrity": "sha512-MnqTXfgeLA3fsUUQjqjJgemEuNyoGALgsExVm0NQAllAAi1wfj+IoKFeK+h3XXMlTFRCFYOUh4AHDv0YXJLsOg==",
"version": "4.23.2",
"resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.23.2.tgz",
"integrity": "sha512-MmFL6P5V1Mr81JLkJyWNedfxENKdRhsvyU7Izji9wp337m8dqRAz7rCF5XWarGKx+iQ7q2H5ryl07nLqKLSvtQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.6",
"@codemirror/commands": "^6.1.0",
"@codemirror/state": "^6.1.1",
"@codemirror/theme-one-dark": "^6.0.0",
"@uiw/codemirror-extensions-basic-setup": "4.23.0",
"@uiw/codemirror-extensions-basic-setup": "4.23.2",
"codemirror": "^6.0.0"
},
"funding": {
@ -4729,9 +4742,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001655",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz",
"integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==",
"version": "1.0.30001660",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
"integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==",
"dev": true,
"funding": [
{
@ -4828,9 +4841,9 @@
}
},
"node_modules/cjs-module-lexer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.0.tgz",
"integrity": "sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz",
"integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==",
"dev": true,
"license": "MIT"
},
@ -5427,12 +5440,12 @@
"license": "MIT"
},
"node_modules/debug": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@ -5521,9 +5534,9 @@
}
},
"node_modules/detect-gpu": {
"version": "5.0.46",
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.46.tgz",
"integrity": "sha512-aulQlEJDVAADo2j4ZkcEu/mtuX9dz104w7uIDa52/ntcKdOEM8aI+k91Wv4x0o+Gds4Nbd2Sds0Uaqp1ZuLLJw==",
"version": "5.0.47",
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.47.tgz",
"integrity": "sha512-hxOjFbFN6/ToNzDs0SIt/P/Y1WxoxAEUXXlrw/HT2IPtDtIxSi57zP/TC6kTvWDWmwSnvfVHsPoDZihscx8OJQ==",
"license": "MIT",
"dependencies": {
"webgl-constants": "^1.1.1"
@ -5616,9 +5629,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.13",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz",
"integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==",
"version": "1.5.19",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz",
"integrity": "sha512-kpLJJi3zxTR1U828P+LIUDZ5ohixyo68/IcYOHLqnbTPr/wdgn4i1ECvmALN9E16JPA6cvCG5UG79gVwVdEK5w==",
"dev": true,
"license": "ISC"
},
@ -5882,9 +5895,9 @@
}
},
"node_modules/eslint": {
"version": "9.9.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz",
"integrity": "sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==",
"version": "9.10.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -5892,7 +5905,8 @@
"@eslint-community/regexpp": "^4.11.0",
"@eslint/config-array": "^0.18.0",
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "9.9.1",
"@eslint/js": "9.10.0",
"@eslint/plugin-kit": "^0.1.0",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.3.0",
"@nodelib/fs.walk": "^1.2.8",
@ -5915,7 +5929,6 @@
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
@ -6495,9 +6508,9 @@
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.8.tgz",
"integrity": "sha512-xgrmBhBToVKay1q2Tao5LI26B83UhrB/vM1avwVSDzt8rx3rO6AizBAaF46EgksTVr+rFTQaqZZ9MVBfUe4nig==",
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
@ -6583,9 +6596,9 @@
}
},
"node_modules/framer-motion": {
"version": "11.5.1",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.5.1.tgz",
"integrity": "sha512-8QI17IxYiqWo+lIXmAopG+6NEi10CgTL1AlQRpcNcSxoyrrcRgXB/tc6pzmeloZ3y4xTdoFCN/s4mJAdTz6Clw==",
"version": "11.5.4",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.5.4.tgz",
"integrity": "sha512-E+tb3/G6SO69POkdJT+3EpdMuhmtCh9EWuK4I1DnIC23L7tFPrl8vxP+LSovwaw6uUr73rUbpb4FgK011wbRJQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.4.0"
@ -10033,9 +10046,9 @@
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/mz": {
@ -11035,12 +11048,12 @@
}
},
"node_modules/react-router": {
"version": "6.26.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.1.tgz",
"integrity": "sha512-kIwJveZNwp7teQRI5QmwWo39A5bXRyqpH0COKKmPnyD2vBvDwgFXSqDUYtt1h+FEyfnE8eXr7oe0MxRzVwCcvQ==",
"version": "6.26.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz",
"integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.19.1"
"@remix-run/router": "1.19.2"
},
"engines": {
"node": ">=14.0.0"
@ -11050,13 +11063,13 @@
}
},
"node_modules/react-router-dom": {
"version": "6.26.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.1.tgz",
"integrity": "sha512-veut7m41S1fLql4pLhxeSW3jlqs+4MtjRLj0xvuCEXsxusJCbs6I8yn9BxzzDX2XDgafrccY6hwjmd/bL54tFw==",
"version": "6.26.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz",
"integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.19.1",
"react-router": "6.26.1"
"@remix-run/router": "1.19.2",
"react-router": "6.26.2"
},
"engines": {
"node": ">=14.0.0"
@ -11662,9 +11675,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@ -12229,9 +12242,9 @@
"license": "MIT"
},
"node_modules/three-mesh-bvh": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.6.tgz",
"integrity": "sha512-rCjsnxEqR9r1/C/lCqzGLS67NDty/S/eT6rAJfDvsanrIctTWdNoR4ZOGWewCB13h1QkVo2BpmC0wakj1+0m8A==",
"version": "0.7.8",
"resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.8.tgz",
"integrity": "sha512-BGEZTOIC14U0XIRw3tO4jY7IjP7n7v24nv9JXS1CyeVRWOCkcOMhRnmENUjuV39gktAw4Ofhr0OvIAiTspQrrw==",
"license": "MIT",
"peerDependencies": {
"three": ">= 0.151.0"
@ -12517,9 +12530,9 @@
}
},
"node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
@ -12531,15 +12544,15 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.4.0.tgz",
"integrity": "sha512-67qoc3zQZe3CAkO0ua17+7aCLI0dU+sSQd1eKPGq06QE4rfQjstVXR6woHO5qQvGUa550NfGckT4tzh3b3c8Pw==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.5.0.tgz",
"integrity": "sha512-uD+XxEoSIvqtm4KE97etm32Tn5MfaZWgWfMMREStLxR6JzvHkc2Tkj7zhTEK5XmtpTmKHNnG8Sot6qDfhHtR1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.4.0",
"@typescript-eslint/parser": "8.4.0",
"@typescript-eslint/utils": "8.4.0"
"@typescript-eslint/eslint-plugin": "8.5.0",
"@typescript-eslint/parser": "8.5.0",
"@typescript-eslint/utils": "8.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -12698,9 +12711,9 @@
}
},
"node_modules/vite": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz",
"integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==",
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.4.tgz",
"integrity": "sha512-RHFCkULitycHVTtelJ6jQLd+KSAAzOgEYorV32R2q++M6COBjKJR6BxqClwp5sf0XaBDjVMuJ9wnNfyAJwjMkA==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@ -14,11 +14,11 @@
"dependencies": {
"@lezer/lr": "^1.4.2",
"@tanstack/react-table": "^8.20.5",
"@uiw/codemirror-themes": "^4.23.0",
"@uiw/react-codemirror": "^4.23.0",
"@uiw/codemirror-themes": "^4.23.2",
"@uiw/react-codemirror": "^4.23.2",
"axios": "^1.7.7",
"clsx": "^2.1.1",
"framer-motion": "^11.5.1",
"framer-motion": "^11.5.4",
"html-to-image": "^1.11.11",
"js-file-download": "^0.4.12",
"react": "^18.3.1",
@ -27,7 +27,7 @@
"react-icons": "^5.3.0",
"react-intl": "^6.6.8",
"react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.26.1",
"react-router-dom": "^6.26.2",
"react-select": "^5.8.0",
"react-tabs": "^6.0.2",
"react-toastify": "^10.0.5",
@ -40,14 +40,14 @@
"devDependencies": {
"@lezer/generator": "^1.7.1",
"@types/jest": "^29.5.12",
"@types/node": "^22.5.3",
"@types/node": "^22.5.4",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.1",
"eslint": "^9.10.0",
"eslint-plugin-react": "^7.35.2",
"eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.9.0",
@ -55,9 +55,9 @@
"postcss": "^8.4.45",
"tailwindcss": "^3.4.10",
"ts-jest": "^29.2.5",
"typescript": "^5.5.4",
"typescript-eslint": "^8.4.0",
"vite": "^5.4.3"
"typescript": "^5.6.2",
"typescript-eslint": "^8.5.0",
"vite": "^5.4.4"
},
"jest": {
"preset": "ts-jest",

View File

@ -116,6 +116,22 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
if (!selection.empty || !schema) {
return;
}
const wordRange = text.getWord(selection.from);
if (wordRange) {
const word = text.getText(wordRange.from, wordRange.to);
if (word.length > 2 && (word.startsWith('Pr') || word.startsWith('pr'))) {
text.setSelection(wordRange.from, wordRange.from + 2);
if (word.startsWith('Pr')) {
text.replaceWith('pr');
} else {
text.replaceWith('Pr');
}
event.preventDefault();
event.stopPropagation();
return;
}
}
const hint = text.getText(selection.from - 1, selection.from);
const type = guessCstType(hint);
if (hint === getCstTypePrefix(type)) {

View File

@ -70,7 +70,9 @@ function PickConstituenta({
cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixID} />
}),
columnHelper.accessor(cst => describeFunc(cst), {
id: 'description'
id: 'description',
size: 1000,
minSize: 1000
})
],
[colors, prefixID, describeFunc]

View File

@ -127,7 +127,6 @@ function DataTable<TData extends RowData>({
const isEmpty = tableImpl.getRowModel().rows.length === 0;
// TODO: refactor formula for different font sizes and pagination tools
const fixedSize = useMemo(() => {
if (!rows) {
return undefined;

View File

@ -46,7 +46,7 @@ function FileInput({ id, label, acceptType, title, className, style, onChange, .
{...restProps}
/>
<Button text={label} icon={<IconUpload size='1.25rem' />} onClick={handleUploadClick} title={title} />
<Label text={fileName} htmlFor={id} />
<Label className='text-wrap' text={fileName} htmlFor={id} />
</div>
);
}

View File

@ -103,23 +103,19 @@ function Modal({
{children}
</div>
<div className={clsx('z-modalControls', 'px-6 py-3 flex gap-12 justify-center')}>
<div className='z-modalControls my-2 flex gap-12 justify-center text-sm'>
{!readonly ? (
<Button
autoFocus
text={submitText}
title={!canSubmit ? submitInvalidTooltip : ''}
className='min-w-[8rem] min-h-[2.6rem]'
className='min-w-[7rem]'
colors='clr-btn-primary'
disabled={!canSubmit}
onClick={handleSubmit}
/>
) : null}
<Button
text={readonly ? 'Закрыть' : 'Отмена'}
className='min-w-[8rem] min-h-[2.6rem]'
onClick={handleCancel}
/>
<Button text={readonly ? 'Закрыть' : 'Отмена'} className='min-w-[7rem]' onClick={handleCancel} />
</div>
</motion.div>
</div>

View File

@ -110,8 +110,14 @@ export const OptionsState = ({ children }: OptionsStateProps) => {
}, [setDarkMode]);
const mainHeight = useMemo(() => {
return !noNavigation ? 'calc(100dvh - 6.75rem)' : '100dvh';
}, [noNavigation]);
if (noNavigation) {
return '100dvh';
} else if (noFooter) {
return 'calc(100dvh - 3rem)';
} else {
return 'calc(100dvh - 6.75rem)';
}
}, [noNavigation, noFooter]);
const viewportHeight = useMemo(() => {
return !noNavigation ? 'calc(100dvh - 3rem)' : '100dvh';

View File

@ -160,13 +160,13 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
<Modal
header='Создание конституенты из шаблона'
submitText='Создать'
className='w-[43rem] h-[36.5rem] px-6'
className='w-[43rem] h-[35rem] px-6'
hideWindow={hideWindow}
canSubmit={validated}
beforeSubmit={handlePrompt}
onSubmit={handleSubmit}
>
<Overlay position='top-0 right-[6rem]'>
<Overlay position='top-0 right-[5.9rem]'>
<BadgeHelp
topic={HelpTopic.RSL_TEMPLATES}
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}

View File

@ -118,7 +118,7 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
data={filteredData}
onSelectValue={cst => partialUpdate({ prototype: cst })}
prefixID={prefixes.cst_template_ist}
rows={9}
rows={8}
/>
<TextArea
id='dlg_template_term'

View File

@ -147,7 +147,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
hideWindow={hideWindow}
canSubmit={isValid}
onSubmit={handleSubmit}
className='w-[40rem] px-6 min-h-[35rem]'
className='w-[40rem] px-6 h-[32rem]'
>
<Overlay position='top-0 right-0'>
<BadgeHelp topic={HelpTopic.CC_OSS} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} offset={14} />
@ -155,11 +155,11 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
<Tabs
selectedTabClassName='clr-selected'
className='flex flex-col'
className='flex flex-col pt-2'
selectedIndex={activeTab}
onSelect={handleSelectTab}
>
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
<TabList className={clsx('self-center absolute top-[2.4rem]', 'flex', 'border divide-x rounded-none')}>
<TabLabel
title={describeOperationType(OperationType.INPUT)}
label={labelOperationType(OperationType.INPUT)}

View File

@ -169,7 +169,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
hideWindow={hideWindow}
canSubmit={canSubmit}
onSubmit={handleSubmit}
className='w-[40rem] px-6 min-h-[35rem]'
className='w-[40rem] px-6 h-[32rem]'
>
<Overlay position='top-0 right-0'>
<BadgeHelp topic={HelpTopic.CC_OSS} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} offset={14} />

View File

@ -35,7 +35,7 @@ function TabSynthesis({
<PickSubstitutions
schemas={schemas}
prefixID={prefixes.dlg_cst_substitutes_list}
rows={10}
rows={8}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
suggestions={suggestions}

View File

@ -70,7 +70,7 @@ function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditRefere
hideWindow={hideWindow}
canSubmit={isValid}
onSubmit={handleSubmit}
className='w-[40rem] px-6 min-h-[35rem]'
className='w-[40rem] px-6 h-[32rem]'
>
<Overlay position='top-0 right-0'>
<BadgeHelp

View File

@ -70,7 +70,7 @@ function TabEntityReference({ initial, schema, setIsValid, setReference }: TabEn
describeFunc={cst => cst.term_resolved}
matchFunc={(cst, filter) => matchConstituenta(cst, filter, CstMatchMode.TERM)}
onBeginFilter={cst => cst.term_resolved !== ''}
rows={8}
rows={7}
/>
<div className='flex gap-3'>

View File

@ -96,7 +96,7 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
<Modal
header='Импорт концептуальной схем'
submitText='Добавить конституенты'
className='w-[40rem] h-[36rem] px-6'
className='w-[40rem] h-[33rem] px-6'
hideWindow={hideWindow}
canSubmit={validated}
onSubmit={handleSubmit}

View File

@ -19,7 +19,7 @@ function TabConstituents({ schema, error, loading, selected, setSelected }: TabC
<DataLoader id='dlg-constituents-tab' isLoading={loading} error={error} hasNoData={!schema}>
<PickMultiConstituenta
schema={schema}
rows={14}
rows={13}
prefixID={prefixes.cst_inline_synth_list}
selected={selected}
setSelected={setSelected}

View File

@ -35,7 +35,7 @@ function TabSchema({ selected, setSelected }: TabSchemaProps) {
id='dlg_schema_picker' // prettier: split lines
items={library.items}
itemType={LibraryItemType.RSFORM}
rows={15}
rows={14}
value={selected}
onSelectValue={setSelected}
/>

View File

@ -25,11 +25,6 @@ export enum CstType {
// CstType constant for category dividers in TemplateSchemas
export const CATEGORY_CST_TYPE = CstType.THEOREM;
/**
* Represents position in linear order.
*/
export type Position = number;
/**
* Represents {@link IConstituenta} identifier type.
*/
@ -71,7 +66,6 @@ export interface TermForm {
export interface IConstituentaMeta {
id: ConstituentaID;
schema: LibraryItemID;
order: Position;
alias: string;
convention: string;
cst_type: CstType;
@ -146,7 +140,7 @@ export interface ICstCreateData
* Represents data, used in ordering a list of {@link IConstituenta}.
*/
export interface ICstMovetoData extends IConstituentaList {
move_to: Position;
move_to: number; // Note: 0-base index
}
/**

View File

@ -120,7 +120,6 @@ export function createMockConstituenta(id: ConstituentaID, alias: string, commen
children: [],
children_alias: [],
is_simple_expression: false,
order: -1,
schema: -1,
alias: alias,
convention: comment,
@ -157,8 +156,17 @@ export function isMockCst(cst: IConstituenta) {
* Apply filter based on start {@link IConstituenta} type.
*/
export function applyFilterCategory(start: IConstituenta, schema: IRSForm): IConstituenta[] {
const nextCategory = schema.items.find(cst => cst.order > start.order && cst.cst_type === CATEGORY_CST_TYPE);
return schema.items.filter(cst => cst.order >= start.order && (!nextCategory || cst.order < nextCategory.order));
const startIndex = schema.items.indexOf(start);
if (startIndex === -1) {
return [];
}
const nextCategoryIndex = schema.items.findIndex(
(cst, index) => index > startIndex && cst.cst_type === CATEGORY_CST_TYPE
);
return schema.items.filter(
(_, index) => index >= startIndex && (nextCategoryIndex === -1 || index < nextCategoryIndex)
);
}
/**

View File

@ -123,7 +123,7 @@ function FormCreateItem() {
return (
<form className={clsx('cc-column', 'min-w-[30rem] max-w-[30rem] mx-auto', 'px-6 py-3')} onSubmit={handleSubmit}>
<h1>
<h1 className='select-none'>
{itemType == LibraryItemType.RSFORM ? (
<Overlay position='top-0 right-[0.5rem]'>
<input
@ -144,7 +144,7 @@ function FormCreateItem() {
Создание схемы
</h1>
{fileName ? <Label text={`Загружен файл: ${fileName}`} /> : null}
{fileName ? <Label className='text-wrap' text={`Загружен файл: ${fileName}`} /> : null}
<TextInput
id='schema_title'

View File

@ -94,7 +94,7 @@ function HelpRSEditor() {
<IconTree className='inline-icon' /> отображение{' '}
<LinkTopic text='дерева разбора' topic={HelpTopic.UI_FORMULA_TREE} />
</li>
<li>Ctrl + Пробел дополняет до незанятого имени</li>
<li>Ctrl + Пробел вставка незанятого имени / замена проекции</li>
<h2>Термин и Текстовое определение</h2>
<li>

View File

@ -49,7 +49,7 @@ function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardPr
/>
<AnimateFade
onKeyDown={handleInput}
className={clsx('md:w-fit md:max-w-fit max-w-[32rem]', 'mx-auto ', 'flex flex-col md:flex-row px-6')}
className={clsx('md:w-fit md:max-w-fit max-w-[32rem]', 'mx-auto pt-[1.9rem]', 'flex flex-col md:flex-row px-6')}
>
<FlexColumn className='px-3'>
<FormOSS id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />

View File

@ -40,7 +40,7 @@ interface OssFlowProps {
}
function OssFlow({ isModified, setIsModified }: OssFlowProps) {
const { calculateHeight, colors } = useConceptOptions();
const { mainHeight, colors } = useConceptOptions();
const model = useOSS();
const controller = useOssEdit();
const flow = useReactFlow();
@ -342,8 +342,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
}
}
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
const OssNodeTypes: NodeTypes = useMemo(
() => ({
synthesis: OperationNode,
@ -352,6 +350,8 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
[]
);
// proOptions={{ hideAttribution: true }}
const graph = useMemo(
() => (
<ReactFlow
@ -360,7 +360,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
onNodesChange={handleNodesChange}
onEdgesChange={onEdgesChange}
onNodeDoubleClick={handleNodeDoubleClick}
proOptions={{ hideAttribution: true }}
fitView
nodeTypes={OssNodeTypes}
maxZoom={2}
@ -389,7 +388,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
return (
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}>
<Overlay position='top-0 pt-1 right-1/2 translate-x-1/2' className='rounded-b-2xl cc-blur'>
<Overlay position='top-[1.9rem] pt-1 right-1/2 translate-x-1/2' className='rounded-b-2xl cc-blur'>
<ToolbarOssGraph
isModified={isModified}
showGrid={showGrid}
@ -419,7 +418,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
{...menuProps}
/>
) : null}
<div className='relative w-[100vw]' style={{ height: canvasHeight }}>
<div className='relative w-[100vw]' style={{ height: mainHeight }}>
{graph}
</div>
</AnimateFade>

View File

@ -9,6 +9,7 @@ import { toast } from 'react-toastify';
import { urls } from '@/app/urls';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import Loader from '@/components/ui/Loader';
import Overlay from '@/components/ui/Overlay';
import TabLabel from '@/components/ui/TabLabel';
import TextURL from '@/components/ui/TextURL';
import AnimateFade from '@/components/wrap/AnimateFade';
@ -37,7 +38,7 @@ function OssTabs() {
const activeTab = query.get('tab') ? (Number(query.get('tab')) as OssTabID) : OssTabID.GRAPH;
const { user } = useAuth();
const { calculateHeight, setNoFooter } = useConceptOptions();
const { setNoFooter } = useConceptOptions();
const { schema, loading, loadingError: errorLoading } = useOSS();
const { destroyItem } = useLibrary();
@ -107,8 +108,6 @@ function OssTabs() {
});
}, [schema, destroyItem, router]);
const panelHeight = useMemo(() => calculateHeight('1.625rem + 2px'), [calculateHeight]);
const cardPanel = useMemo(
() => (
<TabPanel>
@ -143,14 +142,16 @@ function OssTabs() {
selectedTabClassName='clr-selected'
className='flex flex-col mx-auto min-w-fit'
>
<TabList className={clsx('mx-auto w-fit', 'flex items-stretch', 'border-b-2 border-x-2 divide-x-2')}>
<Overlay position='top-0 right-1/2 translate-x-1/2' layer='z-sticky'>
<TabList className={clsx('w-fit', 'flex items-stretch', 'border-b-2 border-x-2 divide-x-2')}>
<MenuOssTabs onDestroy={onDestroySchema} />
<TabLabel label='Карточка' title={schema.title ?? ''} />
<TabLabel label='Граф' />
</TabList>
</Overlay>
<AnimateFade className='overflow-y-auto' style={{ maxHeight: panelHeight }}>
<AnimateFade>
{cardPanel}
{graphPanel}
</AnimateFade>

View File

@ -28,7 +28,7 @@ interface EditorConstituentaProps {
function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }: EditorConstituentaProps) {
const controller = useRSEdit();
const windowSize = useWindowSize();
const { calculateHeight } = useConceptOptions();
const { mainHeight } = useConceptOptions();
const [showList, setShowList] = useLocalStorage(storage.rseditShowList, true);
const [toggleReset, setToggleReset] = useState(false);
@ -39,7 +39,6 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
);
const isNarrow = useMemo(() => !!windowSize.width && windowSize.width <= SIDELIST_LAYOUT_THRESHOLD, [windowSize]);
const panelHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
if (disabled) {
@ -79,7 +78,7 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
}
return (
<div className='overflow-y-auto min-h-[20rem]' style={{ maxHeight: panelHeight }}>
<>
<ToolbarConstituenta
activeCst={activeCst}
disabled={disabled}
@ -89,6 +88,7 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
onReset={() => setToggleReset(prev => !prev)}
onToggleList={() => setShowList(prev => !prev)}
/>
<div className='pt-[1.9rem] overflow-y-auto min-h-[20rem]' style={{ maxHeight: mainHeight }}>
<div
tabIndex={-1}
className={clsx(
@ -122,6 +122,7 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
</AnimatePresence>
</div>
</div>
</>
);
}

View File

@ -50,7 +50,7 @@ function ToolbarConstituenta({
return (
<Overlay
position='top-1 right-1/2 translate-x-1/2 xs:right-4 xs:translate-x-0 md:right-1/2 md:translate-x-1/2'
position='cc-tab-tools right-1/2 translate-x-1/2 xs:right-4 xs:translate-x-0 md:right-1/2 md:translate-x-1/2'
className='cc-icons outline-none transition-all duration-500'
>
{controller.schema && controller.schema?.oss.length > 0 ? (

View File

@ -32,7 +32,7 @@ function ParsingResult({ isOpen, data, disabled, onShowError }: ParsingResultPro
<p
tabIndex={-1}
key={`error-${index}`}
className={`clr-text-red ${disabled ? '' : 'cursor-pointer'}`}
className={`clr-text-red break-all ${disabled ? '' : 'cursor-pointer'}`}
onClick={disabled ? undefined : () => onShowError(error)}
>
<span className='mr-1 font-semibold underline'>

View File

@ -49,7 +49,7 @@ function EditorRSFormCard({ isModified, onDestroy, setIsModified }: EditorRSForm
/>
<AnimateFade
onKeyDown={handleInput}
className={clsx('md:w-fit md:max-w-fit max-w-[32rem] mx-auto', 'flex flex-col md:flex-row px-6')}
className={clsx('md:w-fit md:max-w-fit max-w-[32rem] mx-auto', 'flex flex-col md:flex-row px-6 pt-[1.9rem]')}
>
<FlexColumn className='flex-shrink'>
<FormRSForm id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />

View File

@ -45,7 +45,7 @@ function ToolbarRSFormCard({ modified, controller, onSubmit, onDestroy }: Toolba
}, [controller]);
return (
<Overlay position='top-1 right-1/2 translate-x-1/2' className='cc-icons'>
<Overlay position='cc-tab-tools' className='cc-icons'>
{ossSelector}
{controller.isMutable || modified ? (
<MiniButton

View File

@ -142,7 +142,7 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
return (
<>
{controller.isContentEditable ? <ToolbarRSList /> : null}
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}>
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown} className='pt-[1.9rem]'>
{controller.isContentEditable ? (
<div className='flex items-center border-b'>
<div className='px-2'>

View File

@ -28,7 +28,7 @@ function ToolbarRSList() {
return (
<Overlay
position='top-1 right-4 translate-x-0 md:right-1/2 md:translate-x-1/2'
position='cc-tab-tools right-4 translate-x-0 md:right-1/2 md:translate-x-1/2'
className='cc-icons items-start outline-none transition-all duration-500'
>
{controller.schema && controller.schema?.oss.length > 0 ? (

View File

@ -287,7 +287,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
);
return (
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}>
<>
<AnimatePresence>
{showParamsDialog ? (
<DlgGraphParams
@ -298,10 +298,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
) : null}
</AnimatePresence>
<Overlay
position='top-0 pt-1 right-1/2 translate-x-1/2'
className='flex flex-col items-center rounded-b-2xl cc-blur'
>
<Overlay position='cc-tab-tools' className='flex flex-col items-center rounded-b-2xl cc-blur'>
<ToolbarTermGraph
is3D={is3D}
orbit={orbit}
@ -352,6 +349,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
) : null}
</Overlay>
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}>
<SelectedCounter
hideZero
totalCount={controller.schema?.stats?.count_all ?? 0}
@ -362,7 +360,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
{hoverCst && hoverCstDebounced && hoverCst === hoverCstDebounced ? (
<Overlay
layer='z-tooltip'
position={clsx('top-[1.6rem]', { 'left-[2.6rem]': hoverLeft, 'right-[2.6rem]': !hoverLeft })}
position={clsx('top-[3.5rem]', { 'left-[2.6rem]': hoverLeft, 'right-[2.6rem]': !hoverLeft })}
className={clsx(
'w-[25rem] max-h-[calc(100dvh-15rem)]',
'px-3',
@ -376,7 +374,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
</Overlay>
) : null}
<Overlay position='top-[6.25rem] sm:top-[4rem] left-0' className='flex gap-1'>
<Overlay position='top-[8.15rem] sm:top-[5.9rem] left-0' className='flex gap-1'>
<div className='flex flex-col ml-2 w-[13.5rem]'>
{selectors}
{viewHidden}
@ -385,6 +383,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
{graph}
</AnimateFade>
</>
);
}

View File

@ -53,7 +53,7 @@ function TermGraph({
onSelect,
onDeselect
}: TermGraphProps) {
const { calculateHeight, darkMode } = useConceptOptions();
const { mainHeight, darkMode } = useConceptOptions();
const { selections, setSelections } = useSelection({
ref: graphRef,
@ -111,10 +111,8 @@ function TermGraph({
return 'calc(100vw - 1rem)';
}, []);
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
return (
<div className='relative outline-none' style={{ width: canvasWidth, height: canvasHeight }}>
<div className='relative outline-none' style={{ width: canvasWidth, height: mainHeight }}>
<GraphUI
nodes={nodes}
edges={edges}

View File

@ -406,8 +406,8 @@ export const RSEditState = ({
}
return Math.min(prev, index);
}, -1);
const target = Math.max(0, currentIndex - 1) + 1;
const data = {
const target = Math.max(0, currentIndex - 1);
const data: ICstMovetoData = {
items: selected,
move_to: target
};
@ -430,7 +430,7 @@ export const RSEditState = ({
return Math.max(prev, index);
}
}, -1);
const target = Math.min(model.schema.items.length - 1, currentIndex - count + 2) + 1;
const target = Math.min(model.schema.items.length - 1, currentIndex - count + 2);
const data: ICstMovetoData = {
items: selected,
move_to: target

View File

@ -10,6 +10,7 @@ import { urls } from '@/app/urls';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import Divider from '@/components/ui/Divider';
import Loader from '@/components/ui/Loader';
import Overlay from '@/components/ui/Overlay';
import TabLabel from '@/components/ui/TabLabel';
import TextURL from '@/components/ui/TextURL';
import AnimateFade from '@/components/wrap/AnimateFade';
@ -45,7 +46,7 @@ function RSTabs() {
const version = query.get('v') ? Number(query.get('v')) : undefined;
const cstQuery = query.get('active');
const { setNoFooter, calculateHeight } = useConceptOptions();
const { setNoFooter } = useConceptOptions();
const { schema, loading, errorLoading, isArchive, itemID } = useRSForm();
const library = useLibrary();
const oss = useGlobalOss();
@ -73,7 +74,7 @@ function RSTabs() {
}, [schema, schema?.title]);
useLayoutEffect(() => {
setNoFooter(activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST);
setNoFooter(activeTab !== RSTabID.CARD);
setIsModified(false);
if (activeTab === RSTabID.CST_EDIT) {
const cstID = Number(cstQuery);
@ -189,8 +190,6 @@ function RSTabs() {
});
}, [schema, library, oss, router]);
const panelHeight = useMemo(() => calculateHeight('1.625rem + 2px'), [calculateHeight]);
const cardPanel = useMemo(
() => (
<TabPanel>
@ -255,19 +254,23 @@ function RSTabs() {
selectedTabClassName='clr-selected'
className='flex flex-col mx-auto min-w-fit'
>
<Overlay position='top-0 right-1/2 translate-x-1/2' layer='z-sticky'>
<TabList className={clsx('mx-auto w-fit', 'flex items-stretch', 'border-b-2 border-x-2 divide-x-2')}>
<MenuRSTabs onDestroy={onDestroySchema} />
<TabLabel label='Карточка' titleHtml={`${schema.title ?? ''}<br />Версия: ${labelVersion(schema)}`} />
<TabLabel
label='Содержание'
titleHtml={`Конституент: ${schema.stats?.count_all ?? 0}<br />Ошибок: ${schema.stats?.count_errors ?? 0}`}
titleHtml={`Конституент: ${schema.stats?.count_all ?? 0}<br />Ошибок: ${
schema.stats?.count_errors ?? 0
}`}
/>
<TabLabel label='Редактор' />
<TabLabel label='Граф термов' />
</TabList>
</Overlay>
<AnimateFade className='overflow-y-auto overflow-x-hidden' style={{ maxHeight: panelHeight }}>
<AnimateFade className='overflow-x-hidden'>
{cardPanel}
{listPanel}
{editorPanel}

View File

@ -64,6 +64,14 @@
cursor: default;
}
.react-flow__attribution {
background-color: transparent;
color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-60);
}
}
:is(.react-flow__node-input, .react-flow__node-synthesis) {
cursor: pointer;

View File

@ -49,17 +49,40 @@
}
}
:is(.clr-app, .clr-footer, .cc-modal-backdrop, .clr-btn-nav, .clr-input:disabled) {
:is(.clr-app, .clr-btn-nav) {
background-color: var(--cl-bg-100);
.dark & {
background-color: var(--cd-bg-100);
}
}
:is(.clr-input) {
.clr-footer {
color: var(--cl-fg-60);
background-color: var(--cl-bg-100);
.dark & {
color: var(--cd-fg-60);
background-color: var(--cd-bg-100);
}
}
.cc-modal-backdrop {
opacity: 0.5;
background-color: var(--cl-bg-100);
.dark & {
background-color: var(--cd-bg-100);
}
}
.clr-input {
background-color: var(--cl-bg-120);
&:disabled {
background-color: var(--cl-bg-100);
}
.dark & {
background-color: var(--cd-bg-120);
&:disabled {
background-color: var(--cd-bg-100);
}
}
}
@ -126,13 +149,6 @@
}
}
.clr-footer {
color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-60);
}
}
:is(.clr-text-controls, .clr-btn-nav, .clr-btn-clear) {
color: var(--cl-fg-80);
&:disabled {
@ -153,7 +169,7 @@
}
}
:is(.clr-text-default, input:disabled, textarea:disabled) {
:is(.clr-text-default, input:disabled:not(::placeholder), textarea:disabled:not(::placeholder)) {
opacity: 1;
-webkit-text-fill-color: var(--cl-fg-100);
color: var(--cl-fg-100);
@ -205,15 +221,15 @@
@apply clr-text-primary;
}
.cc-tab-tools {
@apply top-[1.9rem] pt-1 right-1/2 translate-x-1/2;
}
.cc-modal-blur {
opacity: 0.3;
backdrop-filter: blur(2px);
}
.cc-modal-backdrop {
opacity: 0.5;
}
.cc-label {
@apply text-sm font-medium cursor-default select-text whitespace-nowrap;
}

View File

@ -357,6 +357,10 @@ export class CodeMirrorWrapper {
return this.ref.view.state.doc.sliceString(from, to);
}
getWord(position: number): SelectionRange | null {
return this.ref.view.state.wordAt(position);
}
getSelection(): SelectionRange {
return this.ref.view.state.selection.main;
}