- Новый проект
+
+ Новый проект
@if (publishedAt()) {
-
-
-
-
- {{ feedItem().name }}
+
diff --git a/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.scss b/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.scss
index 26c03819b..9dda78b6c 100644
--- a/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.scss
+++ b/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.scss
@@ -1,74 +1,11 @@
-.card {
- display: flex;
- flex-direction: column;
- gap: 18px;
- min-height: 100%;
- padding: 24px;
- color: var(--white);
- background: var(--accent);
- border-radius: var(--rounded-xl);
-
- &__meta {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- align-items: center;
- justify-content: space-between;
- }
-
- &__type {
- padding: 7px 12px;
- font-size: 12px;
- font-weight: 600;
- color: var(--accent);
- background: var(--white);
- border-radius: var(--rounded-xxl);
- }
-
- &__date {
- font-size: 12px;
- }
+@use "styles/feed-card";
- &__heading {
- display: flex;
- gap: 16px;
- align-items: center;
- min-width: 0;
- }
-
- &__title {
- font-size: 20px;
- font-weight: 700;
- line-height: 1.3;
- overflow-wrap: anywhere;
- }
-
- /* stylelint-disable value-no-vendor-prefix */
- &__description {
- display: -webkit-box;
- overflow: hidden;
- font-size: 14px;
- line-height: 1.5;
- overflow-wrap: anywhere;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 3;
- }
- /* stylelint-enable value-no-vendor-prefix */
-
- &__industry {
- align-self: flex-start;
- min-height: 32px;
- padding: 7px 14px;
- font-size: 14px;
- font-weight: 600;
- line-height: 18px;
- color: var(--accent);
- background: var(--white);
- border-radius: var(--rounded-xxl);
- }
+.card {
+ --feed-background: var(--accent);
+ --feed-text: var(--white);
+ --feed-secondary: var(--white);
+ --feed-cta-text: var(--accent-dark);
+ --feed-cta-background: var(--white);
- &__cta {
- align-self: flex-start;
- margin-top: auto;
- }
+ @include feed-card.layout;
}
diff --git a/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.spec.ts b/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.spec.ts
index 7c542ea77..16e9c260d 100644
--- a/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.spec.ts
+++ b/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.spec.ts
@@ -36,5 +36,7 @@ describe("NewProjectComponent", () => {
);
expect(element.querySelector(".card__industry")?.textContent?.trim()).toBe("Инженерия");
expect(element.querySelector(".card__cta")?.textContent).toContain("Перейти в проект");
+ expect(element.querySelector("a.card__cta")?.getAttribute("href")).toBe("/office/projects/5");
+ expect(element.querySelector("time")?.getAttribute("datetime")).toBe("2026-09-23T12:00:00Z");
});
});
diff --git a/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.ts b/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.ts
index e0c979b23..46646eef0 100644
--- a/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.ts
+++ b/projects/social_platform/src/app/ui/pages/feed/new-project/new-project.component.ts
@@ -7,12 +7,11 @@ import { FeedProject } from "@domain/feed/feed-item.model";
import { AppRoutes } from "@api/paths/app-routes";
import { IndustryRepositoryPort } from "@domain/industry/ports/industry.repository.port";
import { AvatarComponent } from "@ui/primitives/avatar/avatar.component";
-import { ButtonComponent } from "@ui/primitives";
/** Карточка нового проекта в общей ленте. */
@Component({
selector: "app-new-project",
- imports: [RouterLink, DayjsPipe, AvatarComponent, ButtonComponent],
+ imports: [RouterLink, DayjsPipe, AvatarComponent],
templateUrl: "./new-project.component.html",
styleUrl: "./new-project.component.scss",
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.html b/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.html
index 1a52a730f..c4ee6e578 100644
--- a/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.html
+++ b/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.html
@@ -1,55 +1,61 @@
-
-
{{ feedItem().name }}
+
+
+
-
- @if (feedItem().shortDescription) {
-
+ Проект
+ {{ feedItem().name }}
+
{{ feedItem().shortDescription }}
- } - - @if (industryRepository.getOne(feedItem().industry); as industry) { - {{ industry.name }} - } - -{{ feedItem().shortDescription }}
+
+ @if (industryRepository.getOne(feedItem().industry); as industry) {
+ {{ industry.name }}
+ }
+
+ Перейти в проект
- Перейти в проект
-
- Новая вакансия
+
+ Новая вакансия
@if (publishedAt()) {
-
+
{{ publishedAt() | dayjs: "format" : "DD.MM.YYYY" }}
}
-
- @if (feedItem().project; as project) {
-
-
-
- Проект / компания
- {{ project.name }}
+
+
+ @if (feedItem().project; as project) {
+
+
- }
-
-
+ Проект / компания
+ {{ project.name }}
- {{ feedItem().role }}
- - @if (feedItem().description) { - - } - - @if (feedItem().requiredSkills.length) { --
- @for (skill of feedItem().requiredSkills.slice(0, 5); track skill.id) {
-
- {{ skill.name }} + } +
{{ feedItem().role }}
+ +
+ @if (feedItem().requiredSkills.length) {
+
+ Подробнее о вакансии
- Подробнее о вакансии
-
diff --git a/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.scss b/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.scss
index 8c1d7409f..69b26fc81 100644
--- a/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.scss
+++ b/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.scss
@@ -1,108 +1,5 @@
-.card {
- display: flex;
- flex-direction: column;
- gap: 14px;
- min-height: 100%;
- padding: 24px;
- background: var(--light-white);
- border: 1px solid var(--medium-grey-for-outline);
- border-radius: var(--rounded-xl);
-
- &__meta {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- align-items: center;
- justify-content: space-between;
- }
-
- &__type {
- padding: 7px 12px;
- font-size: 12px;
- font-weight: 600;
- color: var(--accent);
- background: var(--white);
- border: 1px solid var(--accent);
- border-radius: var(--rounded-xxl);
- }
-
- &__date,
- &__eyebrow {
- font-size: 12px;
- color: var(--dark-grey);
- }
-
- &__source {
- display: flex;
- gap: 12px;
- align-items: center;
- min-width: 0;
- }
-
- &__source-text {
- display: flex;
- flex-direction: column;
- gap: 4px;
- min-width: 0;
-
- a {
- font-size: 14px;
- font-weight: 600;
- color: var(--black);
- overflow-wrap: anywhere;
- }
- }
+@use "styles/feed-card";
- &__title {
- font-size: 20px;
- font-weight: 700;
- line-height: 1.3;
- color: var(--black);
- overflow-wrap: anywhere;
- }
-
- /* stylelint-disable value-no-vendor-prefix */
- &__description {
- display: -webkit-box;
- overflow: hidden;
- font-size: 14px;
- line-height: 1.5;
- color: var(--grey-for-text);
- overflow-wrap: anywhere;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 3;
- }
- /* stylelint-enable value-no-vendor-prefix */
-
- &__skills {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- }
-
- &__skill {
- padding: 6px 10px;
- font-size: 12px;
- color: var(--accent);
- background: var(--white);
- border: 1px solid var(--medium-grey-for-outline);
- border-radius: var(--rounded-xxl);
- }
-
- &__industry {
- align-self: flex-start;
- min-height: 32px;
- padding: 7px 14px;
- font-size: 14px;
- font-weight: 600;
- line-height: 18px;
- color: var(--white);
- background: var(--green);
- border-radius: var(--rounded-xxl);
- }
-
- &__cta {
- align-self: flex-start;
- margin-top: auto;
- }
+.card {
+ @include feed-card.layout;
}
diff --git a/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.spec.ts b/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.spec.ts
index 04cac7959..8c147c26e 100644
--- a/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.spec.ts
+++ b/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.spec.ts
@@ -41,5 +41,25 @@ describe("OpenVacancyComponent", () => {
).toEqual(["Angular", "TypeScript"]);
expect(element.querySelector(".card__industry")?.textContent?.trim()).toBe("IT");
expect(element.querySelector(".card__cta")?.textContent).toContain("Подробнее о вакансии");
+ expect(element.querySelector("a.card__cta")?.getAttribute("href")).toBe("/office/vacancies/7");
+ expect(element.querySelector("time")?.getAttribute("datetime")).toBe("2026-09-23T12:00:00Z");
+
+ fixture.componentRef.setInput("feedItem", {
+ ...fixture.componentInstance.feedItem(),
+ requiredSkills: ["QA", "Postman", "API", "SQL", "Git", "Аналитика"].map((name, id) => ({
+ id,
+ name,
+ })),
+ });
+ fixture.detectChanges();
+ expect(
+ Array.from(element.querySelectorAll(".card__skills li")).map(node =>
+ node.textContent?.trim(),
+ ),
+ ).toEqual(["QA", "Postman", "API", "+3"]);
+ expect(element.querySelector(".card__skills li:last-child")?.getAttribute("aria-label")).toBe(
+ "Ещё навыков: 3",
+ );
+ expect(fixture.componentInstance.feedItem().requiredSkills).toHaveLength(6);
});
});
diff --git a/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.ts b/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.ts
index d276f6367..512e4bb9d 100644
--- a/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.ts
+++ b/projects/social_platform/src/app/ui/pages/feed/open-vacancy/open-vacancy.component.ts
@@ -7,19 +7,11 @@ import { Vacancy } from "@domain/vacancy/vacancy.model";
import { AppRoutes } from "@api/paths/app-routes";
import { IndustryRepositoryPort } from "@domain/industry/ports/industry.repository.port";
import { AvatarComponent } from "@ui/primitives/avatar/avatar.component";
-import { ButtonComponent } from "@ui/primitives";
/** Карточка вакансии в ленте: источник, роль, описание, навыки и переход к вакансии. */
@Component({
selector: "app-open-vacancy",
- imports: [
- RouterLink,
- DayjsPipe,
- ParseLinksPipe,
- ParseBreaksPipe,
- AvatarComponent,
- ButtonComponent,
- ],
+ imports: [RouterLink, DayjsPipe, ParseLinksPipe, ParseBreaksPipe, AvatarComponent],
templateUrl: "./open-vacancy.component.html",
styleUrl: "./open-vacancy.component.scss",
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.html b/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.html
index 2dfb9185a..9d6772d95 100644
--- a/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.html
+++ b/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.html
@@ -32,23 +32,26 @@
diff --git a/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.scss b/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.scss
index 8a503fce5..ac06a6af0 100644
--- a/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.scss
+++ b/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.scss
@@ -75,19 +75,24 @@
.filter {
display: grid;
- grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
+ grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 30px 12px;
padding-top: 25px;
+ @include responsive.apply-desktop {
+ grid-template-columns: repeat(6, minmax(0, 1fr));
+ }
+
&__option {
position: relative;
- display: flex;
- flex-direction: column;
+ display: grid;
+ grid-template-rows: 32px 18px;
+ row-gap: 4px;
align-items: center;
- justify-content: center;
+ justify-items: center;
min-width: 0;
- min-height: 88px;
- padding: 36px 10px 12px;
+ height: 104px;
+ padding: 36px 6px 12px;
cursor: pointer;
background-color: var(--light-white);
border: 0.5px solid var(--medium-grey-for-outline);
@@ -109,7 +114,7 @@
background-color: var(--accent);
}
- p {
+ .filter__title {
color: var(--accent);
}
}
@@ -133,17 +138,26 @@
}
}
- p {
- color: var(--grey-for-text);
- text-align: center;
+ &:focus-visible {
+ outline: 2px solid var(--accent-dark);
+ outline-offset: 3px;
}
}
+ // Одна двухстрочная область названия задаёт общую базовую линию всех счётчиков.
+ &__title {
+ font-size: 11px;
+ line-height: 16px;
+ color: var(--grey-for-text);
+ text-align: center;
+ }
+
&__count {
display: block;
- margin-top: 4px;
font-size: 14px;
font-weight: 600;
+ font-variant-numeric: tabular-nums;
+ line-height: 18px;
color: var(--accent);
}
diff --git a/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.spec.ts b/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.spec.ts
index 9f89fcca4..eecc17119 100644
--- a/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.spec.ts
+++ b/projects/social_platform/src/app/ui/widgets/feed-filter/feed-filter.component.spec.ts
@@ -43,5 +43,21 @@ describe("FeedComponent", () => {
expect(
options.map(option => option.querySelector(".filter__count")?.textContent?.trim()),
).toEqual(["12", "5", "3", "4", "0", "0"]);
+ for (const option of options) {
+ expect(option.tagName).toBe("BUTTON");
+ expect(Array.from(option.children).map(child => child.className)).toEqual([
+ "filter__option--icon",
+ "filter__title",
+ "filter__count",
+ ]);
+ expect(option.querySelector(".filter__option--icon")?.getAttribute("aria-hidden")).toBe(
+ "true",
+ );
+ }
+ expect((options[5] as HTMLButtonElement).disabled).toBe(true);
+ expect(options[0].getAttribute("aria-pressed")).toBe("true");
+ const setFilter = vi.spyOn(component, "setFilter");
+ options[1].click();
+ expect(setFilter).toHaveBeenCalledExactlyOnceWith("project");
});
});
diff --git a/projects/social_platform/src/app/ui/widgets/news-card/carousel/carousel.component.scss b/projects/social_platform/src/app/ui/widgets/news-card/carousel/carousel.component.scss
index 162f8bb53..cd3db50b1 100644
--- a/projects/social_platform/src/app/ui/widgets/news-card/carousel/carousel.component.scss
+++ b/projects/social_platform/src/app/ui/widgets/news-card/carousel/carousel.component.scss
@@ -6,6 +6,9 @@
align-items: center;
justify-content: center;
+ // Лента задаёт высоту preview; остальные страницы сохраняют естественный размер изображения.
+ height: var(--carousel-height, auto);
+
&__image-wrapper {
position: relative;
width: 100%;
@@ -15,6 +18,7 @@
img {
display: block;
width: 100%;
+ height: var(--carousel-height, auto);
border-radius: var(--rounded-xl);
object-fit: cover;
}
diff --git a/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.html b/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.html
index 2c7782c11..d346074b9 100644
--- a/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.html
+++ b/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.html
@@ -1,176 +1,301 @@
-
-
+ @for (skill of feedItem().requiredSkills.slice(0, 3); track skill.id) {
+
- + {{ skill.name }} + + } + @if (feedItem().requiredSkills.length > 3) { +
- + +{{ feedItem().requiredSkills.length - 3 }} + + } +
@for (filterItem of feedFilterOptions; track $index) {
-
-
+
}
+
-
- {{ filterItem.name }}
+ + {{ filterItem.name }} {{ categoryCount(filterItem.value) }} -
- @if (feedType()) {
-
-
- {{ feedType() === "people" ? "Новости людей" : "Новости проекта" }}
-
-
- {{ publishedAt() || feedItem().datetimeCreated | dayjs: "format" : "DD.MM.YYYY" }}
-
+@if (feedType() && !editMode) {
+
+
+ {{
+ feedType() === "people" ? "Новости людей" : "Новости проекта"
+ }}
+ {{ publishedAt() || feedItem().datetimeCreated | dayjs: "format" : "DD.MM.YYYY" }}
- }
-
-
+
-
-
{{ feedItem().name | truncate: 30 }}
- @if (!feedType()) {
-
- {{ feedItem().datetimeCreated | dayjs: "format" : "DD.MM.YYYY" }}
-
- }
+
+ {{ feedType() === "people" ? "Автор" : "Проект" }}
+ {{
+ feedItem().name
+ }}
-
- @if (isOwner()) {
-
-
-
+ @if (isOwner()) {
+
+
+
+ @if (menuOpen) {
+
- @if (menuOpen) {
- -
+
- + + +
- + + +
-
- @if (!editMode) {
-
- редактировать - } -
- удалить -
+ {{ feedText().headline }} +
+ +
+ @if (feedIndustryId(); as industryId) {
+ @if (industryRepository.getOne(industryId); as industry) {
+ {{ industry.name }}
}
+ }
+ @if (imagesViewList.length || filesViewList.length) {
+
+ {{
+ feedType() === "people" ? "Перейти в профиль" : "Перейти в проект"
+ }}
+
+} @else {
+
+ @if (imagesViewList.length) {
+
+ }
+ @if (filesViewList.length) {
+
+ }
+ -
+ @for (file of filesViewList; track $index) {
+
- + {{ + file.name + }} + + } +
+
+
+
+
+
+ {{ feedItem().viewsCount }}
+
+ @if (feedType()) {
+
- @if (feedItem().text) {
-
+
+ {{ feedType() === "people" ? "Новости людей" : "Новости проекта" }}
+
+
+ {{ publishedAt() || feedItem().datetimeCreated | dayjs: "format" : "DD.MM.YYYY" }}
+
}
-
- @if (!editMode) {
- @if (feedType()) {
-
- }
- @defer (when editMode) {
- {{ feedText().headline }}
- @if (feedText().summary) { +
+
+
+
+ @if (feedItem().text) {
+
+
+
+ @if (isOwner()) {
+ {{ feedItem().name | truncate: 30 }}
+ @if (!feedType()) {
+
+ {{ feedItem().datetimeCreated | dayjs: "format" : "DD.MM.YYYY" }}
+
+ }
+
+
+ }
+
+
+
+ @if (menuOpen) {
+ -
+ @if (!editMode) {
+
- редактировать + } +
- + удалить + +
+ @if (!editMode) {
+ @if (feedType()) {
+
+ }
}
- } @else {
- @if (editForm.get("text"); as text) {
-
+
+ }
+ @defer (when editMode) {
+ {{ feedText().headline }}
+ @if (feedText().summary) { + + } + } @else { } } @else { - + @if (editForm.get("text"); as text) { +-
+ @for (f of imagesEditList; track f.id) {
+
-
+ @for (f of filesEditList; track f.id) {
+
+ {{ readFullDescription() ? "скрыть" : "подробнее" }}
+
+ }
+ @if (!editMode) {
+
+
+
+ }
+ @if (!editMode && filesViewList.length) {
+
+ @for (f of filesViewList; track $index) {
+
+ }
+
+ }
+ @if (feedType() && !editMode) {
+ @if (feedIndustryId(); as industryId) {
+ @if (industryRepository.getOne(industryId); as industry) {
+ {{ industry.name }}
}
}
- -
- @for (f of imagesEditList; track f.id) {
-
-
- @for (f of filesEditList; track f.id) {
-
- {{ readFullDescription() ? "скрыть" : "подробнее" }}
-
- }
- @if (!editMode) {
-
-
-
- }
- @if (!editMode && filesViewList.length) {
-
- @for (f of filesViewList; track $index) {
-
- }
-
- }
- @if (feedType() && !editMode) {
- @if (feedIndustryId(); as industryId) {
- @if (industryRepository.getOne(industryId); as industry) {
- {{ industry.name }}
- }
+
+ {{ feedType() === "people" ? "Перейти в профиль" : "Перейти в проект" }}
+
}
-
- {{ feedType() === "people" ? "Перейти в профиль" : "Перейти в проект" }}
-
- }
- @if (!editMode) {
-
-
- ('button[aria-label="Нравится"]')!.click();
+ expect(emit).toHaveBeenCalledExactlyOnceWith(news.id);
+ expect(root.querySelector(".card__feed-files a")?.getAttribute("href")).toBe("/test.pdf");
+ expect(root.querySelector(".card__feed-files a")?.getAttribute("download")).toBe(
+ "Тестовый документ.pdf",
+ );
+ });
+
+ it("вне ленты сохраняет обычную карточку и действия владельца", () => {
+ fixture.componentRef.setInput("isOwner", true);
+ fixture.detectChanges();
+ const root: HTMLElement = fixture.nativeElement;
+ expect(root.querySelector(".card--feed")).toBeNull();
+ expect(root.querySelector(".feed-card__cta")).toBeNull();
+ expect(root.querySelector(".card__text")?.textContent).toContain(component.feedItem().text);
+ root.querySelector(".card__dots")!.click();
+ fixture.detectChanges();
+ expect(root.querySelector(".card__options")?.textContent).toContain("редактировать");
+
+ fixture.componentRef.setInput("feedType", "project");
+ component.editMode = true;
+ fixture.detectChanges();
+ expect(root.querySelector(".card--feed")).toBeNull();
+ expect(root.querySelector(".editor-footer")).not.toBeNull();
});
});
diff --git a/projects/social_platform/src/styles/_feed-card.scss b/projects/social_platform/src/styles/_feed-card.scss
new file mode 100644
index 000000000..fa8e278a3
--- /dev/null
+++ b/projects/social_platform/src/styles/_feed-card.scss
@@ -0,0 +1,188 @@
+// Общие строки удерживают CTA и заголовки на одном уровне даже при отсутствующих данных.
+// Миксин подключается только карточками ленты; detail/profile сохраняют свою геометрию.
+@mixin layout {
+ box-sizing: border-box;
+ display: grid;
+ grid-template-rows: 26px 44px 44px 54px minmax(0, 1fr) 36px;
+ gap: 10px;
+ width: 100%;
+ min-width: 0;
+ height: 420px;
+ padding: 20px;
+ color: var(--feed-text, var(--black));
+ background: var(--feed-background, var(--light-white));
+ border: 1px solid var(--medium-grey-for-outline);
+ border-radius: var(--rounded-xl);
+
+ > * {
+ min-width: 0;
+ min-height: 0;
+ }
+
+ a:focus-visible,
+ button:focus-visible {
+ outline: 2px solid var(--feed-text, var(--accent-dark));
+ outline-offset: 3px;
+ }
+
+ .feed-card__meta {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ justify-content: space-between;
+ }
+
+ .feed-card__type {
+ display: inline-flex;
+ align-items: center;
+ height: 26px;
+ padding: 0 10px;
+ overflow: hidden;
+ font-size: 11px;
+ font-weight: 600;
+ line-height: 16px;
+ color: var(--accent-dark);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ background: var(--accent-light);
+ border: 1px solid transparent;
+ border-radius: var(--rounded-xxl);
+ }
+
+ .feed-card__date {
+ flex-shrink: 0;
+ font-size: 10px;
+ font-variant-numeric: tabular-nums;
+ line-height: 14px;
+ color: var(--feed-secondary, var(--grey-for-text));
+ }
+
+ .feed-card__source {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ }
+
+ .feed-card__avatar {
+ flex: 0 0 40px;
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ object-fit: cover;
+ }
+
+ .feed-card__source-text {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ min-width: 0;
+ }
+
+ .feed-card__eyebrow {
+ font-size: 10px;
+ line-height: 14px;
+ color: var(--feed-secondary, var(--grey-for-text));
+ }
+
+ .feed-card__source-name {
+ overflow: hidden;
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 17px;
+ color: inherit;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ /* stylelint-disable value-no-vendor-prefix -- Line-clamp ограничивает текст, сохраняя полное значение в DOM. */
+ .feed-card__title,
+ .feed-card__description {
+ display: -webkit-box;
+ margin: 0;
+ overflow: hidden;
+ overflow-wrap: anywhere;
+ -webkit-box-orient: vertical;
+ }
+ /* stylelint-enable value-no-vendor-prefix */
+
+ .feed-card__title {
+ font-size: 18px;
+ font-weight: 600;
+ line-height: 22px;
+ -webkit-line-clamp: 2;
+ }
+
+ .feed-card__description {
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 18px;
+ color: var(--feed-secondary, var(--grey-for-text));
+ -webkit-line-clamp: 3;
+ }
+
+ .feed-card__metadata {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ align-items: flex-start;
+ min-width: 0;
+ }
+
+ .feed-card__skills {
+ display: flex;
+ gap: 6px;
+ width: 100%;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ }
+
+ .feed-card__chip {
+ box-sizing: border-box;
+ flex-shrink: 0;
+ max-width: 100%;
+ height: 24px;
+ padding: 4px 8px;
+ overflow: hidden;
+ font-size: 10px;
+ font-weight: 400;
+ line-height: 16px;
+ color: var(--accent-dark);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ background: var(--accent-light);
+ border-radius: var(--rounded-lg);
+
+ &--skill {
+ flex-shrink: 1;
+ max-width: calc((100% - 18px) / 4);
+ }
+
+ &--industry {
+ color: var(--green-dark);
+ background: var(--green-light);
+ }
+ }
+
+ .feed-card__cta {
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 36px;
+ padding: 0 12px;
+ font-size: 12px;
+ font-weight: 600;
+ line-height: 16px;
+ color: var(--feed-cta-text, var(--white));
+ text-align: center;
+ background: var(--feed-cta-background, var(--accent));
+ border: 1px solid transparent;
+ border-radius: var(--rounded-xxl);
+
+ &:hover {
+ filter: brightness(0.95);
+ }
+ }
+}
-
- {{ feedItem().likesCount > 0 ? feedItem().likesCount : "" }}
-
+ @if (!editMode) {
+
+
+}
diff --git a/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.scss b/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.scss
index 47579ee03..fd5013e68 100644
--- a/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.scss
+++ b/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.scss
@@ -1,3 +1,4 @@
+@use "styles/feed-card";
@use "styles/typography";
@use "styles/responsive";
@@ -7,71 +8,6 @@
border: 0.5px solid var(--medium-grey-for-outline);
border-radius: var(--rounded-lg);
- &--feed {
- padding: 24px;
- }
-
- &__feed-meta {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 16px;
- }
-
- &__feed-type {
- padding: 7px 12px;
- font-size: 12px;
- font-weight: 600;
- color: var(--accent);
- background: var(--white);
- border: 1px solid var(--accent);
- border-radius: var(--rounded-xxl);
- }
-
- &__feed-date {
- font-size: 12px;
- color: var(--dark-grey);
- }
-
- &__feed-headline {
- margin-bottom: 8px;
- font-size: 18px;
- font-weight: 700;
- line-height: 1.35;
- color: var(--black);
- overflow-wrap: anywhere;
- }
-
- &__feed-industry {
- display: inline-flex;
- align-items: center;
- min-height: 32px;
- padding: 7px 14px;
- margin-top: 12px;
- font-size: 14px;
- font-weight: 600;
- line-height: 18px;
- color: var(--white);
- background: var(--green);
- border-radius: var(--rounded-xxl);
- }
-
- &__feed-cta {
- display: flex;
- align-items: center;
- justify-content: center;
- width: fit-content;
- min-height: 36px;
- padding: 8px 16px;
- margin-top: 16px;
- font-size: 12px;
- color: var(--accent);
- border: 1px solid var(--accent);
- border-radius: var(--rounded-xxl);
- }
-
&__head {
display: flex;
align-items: center;
@@ -314,3 +250,69 @@
}
}
}
+
+// Ограничения применяются только к preview ленты: файлы и изображения не меняют высоту ряда.
+.card--feed {
+ @include feed-card.layout;
+
+ .feed-card__source-text { flex: 1; }
+
+ .card__feed-extras {
+ gap: 6px;
+ align-items: stretch;
+ }
+
+ .card__feed-industry { align-self: flex-start; }
+
+ .card__feed-media {
+ display: flex;
+ flex: 1;
+ gap: 8px;
+ min-height: 0;
+ }
+
+ app-carousel {
+ --carousel-height: 100%;
+
+ display: block;
+ flex: 1;
+ min-width: 0;
+ min-height: 0;
+ }
+
+ .card__feed-files {
+ flex: 1;
+ min-width: 0;
+ padding: 3px;
+ margin: 0;
+ overflow: hidden auto;
+ list-style: none;
+
+ a {
+ display: block;
+ padding: 4px 0;
+ overflow: hidden;
+ font-size: 11px;
+ line-height: 16px;
+ color: var(--accent-dark);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
+
+ .card__feed-actions {
+ flex: 0 0 22px;
+ margin-top: auto;
+ font-size: 11px;
+ }
+
+ button {
+ padding: 0;
+ font: inherit;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+ }
+
+ .card__option { padding: 5px 20px; }
+}
diff --git a/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.spec.ts b/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.spec.ts
index f0866ca0d..6bb8d1ca2 100644
--- a/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.spec.ts
+++ b/projects/social_platform/src/app/ui/widgets/news-card/news-card.component.spec.ts
@@ -74,5 +74,56 @@ describe("NewsCardComponent", () => {
const element = fixture.nativeElement as HTMLElement;
expect(element.querySelector(".card__feed-type")?.textContent?.trim()).toBe("Новости людей");
expect(element.querySelector(".card__feed-cta")?.textContent?.trim()).toBe("Перейти в профиль");
+ expect(element.querySelector(".card__feed-cta")?.getAttribute("href")).toBe(
+ "/office/profile/42",
+ );
+ });
+
+ it("сохраняет лайк, ссылку на скачивание и полное имя источника в компактной ленте", () => {
+ const name = "Очень длинное название проекта ".repeat(8);
+ const news = {
+ ...FeedNews.default(),
+ name,
+ text: "Заголовок новости. Подробности новости",
+ files: [],
+ };
+ fixture.componentRef.setInput("feedItem", news);
+ fixture.componentRef.setInput("feedType", "project");
+ component.filesViewList = [
+ { link: "/test.pdf", name: "Тестовый документ.pdf" },
+ ] as typeof component.filesViewList;
+ fixture.detectChanges();
+ const root: HTMLElement = fixture.nativeElement;
+ expect(root.querySelector(".feed-card__source-name")?.textContent).toBe(name);
+ expect(root.querySelector(".feed-card__source-name")?.getAttribute("title")).toBe(name);
+ expect(root.querySelector(".feed-card__title")?.textContent?.trim()).toBe("Заголовок новости.");
+ expect(root.querySelector(".feed-card__description")?.textContent?.trim()).toBe(
+ "Подробности новости",
+ );
+ const emit = vi.spyOn(component.like, "emit");
+ root.querySelector
+
-
+
+ {{ feedItem().likesCount > 0 ? feedItem().likesCount : "" }}
+
-
-
-
+
-
+
+
-
- } @else {
-
-
- {{ feedItem().viewsCount }}
+
-
+
+
+ {{ feedItem().viewsCount }}
+
-
-
+ }
+
- отмена
- сохранить
+ } @else {
+
- }
-
+
+
-
+ отмена
+ сохранить
+