mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
Merge branch 'main' into fix/trigger-apis
This commit is contained in:
@ -1,14 +1,27 @@
|
|||||||
from typing import Literal
|
from typing import Literal
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
from flask_restx import Namespace, Resource, fields, marshal_with
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
|
from controllers.common.schema import register_schema_models
|
||||||
|
from controllers.console import console_ns
|
||||||
from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required
|
from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required
|
||||||
from controllers.fastopenapi import console_router
|
|
||||||
from libs.login import current_account_with_tenant, login_required
|
from libs.login import current_account_with_tenant, login_required
|
||||||
from services.tag_service import TagService
|
from services.tag_service import TagService
|
||||||
|
|
||||||
|
dataset_tag_fields = {
|
||||||
|
"id": fields.String,
|
||||||
|
"name": fields.String,
|
||||||
|
"type": fields.String,
|
||||||
|
"binding_count": fields.String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_dataset_tag_fields(api_or_ns: Namespace):
|
||||||
|
return api_or_ns.model("DataSetTag", dataset_tag_fields)
|
||||||
|
|
||||||
|
|
||||||
class TagBasePayload(BaseModel):
|
class TagBasePayload(BaseModel):
|
||||||
name: str = Field(description="Tag name", min_length=1, max_length=50)
|
name: str = Field(description="Tag name", min_length=1, max_length=50)
|
||||||
@ -32,129 +45,115 @@ class TagListQueryParam(BaseModel):
|
|||||||
keyword: str | None = Field(None, description="Search keyword")
|
keyword: str | None = Field(None, description="Search keyword")
|
||||||
|
|
||||||
|
|
||||||
class TagResponse(BaseModel):
|
register_schema_models(
|
||||||
id: str = Field(description="Tag ID")
|
console_ns,
|
||||||
name: str = Field(description="Tag name")
|
TagBasePayload,
|
||||||
type: str = Field(description="Tag type")
|
TagBindingPayload,
|
||||||
binding_count: int = Field(description="Number of bindings")
|
TagBindingRemovePayload,
|
||||||
|
TagListQueryParam,
|
||||||
|
|
||||||
class TagBindingResult(BaseModel):
|
|
||||||
result: Literal["success"] = Field(description="Operation result", examples=["success"])
|
|
||||||
|
|
||||||
|
|
||||||
@console_router.get(
|
|
||||||
"/tags",
|
|
||||||
response_model=list[TagResponse],
|
|
||||||
tags=["console"],
|
|
||||||
)
|
)
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
def list_tags(query: TagListQueryParam) -> list[TagResponse]:
|
|
||||||
_, current_tenant_id = current_account_with_tenant()
|
|
||||||
tags = TagService.get_tags(query.type, current_tenant_id, query.keyword)
|
|
||||||
|
|
||||||
return [
|
|
||||||
TagResponse(
|
|
||||||
id=tag.id,
|
|
||||||
name=tag.name,
|
|
||||||
type=tag.type,
|
|
||||||
binding_count=int(tag.binding_count),
|
|
||||||
)
|
|
||||||
for tag in tags
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@console_router.post(
|
@console_ns.route("/tags")
|
||||||
"/tags",
|
class TagListApi(Resource):
|
||||||
response_model=TagResponse,
|
@setup_required
|
||||||
tags=["console"],
|
@login_required
|
||||||
)
|
@account_initialization_required
|
||||||
@setup_required
|
@console_ns.doc(
|
||||||
@login_required
|
params={"type": 'Tag type filter. Can be "knowledge" or "app".', "keyword": "Search keyword for tag name."}
|
||||||
@account_initialization_required
|
)
|
||||||
def create_tag(payload: TagBasePayload) -> TagResponse:
|
@marshal_with(dataset_tag_fields)
|
||||||
current_user, _ = current_account_with_tenant()
|
def get(self):
|
||||||
# The role of the current user in the tag table must be admin, owner, or editor
|
_, current_tenant_id = current_account_with_tenant()
|
||||||
if not (current_user.has_edit_permission or current_user.is_dataset_editor):
|
raw_args = request.args.to_dict()
|
||||||
raise Forbidden()
|
param = TagListQueryParam.model_validate(raw_args)
|
||||||
|
tags = TagService.get_tags(param.type, current_tenant_id, param.keyword)
|
||||||
|
|
||||||
tag = TagService.save_tags(payload.model_dump())
|
return tags, 200
|
||||||
|
|
||||||
return TagResponse(id=tag.id, name=tag.name, type=tag.type, binding_count=0)
|
@console_ns.expect(console_ns.models[TagBasePayload.__name__])
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
def post(self):
|
||||||
|
current_user, _ = current_account_with_tenant()
|
||||||
|
# The role of the current user in the ta table must be admin, owner, or editor
|
||||||
|
if not (current_user.has_edit_permission or current_user.is_dataset_editor):
|
||||||
|
raise Forbidden()
|
||||||
|
|
||||||
|
payload = TagBasePayload.model_validate(console_ns.payload or {})
|
||||||
|
tag = TagService.save_tags(payload.model_dump())
|
||||||
|
|
||||||
|
response = {"id": tag.id, "name": tag.name, "type": tag.type, "binding_count": 0}
|
||||||
|
|
||||||
|
return response, 200
|
||||||
|
|
||||||
|
|
||||||
@console_router.patch(
|
@console_ns.route("/tags/<uuid:tag_id>")
|
||||||
"/tags/<uuid:tag_id>",
|
class TagUpdateDeleteApi(Resource):
|
||||||
response_model=TagResponse,
|
@console_ns.expect(console_ns.models[TagBasePayload.__name__])
|
||||||
tags=["console"],
|
@setup_required
|
||||||
)
|
@login_required
|
||||||
@setup_required
|
@account_initialization_required
|
||||||
@login_required
|
def patch(self, tag_id):
|
||||||
@account_initialization_required
|
current_user, _ = current_account_with_tenant()
|
||||||
def update_tag(tag_id: UUID, payload: TagBasePayload) -> TagResponse:
|
tag_id = str(tag_id)
|
||||||
current_user, _ = current_account_with_tenant()
|
# The role of the current user in the ta table must be admin, owner, or editor
|
||||||
tag_id_str = str(tag_id)
|
if not (current_user.has_edit_permission or current_user.is_dataset_editor):
|
||||||
# The role of the current user in the ta table must be admin, owner, or editor
|
raise Forbidden()
|
||||||
if not (current_user.has_edit_permission or current_user.is_dataset_editor):
|
|
||||||
raise Forbidden()
|
|
||||||
|
|
||||||
tag = TagService.update_tags(payload.model_dump(), tag_id_str)
|
payload = TagBasePayload.model_validate(console_ns.payload or {})
|
||||||
|
tag = TagService.update_tags(payload.model_dump(), tag_id)
|
||||||
|
|
||||||
binding_count = TagService.get_tag_binding_count(tag_id_str)
|
binding_count = TagService.get_tag_binding_count(tag_id)
|
||||||
|
|
||||||
return TagResponse(id=tag.id, name=tag.name, type=tag.type, binding_count=binding_count)
|
response = {"id": tag.id, "name": tag.name, "type": tag.type, "binding_count": binding_count}
|
||||||
|
|
||||||
|
return response, 200
|
||||||
|
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@edit_permission_required
|
||||||
|
def delete(self, tag_id):
|
||||||
|
tag_id = str(tag_id)
|
||||||
|
|
||||||
|
TagService.delete_tag(tag_id)
|
||||||
|
|
||||||
|
return 204
|
||||||
|
|
||||||
|
|
||||||
@console_router.delete(
|
@console_ns.route("/tag-bindings/create")
|
||||||
"/tags/<uuid:tag_id>",
|
class TagBindingCreateApi(Resource):
|
||||||
tags=["console"],
|
@console_ns.expect(console_ns.models[TagBindingPayload.__name__])
|
||||||
status_code=204,
|
@setup_required
|
||||||
)
|
@login_required
|
||||||
@setup_required
|
@account_initialization_required
|
||||||
@login_required
|
def post(self):
|
||||||
@account_initialization_required
|
current_user, _ = current_account_with_tenant()
|
||||||
@edit_permission_required
|
# The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
|
||||||
def delete_tag(tag_id: UUID) -> None:
|
if not (current_user.has_edit_permission or current_user.is_dataset_editor):
|
||||||
tag_id_str = str(tag_id)
|
raise Forbidden()
|
||||||
|
|
||||||
TagService.delete_tag(tag_id_str)
|
payload = TagBindingPayload.model_validate(console_ns.payload or {})
|
||||||
|
TagService.save_tag_binding(payload.model_dump())
|
||||||
|
|
||||||
|
return {"result": "success"}, 200
|
||||||
|
|
||||||
|
|
||||||
@console_router.post(
|
@console_ns.route("/tag-bindings/remove")
|
||||||
"/tag-bindings/create",
|
class TagBindingDeleteApi(Resource):
|
||||||
response_model=TagBindingResult,
|
@console_ns.expect(console_ns.models[TagBindingRemovePayload.__name__])
|
||||||
tags=["console"],
|
@setup_required
|
||||||
)
|
@login_required
|
||||||
@setup_required
|
@account_initialization_required
|
||||||
@login_required
|
def post(self):
|
||||||
@account_initialization_required
|
current_user, _ = current_account_with_tenant()
|
||||||
def create_tag_binding(payload: TagBindingPayload) -> TagBindingResult:
|
# The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
|
||||||
current_user, _ = current_account_with_tenant()
|
if not (current_user.has_edit_permission or current_user.is_dataset_editor):
|
||||||
# The role of the current user in the tag table must be admin, owner, editor, or dataset_operator
|
raise Forbidden()
|
||||||
if not (current_user.has_edit_permission or current_user.is_dataset_editor):
|
|
||||||
raise Forbidden()
|
|
||||||
|
|
||||||
TagService.save_tag_binding(payload.model_dump())
|
payload = TagBindingRemovePayload.model_validate(console_ns.payload or {})
|
||||||
|
TagService.delete_tag_binding(payload.model_dump())
|
||||||
|
|
||||||
return TagBindingResult(result="success")
|
return {"result": "success"}, 200
|
||||||
|
|
||||||
|
|
||||||
@console_router.post(
|
|
||||||
"/tag-bindings/remove",
|
|
||||||
response_model=TagBindingResult,
|
|
||||||
tags=["console"],
|
|
||||||
)
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
def delete_tag_binding(payload: TagBindingRemovePayload) -> TagBindingResult:
|
|
||||||
current_user, _ = current_account_with_tenant()
|
|
||||||
# The role of the current user in the tag table must be admin, owner, editor, or dataset_operator
|
|
||||||
if not (current_user.has_edit_permission or current_user.is_dataset_editor):
|
|
||||||
raise Forbidden()
|
|
||||||
|
|
||||||
TagService.delete_tag_binding(payload.model_dump())
|
|
||||||
|
|
||||||
return TagBindingResult(result="success")
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ class TagService:
|
|||||||
escaped_keyword = escape_like_pattern(keyword)
|
escaped_keyword = escape_like_pattern(keyword)
|
||||||
query = query.where(sa.and_(Tag.name.ilike(f"%{escaped_keyword}%", escape="\\")))
|
query = query.where(sa.and_(Tag.name.ilike(f"%{escaped_keyword}%", escape="\\")))
|
||||||
query = query.group_by(Tag.id, Tag.type, Tag.name, Tag.created_at)
|
query = query.group_by(Tag.id, Tag.type, Tag.name, Tag.created_at)
|
||||||
results = query.order_by(Tag.created_at.desc()).all()
|
results: list = query.order_by(Tag.created_at.desc()).all()
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -1,222 +0,0 @@
|
|||||||
import builtins
|
|
||||||
import contextlib
|
|
||||||
import importlib
|
|
||||||
import sys
|
|
||||||
from types import SimpleNamespace
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from flask import Flask
|
|
||||||
from flask.views import MethodView
|
|
||||||
|
|
||||||
from extensions import ext_fastopenapi
|
|
||||||
from extensions.ext_database import db
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def app():
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.config["TESTING"] = True
|
|
||||||
app.config["SECRET_KEY"] = "test-secret"
|
|
||||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
|
|
||||||
|
|
||||||
db.init_app(app)
|
|
||||||
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def fix_method_view_issue(monkeypatch):
|
|
||||||
if not hasattr(builtins, "MethodView"):
|
|
||||||
monkeypatch.setattr(builtins, "MethodView", MethodView, raising=False)
|
|
||||||
|
|
||||||
|
|
||||||
def _create_isolated_router():
|
|
||||||
import controllers.fastopenapi
|
|
||||||
|
|
||||||
router_class = type(controllers.fastopenapi.console_router)
|
|
||||||
return router_class()
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def _patch_auth_and_router(temp_router):
|
|
||||||
def noop(func):
|
|
||||||
return func
|
|
||||||
|
|
||||||
default_user = MagicMock(has_edit_permission=True, is_dataset_editor=False)
|
|
||||||
|
|
||||||
with (
|
|
||||||
patch("controllers.fastopenapi.console_router", temp_router),
|
|
||||||
patch("extensions.ext_fastopenapi.console_router", temp_router),
|
|
||||||
patch("controllers.console.wraps.setup_required", side_effect=noop),
|
|
||||||
patch("libs.login.login_required", side_effect=noop),
|
|
||||||
patch("controllers.console.wraps.account_initialization_required", side_effect=noop),
|
|
||||||
patch("controllers.console.wraps.edit_permission_required", side_effect=noop),
|
|
||||||
patch("libs.login.current_account_with_tenant", return_value=(default_user, "tenant-id")),
|
|
||||||
patch("configs.dify_config.EDITION", "CLOUD"),
|
|
||||||
):
|
|
||||||
import extensions.ext_fastopenapi
|
|
||||||
|
|
||||||
importlib.reload(extensions.ext_fastopenapi)
|
|
||||||
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
def _force_reload_module(target_module: str, alias_module: str):
|
|
||||||
if target_module in sys.modules:
|
|
||||||
del sys.modules[target_module]
|
|
||||||
if alias_module in sys.modules:
|
|
||||||
del sys.modules[alias_module]
|
|
||||||
|
|
||||||
module = importlib.import_module(target_module)
|
|
||||||
sys.modules[alias_module] = sys.modules[target_module]
|
|
||||||
|
|
||||||
return module
|
|
||||||
|
|
||||||
|
|
||||||
def _dedupe_routes(router):
|
|
||||||
seen = set()
|
|
||||||
unique_routes = []
|
|
||||||
for path, method, endpoint in reversed(router.get_routes()):
|
|
||||||
key = (path, method, endpoint.__name__)
|
|
||||||
if key in seen:
|
|
||||||
continue
|
|
||||||
seen.add(key)
|
|
||||||
unique_routes.append((path, method, endpoint))
|
|
||||||
router._routes = list(reversed(unique_routes))
|
|
||||||
|
|
||||||
|
|
||||||
def _cleanup_modules(target_module: str, alias_module: str):
|
|
||||||
if target_module in sys.modules:
|
|
||||||
del sys.modules[target_module]
|
|
||||||
if alias_module in sys.modules:
|
|
||||||
del sys.modules[alias_module]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_tags_module_env():
|
|
||||||
target_module = "controllers.console.tag.tags"
|
|
||||||
alias_module = "api.controllers.console.tag.tags"
|
|
||||||
temp_router = _create_isolated_router()
|
|
||||||
|
|
||||||
try:
|
|
||||||
with _patch_auth_and_router(temp_router):
|
|
||||||
tags_module = _force_reload_module(target_module, alias_module)
|
|
||||||
_dedupe_routes(temp_router)
|
|
||||||
yield tags_module
|
|
||||||
finally:
|
|
||||||
_cleanup_modules(target_module, alias_module)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_tags_success(app: Flask, mock_tags_module_env):
|
|
||||||
# Arrange
|
|
||||||
tag = SimpleNamespace(id="tag-1", name="Alpha", type="app", binding_count=2)
|
|
||||||
with patch("controllers.console.tag.tags.TagService.get_tags", return_value=[tag]):
|
|
||||||
ext_fastopenapi.init_app(app)
|
|
||||||
client = app.test_client()
|
|
||||||
|
|
||||||
# Act
|
|
||||||
response = client.get("/console/api/tags?type=app&keyword=Alpha")
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.get_json() == [
|
|
||||||
{"id": "tag-1", "name": "Alpha", "type": "app", "binding_count": 2},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_tag_success(app: Flask, mock_tags_module_env):
|
|
||||||
# Arrange
|
|
||||||
tag = SimpleNamespace(id="tag-2", name="Beta", type="app")
|
|
||||||
with patch("controllers.console.tag.tags.TagService.save_tags", return_value=tag) as mock_save:
|
|
||||||
ext_fastopenapi.init_app(app)
|
|
||||||
client = app.test_client()
|
|
||||||
|
|
||||||
# Act
|
|
||||||
response = client.post("/console/api/tags", json={"name": "Beta", "type": "app"})
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.get_json() == {
|
|
||||||
"id": "tag-2",
|
|
||||||
"name": "Beta",
|
|
||||||
"type": "app",
|
|
||||||
"binding_count": 0,
|
|
||||||
}
|
|
||||||
mock_save.assert_called_once_with({"name": "Beta", "type": "app"})
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_tag_success(app: Flask, mock_tags_module_env):
|
|
||||||
# Arrange
|
|
||||||
tag = SimpleNamespace(id="tag-3", name="Gamma", type="app")
|
|
||||||
with (
|
|
||||||
patch("controllers.console.tag.tags.TagService.update_tags", return_value=tag) as mock_update,
|
|
||||||
patch("controllers.console.tag.tags.TagService.get_tag_binding_count", return_value=4),
|
|
||||||
):
|
|
||||||
ext_fastopenapi.init_app(app)
|
|
||||||
client = app.test_client()
|
|
||||||
|
|
||||||
# Act
|
|
||||||
response = client.patch(
|
|
||||||
"/console/api/tags/11111111-1111-1111-1111-111111111111",
|
|
||||||
json={"name": "Gamma", "type": "app"},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.get_json() == {
|
|
||||||
"id": "tag-3",
|
|
||||||
"name": "Gamma",
|
|
||||||
"type": "app",
|
|
||||||
"binding_count": 4,
|
|
||||||
}
|
|
||||||
mock_update.assert_called_once_with(
|
|
||||||
{"name": "Gamma", "type": "app"},
|
|
||||||
"11111111-1111-1111-1111-111111111111",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_tag_success(app: Flask, mock_tags_module_env):
|
|
||||||
# Arrange
|
|
||||||
with patch("controllers.console.tag.tags.TagService.delete_tag") as mock_delete:
|
|
||||||
ext_fastopenapi.init_app(app)
|
|
||||||
client = app.test_client()
|
|
||||||
|
|
||||||
# Act
|
|
||||||
response = client.delete("/console/api/tags/11111111-1111-1111-1111-111111111111")
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert response.status_code == 204
|
|
||||||
mock_delete.assert_called_once_with("11111111-1111-1111-1111-111111111111")
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_tag_binding_success(app: Flask, mock_tags_module_env):
|
|
||||||
# Arrange
|
|
||||||
payload = {"tag_ids": ["tag-1", "tag-2"], "target_id": "target-1", "type": "app"}
|
|
||||||
with patch("controllers.console.tag.tags.TagService.save_tag_binding") as mock_bind:
|
|
||||||
ext_fastopenapi.init_app(app)
|
|
||||||
client = app.test_client()
|
|
||||||
|
|
||||||
# Act
|
|
||||||
response = client.post("/console/api/tag-bindings/create", json=payload)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.get_json() == {"result": "success"}
|
|
||||||
mock_bind.assert_called_once_with(payload)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_tag_binding_success(app: Flask, mock_tags_module_env):
|
|
||||||
# Arrange
|
|
||||||
payload = {"tag_id": "tag-1", "target_id": "target-1", "type": "app"}
|
|
||||||
with patch("controllers.console.tag.tags.TagService.delete_tag_binding") as mock_unbind:
|
|
||||||
ext_fastopenapi.init_app(app)
|
|
||||||
client = app.test_client()
|
|
||||||
|
|
||||||
# Act
|
|
||||||
response = client.post("/console/api/tag-bindings/remove", json=payload)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.get_json() == {"result": "success"}
|
|
||||||
mock_unbind.assert_called_once_with(payload)
|
|
||||||
@ -3,8 +3,6 @@ import type { FC, ReactNode } from 'react'
|
|||||||
import type { SliceProps } from './type'
|
import type { SliceProps } from './type'
|
||||||
import { autoUpdate, flip, FloatingFocusManager, offset, shift, useDismiss, useFloating, useHover, useInteractions, useRole } from '@floating-ui/react'
|
import { autoUpdate, flip, FloatingFocusManager, offset, shift, useDismiss, useFloating, useHover, useInteractions, useRole } from '@floating-ui/react'
|
||||||
import { RiDeleteBinLine } from '@remixicon/react'
|
import { RiDeleteBinLine } from '@remixicon/react'
|
||||||
// @ts-expect-error no types available
|
|
||||||
import lineClamp from 'line-clamp'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||||
import { cn } from '@/utils/classnames'
|
import { cn } from '@/utils/classnames'
|
||||||
@ -58,12 +56,8 @@ export const EditSlice: FC<EditSliceProps> = (props) => {
|
|||||||
<>
|
<>
|
||||||
<SliceContainer
|
<SliceContainer
|
||||||
{...rest}
|
{...rest}
|
||||||
className={cn('mr-0 block', className)}
|
className={cn('mr-0 line-clamp-4 block', className)}
|
||||||
ref={(ref) => {
|
ref={refs.setReference}
|
||||||
refs.setReference(ref)
|
|
||||||
if (ref)
|
|
||||||
lineClamp(ref, 4)
|
|
||||||
}}
|
|
||||||
{...getReferenceProps()}
|
{...getReferenceProps()}
|
||||||
>
|
>
|
||||||
<SliceLabel
|
<SliceLabel
|
||||||
|
|||||||
@ -117,7 +117,6 @@
|
|||||||
"ky": "1.12.0",
|
"ky": "1.12.0",
|
||||||
"lamejs": "1.2.1",
|
"lamejs": "1.2.1",
|
||||||
"lexical": "0.38.2",
|
"lexical": "0.38.2",
|
||||||
"line-clamp": "1.0.0",
|
|
||||||
"mermaid": "11.11.0",
|
"mermaid": "11.11.0",
|
||||||
"mime": "4.1.0",
|
"mime": "4.1.0",
|
||||||
"mitt": "3.0.1",
|
"mitt": "3.0.1",
|
||||||
|
|||||||
8
web/pnpm-lock.yaml
generated
8
web/pnpm-lock.yaml
generated
@ -233,9 +233,6 @@ importers:
|
|||||||
lexical:
|
lexical:
|
||||||
specifier: 0.38.2
|
specifier: 0.38.2
|
||||||
version: 0.38.2
|
version: 0.38.2
|
||||||
line-clamp:
|
|
||||||
specifier: 1.0.0
|
|
||||||
version: 1.0.0
|
|
||||||
mermaid:
|
mermaid:
|
||||||
specifier: 11.11.0
|
specifier: 11.11.0
|
||||||
version: 11.11.0
|
version: 11.11.0
|
||||||
@ -5403,9 +5400,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
line-clamp@1.0.0:
|
|
||||||
resolution: {integrity: sha512-dCDlvMj572RIRBQ3x9aIX0DTdt2St1bMdpi64jVTAi5vqBck7wf+J97//+J7+pS80rFJaYa8HiyXCTp0flpnBA==}
|
|
||||||
|
|
||||||
lines-and-columns@1.2.4:
|
lines-and-columns@1.2.4:
|
||||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||||
|
|
||||||
@ -12913,8 +12907,6 @@ snapshots:
|
|||||||
|
|
||||||
lilconfig@3.1.3: {}
|
lilconfig@3.1.3: {}
|
||||||
|
|
||||||
line-clamp@1.0.0: {}
|
|
||||||
|
|
||||||
lines-and-columns@1.2.4: {}
|
lines-and-columns@1.2.4: {}
|
||||||
|
|
||||||
lint-staged@15.5.2:
|
lint-staged@15.5.2:
|
||||||
|
|||||||
Reference in New Issue
Block a user