Seo çalışmaları

This commit is contained in:
Sedat ÖZTÜRK 2026-09-22 23:59:36 +03:00
parent 8beeaeb8d5
commit 368756f0ac
152 changed files with 32173 additions and 29306 deletions

View file

@ -60,7 +60,8 @@ System nature:
Anything beyond that — a new React component or page, a new toolbox family, core engine changes (ListForm
runtime, seeders, code generator, auth, DataSourceManager, dispatcher/compiler), new ABP
entity/module/migration, platform table schema changes, new NuGet/npm dependencies, the
`App.Menus.Routes.json` seed data, new external integrations — is **out of frame**: finish everything that
`App.Menus.Routes.json` seed data (except the `Seo*` columns of an existing row, which are part
of ordinary production — lowcode §0.9), new external integrations — is **out of frame**: finish everything that
fits inside the frame first, then ask the user with a single question (why configuration is
insufficient, the smallest change that works, files touched, rollback) and proceed **only with
explicit approval**. Approval is per request and does not carry over. Full lists:
@ -723,6 +724,8 @@ From the `configs/seeds/host/data/App.Menus.*.json` patterns, AI must define bot
- `App.Menus.Routes.json` row:
- `Id`, `Key`, `Path`, `ComponentPath`, `RouteType`, `Authority`
- for a visitor-facing route, also its `Seo*` columns — there is no separate SEO table
(lowcode §0.9)
- `App.Menus.Menu.json` row:
- `ParentCode`, `Code`, `DisplayName`, `Url`, `Icon`, `RequiredPermissionName`, `Order`

View file

@ -28,7 +28,7 @@ This document summarizes the rules, standards, and step-by-step instructions for
## Special File Handling Rules
- **configs/seeds/host/data/App.Menus.*.json:**
- Never modify `App.Menus.Routes.json` directly. Only `App.Menus.Module.json` and `App.Menus.Menu.json` can be changed for menu additions or updates. All menu additions must use the platform's configuration mechanisms or be added to those two files as required by platform design.
- Never add, remove or re-path a route in `App.Menus.Routes.json`. The only field set you may edit on an existing row is its `Seo*` columns (lowcode §0.9). Only `App.Menus.Module.json` and `App.Menus.Menu.json` can be changed for menu additions or updates. All menu additions must use the platform's configuration mechanisms or be added to those two files as required by platform design.
- **LanguagesData.json:**
- When adding a new key, always check if the key already exists. Only add the key if it does not exist to prevent duplicates.

View file

@ -1304,6 +1304,10 @@ yazma, oradan yeni bir Custom Component üret. `Services` sayfası yoktur.
| `/demo` | `views/public/Demo` | `Sas_H_Demo` |
| `/access-denied` | `views/AccessDenied` | — |
Ziyaretçiye açık her yolun SEO alanları ayrı bir tabloda değil, yolu tanımlayan kaydın
üzerindedir: `Route` satırının ya da Custom Component'in `Seo*` kolonları. Listeye yeni bir public
yol eklediğinde bu alanları da doldur (lowcode §0.9).
**Kimlik akışı** (`routeType: authenticated`): `/login`, `/register`, `/forgot-password`,
`/reset-password`, `/confirm`, `/confirm/:userId/:token`, `/extend-login` →
`views/auth/*`. Bunlara dokunma; parola ve doğrulama akışları ABP tarafındadır.

View file

@ -53,9 +53,9 @@ configs/seeds/host/
├── postgres/object|execute/… ← aynısının PostgreSQL diyalekti (hedef PG ise)
├── crud/{EntityName}.json ← tablonun REST uçları
├── wizard/{WizardName}.json ← ekran + menü + yetki + dil anahtarları
└── data/{ListFormCode}.json ← ekrandan girilen liste verisinin aynası (opsiyonel)
Custom Component'ler de burada yaşar:
data/App.DeveloperKit.CustomComponents/{Id}.json
├── data/{ListFormCode}.json ← ekrandan girilen liste verisinin aynası (opsiyonel)
│ Custom Component'ler de burada yaşar:
│ data/App.DeveloperKit.CustomComponents/{Id}.json
```
Tenant kapsamı isteniyorsa `host/` yerine `tenants/{tenantId}/`; iç düzen aynıdır.
@ -70,6 +70,7 @@ Tenant kapsamı isteniyorsa `host/` yerine `tenants/{tenantId}/`; iç düzen ayn
| 4 | Wizard | `wizard/{Ad}.json` | Menü, yetki, dil anahtarı ve ListForm'u üretir |
| 5 | Alt ekranlar (varsa) | ek `wizard/…` dosyaları | Ana wizard'dan **önceki** `SeededAt` damgasını alır |
| 6 | Liste verisi (isteniyorsa) | `data/{ListFormCode}.json` | Ekran ve tablo hazır olmalı; `ListForm.SeedFilePath` bu yolu gösterir |
| 7 | SEO alanları (public yol ürettiysen) | Yolu tanımlayan satırın kendisi: `data/App.Menus.Routes.json` ya da `data/App.DeveloperKit.CustomComponents/{Id}.json` | Yolu üreten dosya yazıldıktan sonra; ziyaretçiye açık hiçbir yol metasız kalmaz (§0.9) |
**`Wizard.SeededAt` bir bağımlılık aracıdır.** Dosya adı tarih taşımaz (`{WizardName}.json`);
`WizardDataSeeder` `wizard/` klasörünü **`Wizard.SeededAt` damgasına göre sıralı** işler (eşitlikte
@ -394,7 +395,7 @@ kullanıcıya sorulur ve yalnızca onayla yapılır.** "Yapılamaz" cevabı yokt
| Alan | Örnek |
| --- | --- |
| Seed artefaktları | Wizard, custom component (visual/code modu), `crud/`, SQL nesneleri (tablo/view/SP/fonksiyon), Custom Endpoint, Dynamic Service, BackgroundWorker, bildirim türü/kuralı, dil anahtarı, yetki kaydı |
| Seed artefaktları | Wizard, custom component (visual/code modu), `crud/`, SQL nesneleri (tablo/view/SP/fonksiyon), Custom Endpoint, Dynamic Service, BackgroundWorker, bildirim türü/kuralı, dil anahtarı, yetki kaydı, **SEO kaydı** (§0.9) |
| Mevcut bileşeni genişletme | Kullanılan bir `ui` bileşenine **eksik olay / prop / küçük yetenek** ekleme (§7.3 notu) — komponentin %70'i hazır araçlarla kuruluyorsa kalanı bu eklemelerle tamamlanır; `componentProps.json` girdisini tamamlama |
| Mevcut sözlüğü genişletme | Yeni EditorScript tarifi (`scriptRecipes.ts` ↔ `Domain.Shared/Editors` birlikte), yeni `EditorOptions` anahtarı, yeni `DefaultValue` token'ı — **geriye dönük uyumlu** olmak şartıyla |
| Tasarımcı sözleşmesi | Var olan düğüme prop/varsayılan ekleme, yeni Script Builder tarifi |
@ -407,7 +408,7 @@ kullanıcıya sorulur ve yalnızca onayla yapılır.** "Yapılamaz" cevabı yokt
| Çekirdek motor | ListForm runtime, seeder'lar, `codeGenerator.ts`, yetki/kimlik altyapısı, `DataSourceManager`, dispatcher/derleyici (`CustomEndpointAppService`, `DynamicServiceCompiler`) |
| Backend şeması | Yeni ABP modülü/entity/migration, platform tablolarında (`{Önek}_{H\|T\|B}_{Ad}` — reference §12.1) kolon değişikliği, `TableNameEnum`/`TableNameResolver` değişikliği, DTO/JSON kolon sözleşmesini **bozan** değişiklik |
| Bağımlılık | Yeni NuGet/npm paketi, sürüm yükseltme |
| Sabit yapılar | `data/App.Menus.Routes.json`, `PlatformConsts` içindeki mevcut değerlerin değişmesi |
| Sabit yapılar | `data/App.Menus.Routes.json` satırlarının eklenmesi/silinmesi/yolunun değişmesi (`Seo*` alanları hariç, §0.9), `PlatformConsts` içindeki mevcut değerlerin değişmesi |
| Dış dünya | Yeni harici entegrasyon, yeni bağlantı tipi, üçüncü taraf servis |
**Sorma biçimi:** çerçeve içinde yapılabilen her şeyi **önce bitir**, sonra tek soruda
@ -416,6 +417,73 @@ hangi değişiklik yeter, hangi dosyalara dokunacak, geri alma nasıl olacak. Se
daima "çerçeve içinde daraltılmış çözüm" alternatifi de bulunsun. Onay gelmeden `api/` ya da
`ui/` altında çerçeve dışı bir satır yazılmaz; onay bir talebe özeldir, sonraki talebe taşınmaz.
---
### 0.9 SEO alanları — ziyaretçiye açık her yol için zorunlu
Ziyaretçiye açık bir yol üreten her iş, o yolun **SEO alanlarını da** doldurur. Bu kullanıcıdan
istenen bir bilgi değildir, üretimin parçasıdır: kullanıcı SEO bilgisi girmez, sen yazarsın.
Metasız yayınlanan bir sayfa arama sonucunda başlıksız ve açıklamasız görünür; paylaşıldığında
da boş bir kart çıkar.
**Ayrı bir SEO tablosu yoktur.** Meta, yolu tanımlayan kaydın üzerinde yaşar. Bu platformda rota
iki yerden doğar, ikisinde de aynı alan kümesi vardır:
| Yolu tanımlayan | Seed dosyası | Örnek yollar |
| --- | --- | --- |
| `Route` satırı (fiziksel React sayfası) | `data/App.Menus.Routes.json` | `/products`, `/blog`, `/demo`, `/checkout` |
| Custom Component `RoutePath` | `data/App.DeveloperKit.CustomComponents/{Id}.json` | `/home`, `/about`, `/contact` |
Aynı yolu ikisi de tanımlıyorsa **bileşen kazanır** — çalışma zamanında da bileşen fiziksel
rotayı gölgeler (`SeoAppService.CollectSeoRoutesAsync`). `/admin/` ile başlayan hiçbir yol için
alan doldurulmaz; yönetim yüzeyi zaten `noindex` gelir.
**Ne zaman:** public bir `RoutePath` taşıyan Custom Component, `Pub` modülüne eklenen bir menü,
ya da ziyaretçiye açık yeni bir fiziksel sayfa ürettiğinde.
```jsonc
// data/App.Menus.Routes.json ya da data/App.DeveloperKit.CustomComponents/{Id}.json
// içindeki satırın üzerine, var olan alanların yanına:
{
"Path": "/kurslar", // Custom Component'te: "RoutePath"
"SeoTitle": "::App.PublicSeo.Courses.Title", // dil anahtarı ya da düz metin
"SeoDescription": "::App.PublicSeo.Courses.Description",
"SeoKeywords": "::App.PublicSeo.Courses.Keywords",
"SeoOgImage": null, // boşsa ayarlardaki varsayılan paylaşım görseli
"SeoCanonicalUrl": null, // boşsa site adresi + yol
"SeoNoIndex": false,
"SeoIncludeInSitemap": true,
"SeoChangeFrequency": "weekly",
"SeoPriority": 0.7,
"SeoStructuredData": null // ham JSON-LD; sayfaya olduğu gibi eklenir
}
```
**Kurallar:**
| Kural | Neden |
| --- | --- |
| Metin alanları **dil anahtarı** taşır (`::App.PublicSeo.{Sayfa}.{Alan}`), anahtarlar `LanguagesData.json`'a EN + TR yazılır | Kayıt kültür başına çoğalmaz; çeviri dil dosyasında, tek yerde durur (standing default 4) |
| `Title` 60, `Description` 160 karakteri aşmaz | Arama sonucunda kırpılır |
| Metni uydurma — sayfanın kendi içeriğinden türet | Sayfada yazmayan bir vaat meta'da da yazmamalı |
| Kişiye özel / tek kullanımlık akış (`/checkout`, `/payment`, `/success`) → `SeoNoIndex: true`, `SeoIncludeInSitemap: false` | İçeriği olmayan, tekrar ziyaret edilemeyen adres |
| Parametreli rota (`/blog/:id`) alan taşımaz | Bir adres değil kalıptır; tek tek adresler sitemap'e veriden üretilir (`SeoAppService`) |
| Yönlendiren adres → `SeoCanonicalUrl` hedefi gösterir, `SeoIncludeInSitemap: false` | Aynı içerik iki adreste görünmesin |
| **Her satır** `SeoNoIndex` ve `SeoIncludeInSitemap` taşır — ziyaretçiye açık olmayanlar da (`true`/`false`) | Alanlar ekranın alan listesinde; `NOT NULL` bit kolonu satırda yoksa insert düşer. Diğer non-nullable alanlarla (`IsActive`, `IsDisabled`) aynı kural |
**Genel varsayılanlar kayıtta değil ayardadır:** site adresi, site adı, varsayılan başlık ve
açıklama, paylaşım görseli, Twitter hesabı, diller, robots yönergesi, Google doğrulaması,
sosyal hesaplar ve sitemap anahtarı `App.SiteManagement.Seo.*` ayarlarındadır
(`configs/seeds/host/data/App.SettingDefinitions.json`). Bir değer bütün site için geçerliyse
oraya yazılır, her yola tekrarlanmaz. Site kökü (`/`) hiçbir kayıt taşımaz; doğrudan bu
varsayılanlardan beslenir.
**Akış:** ayarlar + iki kaynaktan toplanan yol kayıtları uygulama konfigürasyonunun `seo`
alanıyla ziyaretçiye taşınır (`PlatformApplicationConfigurationContributor`), istemcide
`SeoHead` sayfanın başlığını, açıklamasını, kanonik adresini, `og:`/`twitter:` kartını ve
JSON-LD belgelerini kurar; `::` ile başlayan değerler orada çevrilir (`resolveSeoText`).
`sitemap.xml` ve `robots.txt` aynı kaynaktan `SeoController` tarafından üretilir.
## 1. Hangi istek → hangi artefakt
| İstek | Üretilecek artefakt | Bölüm |

View file

@ -1067,11 +1067,53 @@ hâlde ikinci rotasız bileşen birincisiyle çakışırdı.
`/home`, `/about`, `/contact` bu yolla çalışır (`PublicHomePage`, `PublicAboutPage`,
`PublicContactPage`); üst ve alt bilgi rotasızdır (`PublicHeader`, `PublicFooter`).
Yalnızca ikon taşıyan bir bağlantı ya da düğmenin ekran okuyucuda adı olmaz; `a` ve `button`
düğümleri bunun için `aria-label` özelliği taşır (`componentDefinitions.ts`). Görsel düğümüne
`width`/`height` yazmak da tarayıcının yeri önceden ayırmasını sağlar ve yerleşim kaymasını
(CLS) önler — gösterim ölçüsü `className`'den gelmeye devam eder.
Veriden beslenen public sayfalar (`/products`, `/checkout`, `/payment`, `/success`, `/blog`,
`/blog/:id`, `/demo`) fiziksel kalır; içerik sayfaları Custom Component'tir. Blog listesi ve detayı
`PublicAppService`'in `post-list` / `post-by-slug` uçlarından beslenir, yönetimi
`App.BlogManagement.*` ekranlarındadır.
**SEO.** Ziyaretçiye açık yüzeyin arama motoru sözleşmesi üç parçadır:
| Parça | Nerede | Ne taşır |
| --- | --- | --- |
| Genel varsayılanlar | `App.SiteManagement.Seo.*` ayarları (Ayarlar ekranı, yetki `App.SiteManagement.Seo`) | Site adresi, site adı, varsayılan başlık/açıklama/paylaşım görseli, Twitter hesabı, diller, robots yönergesi, Google doğrulaması, sosyal hesaplar, sitemap anahtarı |
| Yol bazlı alanlar | Yolu tanımlayan kaydın `Seo*` kolonları: `Route` (`Sas_H_Route`, ekran **Menü Yönetimi › Route**) ve `CustomComponent` (`Sas_H_CustomComponent`, ekran **DeveloperKit › Custom Components**) | Yolun başlığı, açıklaması, anahtar kelimeleri, paylaşım görseli, kanonik adresi, `SeoNoIndex`, sitemap davranışı ve ek JSON-LD |
| İstemci | `components/shared/SeoHead.tsx` | `title`, `description`, `robots`, `canonical`, `og:`/`twitter:` kartı, `og:locale:alternate` ve JSON-LD |
Ayrı bir SEO tablosu yoktur: meta, yolu tanımlayan kaydın üzerinde yaşar. `SeoAppService`
iki kaynağı tek sözleşmede birleştirir — `RouteType = public` olan `Route` satırları ve rota
taşıyan aktif Custom Component'ler. Aynı yolu ikisi de tanımlıyorsa bileşen kazanır; çalışma
zamanında da bileşen fiziksel rotayı gölgeler.
Alanlar ve ayarlar uygulama konfigürasyonunun `seo` alanıyla ziyaretçiye taşınır
(`PlatformApplicationConfigurationContributor` → `SeoAppService.GetConfigAsync`); sayfa ayrı bir
istek atmaz, meta ilk boyamada hazırdır. Yol çözümü daraldıkça öncelik kazanır: birebir yol, sonra
en uzun joker (`/blog/*`). Metin alanları kültür başına çoğalmaz: `::Anahtar` biçimindeki değer
istemcide çevrilir (`resolveSeoText`), düz metin olduğu gibi kullanılır.
Sayfanın kendi `SeoHead`'i, kabuğun (`PublicLayout`) `fallback` kaydını her zaman yener —
`PageTitle` ile aynı çözümleme.
`sitemap.xml` ve `robots.txt` aynı servisten üretilir (`SeoController`, anonim). Sitemap'in
kaynakları: `Pub` menüleri, aktif custom component rotaları, yayınlanmış blog yazıları ve
yolu tanımlayan kayıtların `Seo*` alanları; `/admin` yolları, parametreli rotalar (`/blog/:id`) ve
`SeoNoIndex` işaretli yollar elenir. Arama motorları bu iki dosyayı **sitenin** kök dizininde aradığı için UI sunucusu her iki
yolu da API'ye geçirir (`vite.config.ts` > `seoProxy`, hem `server` hem `preview`); ayrı bir ters
vekil kuralı gerekmez. `VITE_API_URL` tanımlı değilse proxy kurulmaz ve `ui/public/robots.txt`
içindeki statik varsayılan geçerli kalır.
Yönetim ve oturum yüzeyi `noindex` gelir (`Layouts.tsx`), satın alma akışı (`/checkout`,
`/payment`, `/success`) kendi sayfasında `noIndex` bildirir.
**Ziyaretçiye açık yeni bir yol üreten her iş SEO alanlarını da doldurur**; iki ekranın da
`SeedSync*` bayrakları açık olduğu için ekrandan girilen değer `data/App.Menus.Routes.json` ya da
`data/App.DeveloperKit.CustomComponents/{Id}.json` dosyasına düşer.
Ayrıntı: `.github/instructions/lowcode.instructions.md` §0.9.
Eski statik içerik sayfaları ve sayfa içi tasarım modu (`?design=1`) kaldırılmıştır:
`views/public/Home|About|Contact|Services`, `views/public/designer/`, bunlara özel istemci
servisleri; sunucuda `Home`/`About`/`Contact`/`Service` entity'leri, `PublicAppService` sayfa

View file

@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace Sozsoft.Platform.Public;
/// <summary>
/// Public sitenin arama motoru yuzeyi. Tamami anonim erisime aciktir: sozlesme uygulama
/// konfigurasyonuyla ziyaretciye tasinir, sitemap ve robots metni ise <c>SeoController</c>
/// uzerinden ham metin olarak yayinlanir.
/// </summary>
public interface ISeoAppService : IApplicationService
{
Task<SeoConfigDto> GetConfigAsync();
/// <param name="baseUrl">Ayarda site adresi bos birakildiginda kullanilan istek adresi.</param>
Task<string> GetSitemapAsync(string baseUrl);
Task<string> GetRobotsAsync(string baseUrl);
}

View file

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
namespace Sozsoft.Platform.Public;
/// <summary>
/// Ziyaretciye acik SEO sozlesmesi. Istemci her sayfada bu sozlesmeden kendi metasini kurar:
/// once yola karsilik gelen <see cref="Routes"/> kaydi, yoksa buradaki varsayilanlar.
/// </summary>
public class SeoConfigDto
{
/// <summary>Kanonik kok adres; bos ise istemci tarayicidaki adresi kullanir.</summary>
public string SiteUrl { get; set; } = string.Empty;
public string SiteName { get; set; } = string.Empty;
public string DefaultTitle { get; set; } = string.Empty;
public string DefaultDescription { get; set; } = string.Empty;
public string DefaultKeywords { get; set; } = string.Empty;
public string DefaultImage { get; set; } = string.Empty;
public string TwitterSite { get; set; } = string.Empty;
/// <summary>hreflang baglantilarinin uretildigi kultur listesi.</summary>
public List<string> Locales { get; set; } = [];
/// <summary>Varsayilan robots yonergesi (<c>index, follow</c>).</summary>
public string Robots { get; set; } = string.Empty;
public string GoogleSiteVerification { get; set; } = string.Empty;
/// <summary>Organization yapisal verisinin <c>sameAs</c> alani.</summary>
public List<string> SameAs { get; set; } = [];
public List<SeoRouteDto> Routes { get; set; } = [];
}
/// <summary>
/// Tek bir yolun SEO sozlesmesi. Ayri bir SEO tablosu yoktur: kayit ya <c>Route</c>
/// satirindan ya da rota tasiyan bir Custom Component'ten gelir.
/// </summary>
public class SeoRouteDto
{
/// <summary>Uygulama yolu; <c>/blog/*</c> bicimi alt agacin tamamini kapsar.</summary>
public string RoutePath { get; set; } = string.Empty;
/// <summary>Dogrudan metin ya da <c>::Anahtar</c> bicimiyle dil anahtari; cozumlemeyi istemci yapar.</summary>
public string? Title { get; set; }
public string? Description { get; set; }
public string? Keywords { get; set; }
public string? OgImage { get; set; }
public string? CanonicalUrl { get; set; }
public bool NoIndex { get; set; }
public string? StructuredData { get; set; }
}

View file

@ -0,0 +1,403 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Sozsoft.Platform.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Settings;
using SeoSettings = Sozsoft.Platform.PlatformConsts.AbpSettings.SiteManagement.Seo;
namespace Sozsoft.Platform.Public;
/// <summary>
/// Public sitenin arama motoru yuzeyi: sayfa metasinin sozlesmesi, sitemap ve robots metni.
/// Tamami anonim erisime aciktir.
/// </summary>
[AllowAnonymous]
public class SeoAppService : PlatformAppService, ISeoAppService
{
/// <summary>Sitemap'e yalnizca ziyaretciye acik yollar girer; yonetim yuzeyi disaridadir.</summary>
private const string AdminPathPrefix = "/admin";
private const string DefaultChangeFrequency = "weekly";
/// <summary>Route tablosunda ziyaretciye acik satirlarin tipi.</summary>
private const string PublicRouteType = "public";
private readonly ISettingProvider _settingProvider;
private readonly IRepository<Menu, Guid> _menuRepository;
private readonly IRepository<CustomComponent, Guid> _customComponentRepository;
private readonly IRepository<BlogPost, Guid> _postRepository;
private readonly IRepository<Route, Guid> _routeRepository;
public SeoAppService(
ISettingProvider settingProvider,
IRepository<Menu, Guid> menuRepository,
IRepository<CustomComponent, Guid> customComponentRepository,
IRepository<BlogPost, Guid> postRepository,
IRepository<Route, Guid> routeRepository)
{
_settingProvider = settingProvider;
_menuRepository = menuRepository;
_customComponentRepository = customComponentRepository;
_postRepository = postRepository;
_routeRepository = routeRepository;
}
public async Task<SeoConfigDto> GetConfigAsync()
{
var config = new SeoConfigDto
{
SiteUrl = NormalizeBaseUrl(await GetSettingAsync(SeoSettings.SiteUrl)),
SiteName = await GetSettingAsync(SeoSettings.SiteName),
DefaultTitle = await GetSettingAsync(SeoSettings.DefaultTitle),
DefaultDescription = await GetSettingAsync(SeoSettings.DefaultDescription),
DefaultKeywords = await GetSettingAsync(SeoSettings.DefaultKeywords),
DefaultImage = await GetSettingAsync(SeoSettings.DefaultImage),
TwitterSite = await GetSettingAsync(SeoSettings.TwitterSite),
Robots = await GetSettingAsync(SeoSettings.Robots),
GoogleSiteVerification = await GetSettingAsync(SeoSettings.GoogleSiteVerification),
Locales = SplitList(await GetSettingAsync(SeoSettings.Locales)),
SameAs = SplitList(await GetSettingAsync(SeoSettings.OrganizationSameAs)),
};
config.Routes = [.. (await CollectSeoRoutesAsync())
.Select(entry => entry.ToDto())
.OrderBy(route => route.RoutePath, StringComparer.Ordinal)];
return config;
}
public async Task<string> GetSitemapAsync(string baseUrl)
{
if (!await GetBoolSettingAsync(SeoSettings.SitemapEnabled))
{
return string.Empty;
}
var siteUrl = await ResolveSiteUrlAsync(baseUrl);
var excluded = SplitLines(await GetSettingAsync(SeoSettings.SitemapExcludePaths));
var entries = await CollectSitemapEntriesAsync(excluded);
var builder = new StringBuilder();
builder.AppendLine("""<?xml version="1.0" encoding="UTF-8"?>""");
builder.AppendLine("""<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">""");
foreach (var entry in entries)
{
builder.AppendLine(" <url>");
builder.AppendLine($" <loc>{Escape(siteUrl + entry.Path)}</loc>");
if (entry.LastModified.HasValue)
{
builder.AppendLine($" <lastmod>{entry.LastModified.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}</lastmod>");
}
builder.AppendLine($" <changefreq>{Escape(entry.ChangeFrequency)}</changefreq>");
builder.AppendLine($" <priority>{entry.Priority.ToString("0.0", CultureInfo.InvariantCulture)}</priority>");
builder.AppendLine(" </url>");
}
builder.AppendLine("</urlset>");
return builder.ToString();
}
public async Task<string> GetRobotsAsync(string baseUrl)
{
var robots = await GetSettingAsync(SeoSettings.Robots);
var siteUrl = await ResolveSiteUrlAsync(baseUrl);
var builder = new StringBuilder();
builder.AppendLine("User-agent: *");
// Ayar "dizine ekleme" derse butun site kapatilir; aksi halde yalnizca ziyaretciye
// acik olmayan yuzeyler disarida birakilir.
if (robots.Contains("noindex", StringComparison.OrdinalIgnoreCase))
{
builder.AppendLine("Disallow: /");
return builder.ToString();
}
builder.AppendLine("Disallow: /admin/");
builder.AppendLine("Disallow: /auth/");
builder.AppendLine("Disallow: /checkout");
builder.AppendLine("Disallow: /payment");
builder.AppendLine("Disallow: /success");
foreach (var path in SplitLines(await GetSettingAsync(SeoSettings.SitemapExcludePaths)))
{
builder.AppendLine($"Disallow: {path.TrimEnd('*')}");
}
if (await GetBoolSettingAsync(SeoSettings.SitemapEnabled))
{
builder.AppendLine();
builder.AppendLine($"Sitemap: {siteUrl}/sitemap.xml");
}
return builder.ToString();
}
private async Task<List<SitemapEntry>> CollectSitemapEntriesAsync(List<string> excluded)
{
var entries = new Dictionary<string, SitemapEntry>(StringComparer.OrdinalIgnoreCase);
void Add(string? path, DateTime? lastModified, string changeFrequency, decimal priority)
{
var normalized = NormalizePath(path);
if (normalized == null || IsExcluded(normalized, excluded) || entries.ContainsKey(normalized))
{
return;
}
entries[normalized] = new SitemapEntry(normalized, lastModified, changeFrequency, priority);
}
// Ana sayfa menude bir kayit olmayabilir; site kokunun her zaman listede olmasi gerekir.
Add("/", null, "daily", 1.0m);
var menus = await _menuRepository.GetListAsync(
menu => menu.ModuleId == PlatformConsts.PublicSite.ModuleId && !menu.IsDisabled);
foreach (var menu in menus.OrderBy(menu => menu.Order))
{
Add(menu.Url, null, DefaultChangeFrequency, 0.8m);
}
var components = await _customComponentRepository.GetListAsync(component => component.IsActive);
foreach (var component in components)
{
Add(component.RoutePath, component.LastModificationTime ?? component.CreationTime, DefaultChangeFrequency, 0.7m);
}
var posts = await _postRepository.GetListAsync(post => post.IsPublished);
foreach (var post in posts)
{
Add($"/blog/{post.Slug}", post.LastModificationTime ?? post.PublishedAt, "monthly", 0.6m);
}
// Yolu tanimlayan kayitlarin kendi SEO alanlari hem yeni yol ekler hem de
// yukaridakilerin sirasini ve sikligini ezer.
foreach (var seo in await CollectSeoRoutesAsync())
{
if (seo.RoutePath.Contains('*'))
{
continue;
}
if (seo.NoIndex || !seo.IncludeInSitemap)
{
entries.Remove(seo.RoutePath);
continue;
}
entries[seo.RoutePath] = new SitemapEntry(
seo.RoutePath,
seo.LastModified,
seo.ChangeFrequency.IsNullOrWhiteSpace() ? DefaultChangeFrequency : seo.ChangeFrequency!,
seo.Priority ?? 0.5m);
}
return [.. entries.Values.OrderBy(entry => entry.Path, StringComparer.Ordinal)];
}
/// <summary>Uygulama ici yollari tek bicime indirger; disa donuk ve yonetim adresleri elenir.</summary>
private static string? NormalizePath(string? path)
{
if (path.IsNullOrWhiteSpace())
{
return null;
}
var value = path!.Trim();
if (value.Contains("://", StringComparison.Ordinal))
{
return null;
}
if (!value.StartsWith('/'))
{
value = "/" + value;
}
if (value.StartsWith(AdminPathPrefix + "/", StringComparison.OrdinalIgnoreCase)
|| value.Equals(AdminPathPrefix, StringComparison.OrdinalIgnoreCase))
{
return null;
}
// Parametreli rota (/blog/:id) bir adres degil, bir kaliptir.
if (value.Contains(':', StringComparison.Ordinal))
{
return null;
}
return value.Length > 1 ? value.TrimEnd('/') : value;
}
private static bool IsExcluded(string path, List<string> excluded)
{
return excluded.Exists(rule => rule.EndsWith('*')
? path.StartsWith(rule.TrimEnd('*'), StringComparison.OrdinalIgnoreCase)
: path.Equals(rule, StringComparison.OrdinalIgnoreCase));
}
private async Task<string> ResolveSiteUrlAsync(string baseUrl)
{
var configured = NormalizeBaseUrl(await GetSettingAsync(SeoSettings.SiteUrl));
return configured.IsNullOrWhiteSpace() ? NormalizeBaseUrl(baseUrl) : configured;
}
private static string NormalizeBaseUrl(string value)
{
return value.IsNullOrWhiteSpace() ? string.Empty : value.Trim().TrimEnd('/');
}
private async Task<string> GetSettingAsync(string name)
{
return await _settingProvider.GetOrNullAsync(name) ?? string.Empty;
}
private async Task<bool> GetBoolSettingAsync(string name)
{
var value = await GetSettingAsync(name);
return value.IsNullOrWhiteSpace() || value.Equals("true", StringComparison.OrdinalIgnoreCase);
}
private static List<string> SplitList(string value)
{
return [.. value
.Split([',', ';', '\n', '\r'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
}
private static List<string> SplitLines(string value)
{
return [.. value
.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
}
private static string Escape(string value)
{
return value
.Replace("&", "&amp;", StringComparison.Ordinal)
.Replace("<", "&lt;", StringComparison.Ordinal)
.Replace(">", "&gt;", StringComparison.Ordinal);
}
/// <summary>
/// SEO kayitlarini yolu tanimlayan iki kaynaktan toplar: fiziksel sayfalarin <c>Route</c>
/// satirlari ve rota tasiyan Custom Component kayitlari. Ayni yolu ikisi de tanimliyorsa
/// bilesen kazanir; calisma zamaninda da bilesen fiziksel rotayi golgeler.
/// </summary>
private async Task<List<SeoEntry>> CollectSeoRoutesAsync()
{
var entries = new Dictionary<string, SeoEntry>(StringComparer.OrdinalIgnoreCase);
void Add(SeoEntry? entry)
{
if (entry != null)
{
entries[entry.RoutePath] = entry;
}
}
var routes = await _routeRepository.GetListAsync(route => route.RouteType == PublicRouteType);
foreach (var route in routes)
{
Add(SeoEntry.From(
route.Path,
route.SeoTitle, route.SeoDescription, route.SeoKeywords, route.SeoOgImage,
route.SeoCanonicalUrl, route.SeoNoIndex, route.SeoIncludeInSitemap,
route.SeoChangeFrequency, route.SeoPriority, route.SeoStructuredData,
route.LastModificationTime ?? route.CreationTime));
}
var components = await _customComponentRepository.GetListAsync(component => component.IsActive);
foreach (var component in components)
{
Add(SeoEntry.From(
component.RoutePath,
component.SeoTitle, component.SeoDescription, component.SeoKeywords, component.SeoOgImage,
component.SeoCanonicalUrl, component.SeoNoIndex, component.SeoIncludeInSitemap,
component.SeoChangeFrequency, component.SeoPriority, component.SeoStructuredData,
component.LastModificationTime ?? component.CreationTime));
}
return [.. entries.Values];
}
/// <summary>Iki kaynagin SEO alanlarini tek bicime indirger.</summary>
private sealed record SeoEntry(
string RoutePath,
string? Title,
string? Description,
string? Keywords,
string? OgImage,
string? CanonicalUrl,
bool NoIndex,
bool IncludeInSitemap,
string? ChangeFrequency,
decimal? Priority,
string? StructuredData,
DateTime? LastModified)
{
/// <summary>Hicbir SEO alani doldurulmamis kayit sozlesmeye girmez; genel varsayilanlar zaten uygulanir.</summary>
public static SeoEntry? From(
string? routePath,
string? title, string? description, string? keywords, string? ogImage,
string? canonicalUrl, bool noIndex, bool includeInSitemap,
string? changeFrequency, decimal? priority, string? structuredData,
DateTime? lastModified)
{
var normalized = NormalizePath(routePath);
if (normalized == null)
{
return null;
}
var hasValue = !title.IsNullOrWhiteSpace()
|| !description.IsNullOrWhiteSpace()
|| !keywords.IsNullOrWhiteSpace()
|| !ogImage.IsNullOrWhiteSpace()
|| !canonicalUrl.IsNullOrWhiteSpace()
|| !structuredData.IsNullOrWhiteSpace()
|| !changeFrequency.IsNullOrWhiteSpace()
|| priority.HasValue
|| noIndex
|| !includeInSitemap;
return hasValue
? new SeoEntry(normalized, title, description, keywords, ogImage, canonicalUrl,
noIndex, includeInSitemap, changeFrequency, priority, structuredData, lastModified)
: null;
}
public SeoRouteDto ToDto()
{
return new SeoRouteDto
{
RoutePath = RoutePath,
Title = Title,
Description = Description,
Keywords = Keywords,
OgImage = OgImage,
CanonicalUrl = CanonicalUrl,
NoIndex = NoIndex,
StructuredData = StructuredData,
};
}
}
private sealed record SitemapEntry(string Path, DateTime? LastModified, string ChangeFrequency, decimal Priority);
}

View file

@ -1,7 +1,10 @@
using System;
using System.Data;
using System.Text.Json;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Enums;
using Sozsoft.Languages.Languages;
using Sozsoft.Platform.Editors;
using Sozsoft.Platform.ListForms;
using Sozsoft.Platform.Queries;
using static Sozsoft.Platform.PlatformConsts;
@ -277,4 +280,60 @@ public static class ListFormSeeder_DefaultJsons
Deny = d
});
}
/// <summary>
/// Bir yolun arama motoru sozlesmesi. Ayri bir SEO tablosu yoktur: alanlar yolu tanimlayan
/// kaydin (Route, Custom Component) ekranina eklenir, bu yuzden kume tek yerde durur.
/// Metin alanlari dogrudan yazilabilir ya da <c>::Anahtar</c> ile dil anahtari tasiyabilir.
/// </summary>
/// <param name="listForm">Alanlarin eklenecegi ekran.</param>
/// <param name="editGroupOrderNo">Duzenleme formunda SEO alanlarinin toplandigi grup.</param>
/// <param name="editOrderStart">Grup icindeki ilk siranin numarasi.</param>
/// <param name="listOrderStart">Liste gorunumundeki ilk sutun numarasi.</param>
public static ListFormField[] SeoFields(ListForm listForm, int editGroupOrderNo, int editOrderStart, int listOrderStart)
{
var editOrder = editOrderStart;
var listOrder = listOrderStart;
ListFormField Field(string fieldName, DbType dbType, string editorType, string caption, string? editorOptions = null)
{
return new ListFormField
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = dbType,
FieldName = fieldName,
EditGroupOrderNo = editGroupOrderNo,
EditOrderNo = editOrder++,
EditorType2 = editorType,
ColSpan = 1,
EditorOptions = editorOptions,
CaptionName = caption,
Width = 0,
ListOrderNo = listOrder++,
// SEO alanlari liste gorunumunu sisirir; ekranda varsayilan olarak gizlidir,
// duzenleme formundaki SEO grubundan yonetilir.
Visible = false,
IsActive = true,
AllowSearch = false,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
};
}
return
[
Field("SeoTitle", DbType.String, EditorTypes.dxTextBox, "App.Seo.Title"),
Field("SeoDescription", DbType.String, EditorTypes.dxTextArea, "App.Seo.Description", EditorOptions.Multiline(80)),
Field("SeoKeywords", DbType.String, EditorTypes.dxTextBox, "App.Seo.Keywords"),
Field("SeoOgImage", DbType.String, EditorTypes.dxTextBox, "App.Seo.OgImage"),
Field("SeoCanonicalUrl", DbType.String, EditorTypes.dxTextBox, "App.Seo.CanonicalUrl"),
Field("SeoNoIndex", DbType.Boolean, EditorTypes.dxCheckBox, "App.Seo.NoIndex"),
Field("SeoIncludeInSitemap", DbType.Boolean, EditorTypes.dxCheckBox, "App.Seo.IncludeInSitemap"),
Field("SeoChangeFrequency", DbType.String, EditorTypes.dxTextBox, "App.Seo.ChangeFrequency"),
Field("SeoPriority", DbType.Decimal, EditorTypes.dxNumberBox, "App.Seo.Priority"),
Field("SeoStructuredData", DbType.String, EditorTypes.dxTextArea, "App.Seo.StructuredData", EditorOptions.Multiline(160)),
];
}
}

View file

@ -5709,10 +5709,16 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
{
new() {
Order = 1, ColCount = 1, ColSpan = 1, ItemType = "group" },
// SEO alanlari kendi grubunda toplanir; yolun metasi bu kaydin uzerinde yasar.
new() {
Order = 2, ColCount = 2, ColSpan = 1, ItemType = "group", Caption = "App.Seo" },
}),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
new() { FieldName = "RouteType", FieldDbType = DbType.String, Value = "public", CustomValueType = FieldCustomValueTypeEnum.Value }
new() { FieldName = "RouteType", FieldDbType = DbType.String, Value = "public", CustomValueType = FieldCustomValueTypeEnum.Value },
// NOT NULL bit kolonlar; form bunlari yazmazsa insert NULL gonderir.
new() { FieldName = "SeoNoIndex", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value },
new() { FieldName = "SeoIncludeInSitemap", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value },
})
});
@ -5887,7 +5893,8 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
}),
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
}
},
.. SeoFields(listForm, editGroupOrderNo: 2, editOrderStart: 1, listOrderStart: 8),
]);
#endregion
}
@ -7074,6 +7081,8 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>()
{
new() { Order = 1, ColCount = 2, ColSpan = 1, ItemType = "group" },
// SEO alanlari kendi grubunda toplanir; yolun metasi bu kaydin uzerinde yasar.
new() { Order = 2, ColCount = 2, ColSpan = 1, ItemType = "group", Caption = "App.Seo" },
}),
InsertFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
new() { FieldName = "CreationTime", FieldDbType = DbType.DateTimeOffset, Value = "@NOW", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
@ -7084,6 +7093,9 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
}),
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value },
// NOT NULL bit kolonlar; form bunlari yazmazsa insert NULL gonderir.
new() { FieldName = "SeoNoIndex", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value },
new() { FieldName = "SeoIncludeInSitemap", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value },
}),
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.CustomComponent)),
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
@ -7329,6 +7341,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
.. SeoFields(listForm, editGroupOrderNo: 2, editOrderStart: 1, listOrderStart: 10),
]);
#endregion
}
@ -12296,5 +12309,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
#endregion
}
#endregion
}
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Sozsoft.Languages.Languages;
using Sozsoft.Platform.Enums;
@ -258,6 +258,26 @@ public static class PlatformConsts
public const string TimedLoginEmails = Default + ".TimedLoginEmails";
}
/// <summary>Public sitenin arama motoru varsayilanlari; tamami istemciye tasinir.</summary>
public static class Seo
{
public const string Default = GroupName + ".Seo";
public const string SiteUrl = Default + ".SiteUrl";
public const string SiteName = Default + ".SiteName";
public const string DefaultTitle = Default + ".DefaultTitle";
public const string DefaultDescription = Default + ".DefaultDescription";
public const string DefaultKeywords = Default + ".DefaultKeywords";
public const string DefaultImage = Default + ".DefaultImage";
public const string TwitterSite = Default + ".TwitterSite";
public const string Locales = Default + ".Locales";
public const string Robots = Default + ".Robots";
public const string GoogleSiteVerification = Default + ".GoogleSiteVerification";
public const string OrganizationSameAs = Default + ".OrganizationSameAs";
public const string SitemapEnabled = Default + ".SitemapEnabled";
public const string SitemapExcludePaths = Default + ".SitemapExcludePaths";
}
public static class Theme
{
public const string Default = GroupName + ".Theme";

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
namespace Sozsoft.Platform.Enums;

View file

@ -22,6 +22,29 @@ public class CustomComponent : FullAuditedEntity<Guid>
/// </summary>
public string? DataSources { get; set; }
/// <summary>
/// Arama motoru yuzeyi. Ayri bir SEO tablosu yoktur: meta, yolu tanimlayan kaydin
/// uzerinde yasar. Metin alanlari dogrudan yazilabilir ya da <c>::Anahtar</c> bicimiyle
/// dil anahtari tasiyabilir; kulture gore cozumlemeyi istemci yapar.
/// Rotasiz bilesende (ust bilgi, alt bilgi) bos kalir.
/// </summary>
public string? SeoTitle { get; set; }
public string? SeoDescription { get; set; }
public string? SeoKeywords { get; set; }
public string? SeoOgImage { get; set; }
public string? SeoCanonicalUrl { get; set; }
/// <summary>Dizine eklenmesin; sitemap disinda kalir ve <c>noindex</c> yazilir.</summary>
public bool SeoNoIndex { get; set; }
public bool SeoIncludeInSitemap { get; set; } = true;
public string? SeoChangeFrequency { get; set; }
public decimal? SeoPriority { get; set; }
/// <summary>Sayfaya gomulen JSON-LD; bos ise yalnizca genel Organization verisi yazilir.</summary>
public string? SeoStructuredData { get; set; }
public CustomComponent(string name, string? routePath, string code, string? props, string? description, bool isActive, string? dependencies, string? dataSources = null)
{
Name = name;

View file

@ -12,6 +12,28 @@ public class Route : FullAuditedEntity<Guid>
public string RouteType { get; set; }
public string[] Authority { get; set; }
/// <summary>
/// Arama motoru yuzeyi. Ayri bir SEO tablosu yoktur: meta, yolu tanimlayan kaydin
/// uzerinde yasar. Metin alanlari dogrudan yazilabilir ya da <c>::Anahtar</c> bicimiyle
/// dil anahtari tasiyabilir; kulture gore cozumlemeyi istemci yapar.
/// </summary>
public string? SeoTitle { get; set; }
public string? SeoDescription { get; set; }
public string? SeoKeywords { get; set; }
public string? SeoOgImage { get; set; }
public string? SeoCanonicalUrl { get; set; }
/// <summary>Dizine eklenmesin; sitemap disinda kalir ve <c>noindex</c> yazilir.</summary>
public bool SeoNoIndex { get; set; }
public bool SeoIncludeInSitemap { get; set; } = true;
public string? SeoChangeFrequency { get; set; }
public decimal? SeoPriority { get; set; }
/// <summary>Sayfaya gomulen JSON-LD; bos ise yalnizca genel Organization verisi yazilir.</summary>
public string? SeoStructuredData { get; set; }
public Route(string key, string path, string componentType, string componentPath, string routeType, string[] authority)
{
Key = key;

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
@ -280,6 +280,17 @@ public class PlatformDbContext :
b.Property(x => x.ComponentPath).IsRequired().HasMaxLength(256);
b.Property(x => x.RouteType).HasMaxLength(64);
// SEO metni ya dogrudan yazilir ya da `::Anahtar` bicimiyle dil anahtari tasir.
b.Property(x => x.SeoTitle).HasMaxLength(200);
b.Property(x => x.SeoDescription).HasMaxLength(500);
b.Property(x => x.SeoKeywords).HasMaxLength(300);
b.Property(x => x.SeoOgImage).HasMaxLength(500);
b.Property(x => x.SeoCanonicalUrl).HasMaxLength(500);
b.Property(x => x.SeoChangeFrequency).HasMaxLength(20);
b.Property(x => x.SeoPriority).HasPrecision(2, 1);
// Sayfaya eklenen JSON-LD belgesinin uzunlugu onceden bilinemez.
b.Property(x => x.SeoStructuredData).HasColumnType("nvarchar(max)");
b.Property(x => x.Authority).HasConversion(
v => string.Join(",", v),
v => v.Split(',', StringSplitOptions.RemoveEmptyEntries)
@ -693,6 +704,17 @@ public class PlatformDbContext :
// Data sekmesindeki endpoint listesi; adet ve url uzunlugu onceden bilinemez.
b.Property(x => x.DataSources).HasColumnType("nvarchar(max)");
// SEO metni ya dogrudan yazilir ya da `::Anahtar` bicimiyle dil anahtari tasir.
b.Property(x => x.SeoTitle).HasMaxLength(200);
b.Property(x => x.SeoDescription).HasMaxLength(500);
b.Property(x => x.SeoKeywords).HasMaxLength(300);
b.Property(x => x.SeoOgImage).HasMaxLength(500);
b.Property(x => x.SeoCanonicalUrl).HasMaxLength(500);
b.Property(x => x.SeoChangeFrequency).HasMaxLength(20);
b.Property(x => x.SeoPriority).HasPrecision(2, 1);
// Sayfaya eklenen JSON-LD belgesinin uzunlugu onceden bilinemez.
b.Property(x => x.SeoStructuredData).HasColumnType("nvarchar(max)");
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
// Rotasi olmayan bilesen (alt bilgi, ust bilgi) `RoutePath` tasimaz; tekil indeks
// yalnizca dolu degerleri kapsar, aksi halde her rotasiz bilesen digeriyle carpisir.

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260922094111_Initial")]
[Migration("20260922201238_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -1956,6 +1956,43 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("SeoCanonicalUrl")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("SeoChangeFrequency")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<string>("SeoDescription")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<bool>("SeoIncludeInSitemap")
.HasColumnType("bit");
b.Property<string>("SeoKeywords")
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<bool>("SeoNoIndex")
.HasColumnType("bit");
b.Property<string>("SeoOgImage")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<decimal?>("SeoPriority")
.HasPrecision(2, 1)
.HasColumnType("decimal(2,1)");
b.Property<string>("SeoStructuredData")
.HasColumnType("nvarchar(max)");
b.Property<string>("SeoTitle")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("Id");
b.HasIndex("Name")
@ -4998,6 +5035,43 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("SeoCanonicalUrl")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("SeoChangeFrequency")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<string>("SeoDescription")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<bool>("SeoIncludeInSitemap")
.HasColumnType("bit");
b.Property<string>("SeoKeywords")
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<bool>("SeoNoIndex")
.HasColumnType("bit");
b.Property<string>("SeoOgImage")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<decimal?>("SeoPriority")
.HasPrecision(2, 1)
.HasColumnType("decimal(2,1)");
b.Property<string>("SeoStructuredData")
.HasColumnType("nvarchar(max)");
b.Property<string>("SeoTitle")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("Id");
b.HasIndex("Key")

View file

@ -1187,6 +1187,16 @@ namespace Sozsoft.Platform.Migrations
IsActive = table.Column<bool>(type: "bit", nullable: false),
Dependencies = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
DataSources = table.Column<string>(type: "nvarchar(max)", nullable: true),
SeoTitle = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
SeoDescription = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
SeoKeywords = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: true),
SeoOgImage = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
SeoCanonicalUrl = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
SeoNoIndex = table.Column<bool>(type: "bit", nullable: false),
SeoIncludeInSitemap = table.Column<bool>(type: "bit", nullable: false),
SeoChangeFrequency = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
SeoPriority = table.Column<decimal>(type: "decimal(2,1)", precision: 2, scale: 1, nullable: true),
SeoStructuredData = table.Column<string>(type: "nvarchar(max)", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
@ -1689,6 +1699,16 @@ namespace Sozsoft.Platform.Migrations
ComponentPath = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
RouteType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
Authority = table.Column<string>(type: "nvarchar(max)", nullable: true),
SeoTitle = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
SeoDescription = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
SeoKeywords = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: true),
SeoOgImage = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
SeoCanonicalUrl = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
SeoNoIndex = table.Column<bool>(type: "bit", nullable: false),
SeoIncludeInSitemap = table.Column<bool>(type: "bit", nullable: false),
SeoChangeFrequency = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
SeoPriority = table.Column<decimal>(type: "decimal(2,1)", precision: 2, scale: 1, nullable: true),
SeoStructuredData = table.Column<string>(type: "nvarchar(max)", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),

View file

@ -1953,6 +1953,43 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("SeoCanonicalUrl")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("SeoChangeFrequency")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<string>("SeoDescription")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<bool>("SeoIncludeInSitemap")
.HasColumnType("bit");
b.Property<string>("SeoKeywords")
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<bool>("SeoNoIndex")
.HasColumnType("bit");
b.Property<string>("SeoOgImage")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<decimal?>("SeoPriority")
.HasPrecision(2, 1)
.HasColumnType("decimal(2,1)");
b.Property<string>("SeoStructuredData")
.HasColumnType("nvarchar(max)");
b.Property<string>("SeoTitle")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("Id");
b.HasIndex("Name")
@ -4995,6 +5032,43 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("SeoCanonicalUrl")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("SeoChangeFrequency")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<string>("SeoDescription")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<bool>("SeoIncludeInSitemap")
.HasColumnType("bit");
b.Property<string>("SeoKeywords")
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<bool>("SeoNoIndex")
.HasColumnType("bit");
b.Property<string>("SeoOgImage")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<decimal?>("SeoPriority")
.HasPrecision(2, 1)
.HasColumnType("decimal(2,1)");
b.Property<string>("SeoStructuredData")
.HasColumnType("nvarchar(max)");
b.Property<string>("SeoTitle")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("Id");
b.HasIndex("Key")

View file

@ -0,0 +1,46 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Sozsoft.Platform.Public;
using Volo.Abp.AspNetCore.Mvc;
namespace Sozsoft.Platform.Controllers;
/// <summary>
/// Arama motorlarinin kok dizinde bekledigi iki metin dosyasi. Adresler API alan adinda
/// yayinlanir; site alan adindan servis edilmesi icin ters vekil <c>/sitemap.xml</c> ve
/// <c>/robots.txt</c> yollarini buraya yonlendirir.
/// </summary>
[AllowAnonymous]
public class SeoController : AbpController
{
private readonly ISeoAppService _seoAppService;
public SeoController(ISeoAppService seoAppService)
{
_seoAppService = seoAppService;
}
[HttpGet("sitemap.xml")]
public async Task<IActionResult> SitemapAsync()
{
var sitemap = await _seoAppService.GetSitemapAsync(GetRequestBaseUrl());
// Ayar kapaliyken bos govde yerine 404 donulur; bos bir sitemap arama motoruna
// "bu sitede indekslenecek sayfa yok" demektir.
return string.IsNullOrWhiteSpace(sitemap)
? NotFound()
: Content(sitemap, "application/xml");
}
[HttpGet("robots.txt")]
public async Task<IActionResult> RobotsAsync()
{
return Content(await _seoAppService.GetRobotsAsync(GetRequestBaseUrl()), "text/plain");
}
private string GetRequestBaseUrl()
{
return $"{Request.Scheme}://{Request.Host}";
}
}

View file

@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Identity;
using Sozsoft.Platform.Public;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
@ -75,6 +76,11 @@ public class PlatformApplicationConfigurationContributor : IApplicationConfigura
})
.ToList());
// Public sayfanin metasi ilk boyamada hazir olmali; ayri bir istek beklenirse
// tarayici basligi ve paylasim karti bir kare gec dolar.
var seoAppService = context.ServiceProvider.GetRequiredService<ISeoAppService>();
context.ApplicationConfiguration.SetProperty("seo", await seoAppService.GetConfigAsync());
var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
if (currentUser.Id.HasValue)
{

View file

@ -48,7 +48,7 @@ options you rejected.
3. Every implementation proposal must include tenant and permission design.
4. Never bypass platform authorization patterns.
5. Never hardcode secrets, tenant IDs, or connection strings.
6. Never step outside the framework without explicit approval (standing default 10).
6. Never step outside the framework without explicit approval (standing default 11).
Exception:
@ -138,11 +138,17 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
görsel Tur 2'nin çoğunu cevaplar, yalnızca görselden okunamayanlar sorulur.
Bu kural yalnızca **yeni üretim** içindir; düzeltme/alan ekleme/debug doğrudan yapılır.
Ayrıntı: `lowcode.instructions.md` §0.6–§0.7.
7. **Ekran içi hesap/koşul ihtiyacı** → önce `EditorScript` (Script Builder tarifleri), sonra
7. **Ziyaretçiye açık yeni yol** (public `RoutePath` taşıyan custom component, `Pub` menüsü,
yeni public sayfa) → SEO alanları da doldurulur. Ayrı bir SEO tablosu yoktur: meta, yolu
tanımlayan kaydın `Seo*` kolonlarındadır — `data/App.Menus.Routes.json` ya da
`data/App.DeveloperKit.CustomComponents/{Id}.json`. Metin `::Anahtar` ile dil anahtarı taşır.
Kullanıcı SEO bilgisi girmez; üreten yazar. Genel varsayılanlar kayıtta değil
`App.SiteManagement.Seo.*` ayarlarındadır. Ayrıntı: `lowcode.instructions.md` §0.9.
8. **Ekran içi hesap/koşul ihtiyacı** → önce `EditorScript` (Script Builder tarifleri), sonra
`EditorOptions`; kod yazmak son çare.
8. **SQL** → her zaman parametreli. String birleştirme ile sorgu kurma.
9. **Silme** → `DeleteCommand = DefaultDeleteCommand("{Tablo}")`; ham SQL string'i yazma.
10. **Çerçeve sınırı** → uygulama kendini kendi mekanizmalarıyla geliştirir (seed artefaktları;
9. **SQL** → her zaman parametreli. String birleştirme ile sorgu kurma.
10. **Silme** → `DeleteCommand = DefaultDeleteCommand("{Tablo}")`; ham SQL string'i yazma.
11. **Çerçeve sınırı** → uygulama kendini kendi mekanizmalarıyla geliştirir (seed artefaktları;
var olan `ui` bileşenine eksik olay/prop/küçük yetenek ekleme — komponentin %70'i hazır
araçlarla kuruluyorsa kalanı bu eklemelerle tamamlanır, yeni bileşen yazılmaz; geriye dönük
uyumlu tarif/seçenek/token ekleme).
@ -234,6 +240,18 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
duyurulur (`INotificationRealtimeNotifier`; sözleşme bildirim modülünün Domain'inde, SignalR
gerçeklemesi barındıran uygulamada — modül katmanı ASP.NET Core'a bağlanmaz) — duyuru en iyi çabadır, istemcideki yedek yoklama kaldırılmaz.
Ayrıntı: `README.md` §13.3–§13.5.
- **SEO** → **ayrı bir SEO tablosu yoktur**; meta, yolu tanımlayan kaydın üzerinde yaşar. Üç
parça birlikte çalışır: genel varsayılanlar `App.SiteManagement.Seo.*` ayarları, yol bazlı
alanlar `Route.Seo*` ve `CustomComponent.Seo*` (ikisinin de `SeedSync*` bayrakları açık),
istemci tarafı `SeoHead`. Üçü de uygulama konfigürasyonunun `seo` alanından beslenir
(`PlatformApplicationConfigurationContributor` → `SeoAppService.GetConfigAsync`); `sitemap.xml`
ve `robots.txt` aynı servisten `SeoController` ile yayınlanır. Aynı yolu iki kaynak da
tanımlıyorsa bileşen kazanır — çalışma zamanında da bileşen fiziksel rotayı gölgeler.
**Ziyaretçiye açık yeni bir yol üreten her iş SEO alanlarını da doldurur** (standing default 7).
Sitemap'e giren kaynaklar (`Pub` menüleri, aktif custom component rotaları, yayınlanmış blog
yazıları) `SeoAppService` içindedir; yeni bir public kaynak türü eklenirse toplayıcıya da
eklenir, yoksa sayfa sitemap'te görünmez. Yeni bir meta alanı = iki entity + `SeoRouteDto` +
`SeoFields` (seeder) + `SeoHead` birlikte.
- **Yeni bir dil anahtarı** → `LanguagesData.json`; eklemeden önce anahtarın zaten var olup
olmadığını kontrol et.
- **Custom component sınıfları** → bileşen kodu veritabanında yaşar, Tailwind derlemede yalnızca
@ -308,7 +326,9 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
ve zamanlanmış işler (`App.BackgroundWorkers.RecurringJobs.json`) da buradadır; `HostData.json`
kaldırıldı, `HostDataSeeder` yalnızca bağlantı cümlesi appsettings'ten gelen varsayılan veri kaynağını kurar. Dosyalar ekranın
anahtarını (`KeyFieldName`) kullanır; `Guid` anahtarlı satırın `Id`'si iş anahtarından türetilmiş
sabit bir GUID'dir, satır eklerken aynı kuralla üret. `App.Menus.Routes.json` dosyasına dokunma.
sabit bir GUID'dir, satır eklerken aynı kuralla üret. `App.Menus.Routes.json` dosyasına
yalnızca var olan satırın `Seo*` alanları için dokunulur (standing default 7); rota eklemek,
silmek ya da yolunu değiştirmek çerçeve dışıdır.
- **Public site** → menü `Menu` tablosunda `ModuleId = "Pub"` altında (`PlatformConsts.PublicSite`);
`App.Public.Header` hem üst menüyü hem alt bilgideki hızlı bağlantıları besler (ikisi de
`PublicNav` düğümü) ve ziyaretçiye uygulama konfigürasyonunun `publicMenus` alanıyla taşınır

View file

@ -3483,7 +3483,7 @@
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Sas|Adm"
},
},
{
"GroupName": "App.Administration",
"Name": "App.Notifications.NotificationTypes",
@ -3774,6 +3774,16 @@
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.SiteManagement.Seo",
"ParentName": "App.SiteManagement",
"DisplayName": "App.SiteManagement.Seo",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Sender",

View file

@ -8,7 +8,7 @@
"Title": "App.PostsAi.AiTitle",
"Slug": "ai-ve-gelecegi",
"Summary": "App.PostsAi.Excerpt",
"CoverImage": "https://images.pexels.com/photos/8386434/pexels-photo-8386434.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CoverImage": "/img/blog/ai-ve-gelecegi.webp",
"ReadTime": "5 dk",
"CategoryId": "20294789-3472-5326-843d-4c80a9a0b740",
"Author": "system@sozsoft.com",
@ -25,7 +25,7 @@
"Title": "App.PostsWeb.WebTitle",
"Slug": "web-gelistirmede-son-trendler",
"Summary": "App.PostsWeb.Excerpt",
"CoverImage": "https://images.pexels.com/photos/11035471/pexels-photo-11035471.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CoverImage": "/img/blog/web-gelistirmede-son-trendler.webp",
"ReadTime": "7 dk",
"CategoryId": "29997b64-a21d-5e04-a392-d6246c6cf766",
"Author": "system@sozsoft.com",
@ -42,7 +42,7 @@
"Title": "App.PostsSecurity.SecurityTitle",
"Slug": "siber-guvenlik-tehditleri-ve-korunma-yollari",
"Summary": "App.PostsSecurity.Excerpt",
"CoverImage": "https://images.pexels.com/photos/5380642/pexels-photo-5380642.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CoverImage": "/img/blog/siber-guvenlik-tehditleri-ve-korunma-yollari.webp",
"ReadTime": "6 dk",
"CategoryId": "748a96c1-eba8-53a7-9c3d-7317f12ec627",
"Author": "system@sozsoft.com",
@ -59,7 +59,7 @@
"Title": "App.PostsMobile.MobileTitle",
"Slug": "mobil-uygulama-gelistirmede-cross-platform-cozumler",
"Summary": "App.PostsMobile.Excerpt",
"CoverImage": "https://images.pexels.com/photos/13017583/pexels-photo-13017583.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CoverImage": "/img/blog/mobil-uygulama-gelistirmede-cross-platform-cozumler.webp",
"ReadTime": "4 dk",
"CategoryId": "f282699d-8dda-5d0a-bd11-9e9b40a10161",
"Author": "system@sozsoft.com",
@ -76,7 +76,7 @@
"Title": "App.PostsDatabase.DatabaseTitle",
"Slug": "veritabani-yonetim-sistemleri-karsilastirmasi",
"Summary": "App.PostsDatabase.Excerpt",
"CoverImage": "https://images.pexels.com/photos/325229/pexels-photo-325229.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CoverImage": "/img/blog/veritabani-yonetim-sistemleri-karsilastirmasi.webp",
"ReadTime": "8 dk",
"CategoryId": "63dcbcec-e47f-5859-9de0-5bd46106ffb5",
"Author": "system@sozsoft.com",
@ -93,7 +93,7 @@
"Title": "App.PostsDigital.DigitalTitle",
"Slug": "dijital-pazarlamada-veri-analizi",
"Summary": "App.PostsDigital.Excerpt",
"CoverImage": "https://images.pexels.com/photos/7681091/pexels-photo-7681091.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CoverImage": "/img/blog/dijital-pazarlamada-veri-analizi.webp",
"ReadTime": "6 dk",
"CategoryId": "89a1ed95-dbf9-54f1-a5a8-41dddae7a9d2",
"Author": "system@sozsoft.com",

View file

@ -13,7 +13,17 @@
"Dependencies": [],
"Code": "/*__SOZSOFT_VISUAL_DESIGNER__%7B%22version%22%3A1%2C%22sourceMode%22%3A%22visual%22%2C%22nodes%22%3A%5B%5D%2C%22canvas%22%3A%7B%22width%22%3A%22responsive%22%7D%2C%22lifecycle%22%3A%7B%22onMount%22%3A%22%22%7D%2C%22dataSources%22%3A%5B%5D%7D__*/\nconst NewComponent = () => {\n\n\n return (\n <>\n\n </>\n )\n}\n\nexport default NewComponent",
"Props": "{\"visualDesigner\":{\"version\":1,\"sourceMode\":\"visual\",\"nodes\":[],\"canvas\":{\"width\":\"responsive\"},\"lifecycle\":{\"onMount\":\"\"},\"dataSources\":[]}}",
"DataSources": []
"DataSources": [],
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
}
]
}
}

View file

@ -15,7 +15,17 @@
],
"Code": "const RoleListComponent = ({\n title = \"AbpRoles\"\n}) => {\n return (\n <DynamicEntityComponent id=\"c_mdljvvmq_fno52v\" title={title} />\n );\n};\n\nexport default RoleListComponent;",
"Props": null,
"DataSources": []
"DataSources": [],
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
}
]
}
}

View file

@ -15,7 +15,17 @@
"Props": null,
"DataSources": "",
"LastModificationTime": null,
"CreationTime": "2026-09-05T17:22:11"
"CreationTime": "2026-09-05T17:22:11",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
}
]
}
}

View file

@ -11,7 +11,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/public/Products",
"RouteType": "public",
"Authority": ""
"Authority": "",
"SeoTitle": "::App.PublicSeo.Products.Title",
"SeoDescription": "::App.PublicSeo.Products.Description",
"SeoKeywords": "::App.PublicSeo.Products.Keywords",
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": false,
"SeoIncludeInSitemap": true,
"SeoChangeFrequency": "weekly",
"SeoPriority": 0.9,
"SeoStructuredData": null
},
{
"Id": "5c071295-f626-5e41-93fc-b4462a7a0528",
@ -20,7 +30,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/public/Checkout",
"RouteType": "public",
"Authority": ""
"Authority": "",
"SeoTitle": "::App.PublicSeo.Checkout.Title",
"SeoDescription": "::App.PublicSeo.Checkout.Description",
"SeoKeywords": "::App.PublicSeo.Checkout.Keywords",
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "f88f7222-42e6-5bef-b2aa-a8db81d11383",
@ -29,7 +49,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/public/Payment",
"RouteType": "public",
"Authority": ""
"Authority": "",
"SeoTitle": "::App.PublicSeo.Payment.Title",
"SeoDescription": "::App.PublicSeo.Payment.Description",
"SeoKeywords": "::App.PublicSeo.Payment.Keywords",
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "c4c17ec7-8423-513a-863a-6065816efe0a",
@ -38,7 +68,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/public/Success",
"RouteType": "public",
"Authority": ""
"Authority": "",
"SeoTitle": "::App.PublicSeo.Success.Title",
"SeoDescription": "::App.PublicSeo.Success.Description",
"SeoKeywords": "::App.PublicSeo.Success.Keywords",
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "d3529321-2f70-5965-8e2d-7e4dc9e56dca",
@ -47,7 +87,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/public/Blog",
"RouteType": "public",
"Authority": ""
"Authority": "",
"SeoTitle": "::App.PublicSeo.Blog.Title",
"SeoDescription": "::App.PublicSeo.Blog.Description",
"SeoKeywords": "::App.PublicSeo.Blog.Keywords",
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": false,
"SeoIncludeInSitemap": true,
"SeoChangeFrequency": "daily",
"SeoPriority": 0.8,
"SeoStructuredData": null
},
{
"Id": "037dc714-77fc-5b0c-840c-0cdeca22df19",
@ -56,7 +106,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/public/BlogDetail",
"RouteType": "public",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "d170f3e3-b0bd-5ed0-8394-601b51f5666f",
@ -65,7 +125,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/public/Demo",
"RouteType": "public",
"Authority": ""
"Authority": "",
"SeoTitle": "::App.PublicSeo.Demo.Title",
"SeoDescription": "::App.PublicSeo.Demo.Description",
"SeoKeywords": "::App.PublicSeo.Demo.Keywords",
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": false,
"SeoIncludeInSitemap": true,
"SeoChangeFrequency": null,
"SeoPriority": 0.6,
"SeoStructuredData": null
},
{
"Id": "e4d6f040-ad8a-523f-bd4f-211329c651cf",
@ -74,7 +144,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/AccessDenied",
"RouteType": "public",
"Authority": ""
"Authority": "",
"SeoTitle": "::App.PublicSeo.AccessDenied.Title",
"SeoDescription": "::App.PublicSeo.AccessDenied.Description",
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "bf6d30a3-03cf-5475-9daa-730023459bb0",
@ -83,7 +163,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/auth/Login",
"RouteType": "authenticated",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "df9b57fd-37f7-5377-b84c-70257295261a",
@ -92,7 +182,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/auth/Register",
"RouteType": "authenticated",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "bb282d0d-062b-51b7-87d7-22545c6f0c4d",
@ -101,7 +201,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/auth/ForgotPassword",
"RouteType": "authenticated",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "7be57172-a75d-5f03-8a7a-566523dc5acc",
@ -110,7 +220,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/auth/ResetPassword",
"RouteType": "authenticated",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "2e78c334-4e75-586b-9c14-f5b5169bd895",
@ -119,7 +239,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/auth/SendConfirmationCode",
"RouteType": "authenticated",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "d5f0773b-dece-5b55-beea-db2547ff9293",
@ -128,7 +258,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/auth/ExtendLogin",
"RouteType": "authenticated",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "f639b8fd-a462-5af5-b230-d2e5bd8a8eaf",
@ -137,7 +277,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/auth/VerifyConfirmationCode",
"RouteType": "authenticated",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "56ecf681-cf6b-5968-9acc-754e3026231c",
@ -146,7 +296,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/Dashboard",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "f5d4a187-ef76-5010-bb2f-14844722875d",
@ -155,7 +315,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/menu/MenuManager",
"RouteType": "protected",
"Authority": "App.Menus.Manager"
"Authority": "App.Menus.Manager",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "43285584-09c7-52cb-83ae-a35cb69bfced",
@ -164,7 +334,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/listForm/wizard/Wizard",
"RouteType": "protected",
"Authority": "App.Listforms.Wizard"
"Authority": "App.Listforms.Wizard",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "e87db52d-25cd-5c0f-9aa8-01dc2dc5b032",
@ -173,7 +353,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/listForm/wizard/WizardFileManager",
"RouteType": "protected",
"Authority": "App.Listforms.Wizard"
"Authority": "App.Listforms.Wizard",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "8ce5dfc6-8795-5d5e-b815-447f08292900",
@ -182,7 +372,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/listForm/edit/FormEdit",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "4e380430-9661-55ff-9c6b-270823b14401",
@ -191,7 +391,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/ai/Assistant",
"RouteType": "protected",
"Authority": "App.Definitions.AiBot.Asistant"
"Authority": "App.Definitions.AiBot.Asistant",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "ad21c21d-84c2-5f73-9eff-5bdd4d6754eb",
@ -200,7 +410,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/profile/Profile",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "43df2816-075a-5fb0-8d75-07041acedf60",
@ -209,7 +429,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/profile/Profile",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "5bf4ec7d-09fa-57bc-a832-915f0bd9a424",
@ -218,7 +448,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/profile/Profile",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "c64d7bd0-e884-55a6-915a-7a43e0c5e684",
@ -227,7 +467,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/activityLog/ActivityLog",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "012f714f-fb86-5292-a2f1-c11c4bf88a06",
@ -236,7 +486,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/version/ChangeLog",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "507adc62-0ffd-51f0-a9d2-165eccbfc225",
@ -245,7 +505,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/settings/Settings",
"RouteType": "protected",
"Authority": "App.Setting"
"Authority": "App.Setting",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "9cae5316-b620-5100-9726-bd638cd71517",
@ -254,7 +524,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/user-management/Details",
"RouteType": "protected",
"Authority": "AbpIdentity.Users.Update"
"Authority": "AbpIdentity.Users.Update",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "f44286f9-e9d8-5478-80c1-3643eb848078",
@ -263,7 +543,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/organization-unit/OrganizationUnits",
"RouteType": "protected",
"Authority": "Abp.Identity.OrganizationUnits"
"Authority": "Abp.Identity.OrganizationUnits",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "dc18c096-b996-51f6-b774-14a90e74f137",
@ -272,7 +562,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/hr/OrgChart",
"RouteType": "protected",
"Authority": "App.Definitions.Department"
"Authority": "App.Definitions.Department",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "53d4baee-8360-523a-8470-26eaaaa54371",
@ -281,7 +581,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/forum/Forum",
"RouteType": "protected",
"Authority": "App.ForumManagement.Publish"
"Authority": "App.ForumManagement.Publish",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "094842eb-fc95-51ea-bd57-6284f377c199",
@ -290,7 +600,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/list/List",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "c2230e57-183d-56b5-8e7b-e6fde917b335",
@ -299,7 +619,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/form/FormNew",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "62e585e8-7393-5749-8281-ba94f7fdd43e",
@ -308,7 +638,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/form/FormView",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "fddafca0-a6ad-51fb-85b0-ccdf1cfed1d6",
@ -317,7 +657,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/form/FormEdit",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "35d0b346-fe04-5a5a-896f-5e2b87e90bc3",
@ -326,7 +676,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/list/Chart",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "76a3f75e-11db-560a-b749-989f2ca579d7",
@ -335,7 +695,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/developerKit/SqlQueryManager",
"RouteType": "protected",
"Authority": "App.SqlQueryManager"
"Authority": "App.SqlQueryManager",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "08891b2a-5d2e-54c5-b12a-e0e8c2547b55",
@ -344,7 +714,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/developerKit/ComponentCodeLayout",
"RouteType": "protected",
"Authority": "App.DeveloperKit.CustomComponents"
"Authority": "App.DeveloperKit.CustomComponents",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "cdbfdea5-ea04-5537-bb5c-1dd964c675fe",
@ -353,7 +733,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/intranet/documents/IntranetDocuments",
"RouteType": "protected",
"Authority": "App.Intranet.Document"
"Authority": "App.Intranet.Document",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "ea5acaee-a479-5daf-b4e6-2999578fd78d",
@ -362,7 +752,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/files/FileManager",
"RouteType": "protected",
"Authority": "App.Files"
"Authority": "App.Files",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "c074e5cc-ab9c-51e9-a89c-d9f662316ab5",
@ -371,7 +771,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/report/DevexpressReportViewer",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "57520752-8f21-5d81-aaf7-fa8089b83e1c",
@ -380,7 +790,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/report/DevexpressReportDesigner",
"RouteType": "protected",
"Authority": ""
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "16917407-a912-53ab-ab96-d14e7ef6abd2",
@ -389,7 +809,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/videoroom/Dashboard",
"RouteType": "protected",
"Authority": "App.Videoroom"
"Authority": "App.Videoroom",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "7cfef810-4738-577e-88aa-ca32f9d15ccc",
@ -398,7 +828,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/videoroom/RoomList",
"RouteType": "protected",
"Authority": "App.Videoroom.List"
"Authority": "App.Videoroom.List",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "b6af134c-95b8-5453-915a-4cf5d84cb600",
@ -407,7 +847,17 @@
"ComponentType": "normal",
"ComponentPath": "@/views/admin/videoroom/RoomDetail",
"RouteType": "protected",
"Authority": "App.Videoroom.Detail"
"Authority": "App.Videoroom.Detail",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
}
]
}

View file

@ -12,7 +12,7 @@
"MonthlyPrice": 850,
"YearlyPrice": 3400,
"IsQuantityBased": true,
"ImageUrl": "https://images.pexels.com/photos/1181673/pexels-photo-1181673.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/branch-hosting.webp"
},
{
"Id": "3ced7419-b72a-5bd5-b587-b76cc9778460",
@ -23,7 +23,7 @@
"MonthlyPrice": 2100,
"YearlyPrice": 2100,
"IsQuantityBased": false,
"ImageUrl": "https://images.pexels.com/photos/442150/pexels-photo-442150.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/backup-service.webp"
},
{
"Id": "22d113c5-94ad-5002-8e1f-79a622ca1ac6",
@ -34,7 +34,7 @@
"MonthlyPrice": 5000,
"YearlyPrice": 5000,
"IsQuantityBased": false,
"ImageUrl": "https://images.pexels.com/photos/3184465/pexels-photo-3184465.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/remote-support-contract.webp"
},
{
"Id": "39999ed6-7115-5816-bd32-5a786d68e356",
@ -45,7 +45,7 @@
"MonthlyPrice": 900,
"YearlyPrice": 3500,
"IsQuantityBased": true,
"ImageUrl": "https://images.pexels.com/photos/3184360/pexels-photo-3184360.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/user-license.webp"
},
{
"Id": "0c927e71-0c51-5a2d-899c-c22302b563a0",
@ -56,7 +56,7 @@
"MonthlyPrice": 3700,
"YearlyPrice": 3700,
"IsQuantityBased": true,
"ImageUrl": "https://images.pexels.com/photos/3184339/pexels-photo-3184339.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/remote-branch-training.webp"
},
{
"Id": "37d1aefa-cbfe-5251-9525-ca17becb596f",
@ -67,7 +67,7 @@
"MonthlyPrice": 2000,
"YearlyPrice": 2000,
"IsQuantityBased": true,
"ImageUrl": "https://images.pexels.com/photos/3184291/pexels-photo-3184291.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/extra-hourly-service.webp"
},
{
"Id": "591e9307-c825-521e-bf06-d6f647708ca4",
@ -78,7 +78,7 @@
"MonthlyPrice": 750,
"YearlyPrice": 750,
"IsQuantityBased": true,
"ImageUrl": "https://images.pexels.com/photos/404280/pexels-photo-404280.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/sms5k.webp"
},
{
"Id": "8ff856d2-30d5-5f5e-b38e-f6cebee76421",
@ -89,7 +89,7 @@
"MonthlyPrice": 1350,
"YearlyPrice": 1350,
"IsQuantityBased": true,
"ImageUrl": "https://images.pexels.com/photos/404280/pexels-photo-404280.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/sms5k.webp"
},
{
"Id": "bfa0964b-e8cb-5739-8b4a-d44773553f0a",
@ -100,7 +100,7 @@
"MonthlyPrice": 2900,
"YearlyPrice": 2900,
"IsQuantityBased": true,
"ImageUrl": "https://images.pexels.com/photos/404280/pexels-photo-404280.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/sms5k.webp"
},
{
"Id": "2b675f5f-320f-5424-8393-3dc0363ad27e",
@ -111,7 +111,7 @@
"MonthlyPrice": 5100,
"YearlyPrice": 5100,
"IsQuantityBased": true,
"ImageUrl": "https://images.pexels.com/photos/404280/pexels-photo-404280.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/sms5k.webp"
},
{
"Id": "730c2dbf-c700-506d-b216-a169cbd85a15",
@ -122,7 +122,7 @@
"MonthlyPrice": 8800,
"YearlyPrice": 8800,
"IsQuantityBased": true,
"ImageUrl": "https://images.pexels.com/photos/404280/pexels-photo-404280.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/sms5k.webp"
},
{
"Id": "53ae0678-3c01-5887-89e9-12a45bcc188f",
@ -133,7 +133,7 @@
"MonthlyPrice": 880,
"YearlyPrice": 880,
"IsQuantityBased": true,
"ImageUrl": "https://images.pexels.com/photos/1591061/pexels-photo-1591061.jpeg?auto=compress&cs=tinysrgb&w=300"
"ImageUrl": "/img/products/sms-blocking.webp"
}
]
}

View file

@ -1005,6 +1005,227 @@
"IsVisibleToClients": true,
"IsInherited": true,
"IsEncrypted": false
},
{
"Id": "e5b8222e-664c-564b-b853-d360e9a64f4c",
"Code": "App.SiteManagement.Seo.SiteUrl",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 1,
"NameKey": "App.SiteManagement.Seo.SiteUrl",
"DescriptionKey": "App.SiteManagement.Seo.SiteUrl.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "",
"DataType": "Text",
"SelectOptions": "{}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "8001a080-ff59-5bf6-9da9-5aebc2abd41b",
"Code": "App.SiteManagement.Seo.SiteName",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 2,
"NameKey": "App.SiteManagement.Seo.SiteName",
"DescriptionKey": "App.SiteManagement.Seo.SiteName.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "Sözsoft",
"DataType": "Text",
"SelectOptions": "{}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "5e336aa9-0a1a-56d2-8f9a-5a899ee343d2",
"Code": "App.SiteManagement.Seo.DefaultTitle",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 3,
"NameKey": "App.SiteManagement.Seo.DefaultTitle",
"DescriptionKey": "App.SiteManagement.Seo.DefaultTitle.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "Sözsoft",
"DataType": "Text",
"SelectOptions": "{}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "a80126cc-116e-5eaa-b31a-819738b6272d",
"Code": "App.SiteManagement.Seo.DefaultDescription",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 4,
"NameKey": "App.SiteManagement.Seo.DefaultDescription",
"DescriptionKey": "App.SiteManagement.Seo.DefaultDescription.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "",
"DataType": "Memo",
"SelectOptions": "{}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "929037b4-29dd-5e4f-8004-180dae435c3c",
"Code": "App.SiteManagement.Seo.DefaultKeywords",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 5,
"NameKey": "App.SiteManagement.Seo.DefaultKeywords",
"DescriptionKey": "App.SiteManagement.Seo.DefaultKeywords.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "",
"DataType": "Text",
"SelectOptions": "{}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "39a4b2a8-9809-5cb8-999c-738fe94a44c7",
"Code": "App.SiteManagement.Seo.DefaultImage",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 6,
"NameKey": "App.SiteManagement.Seo.DefaultImage",
"DescriptionKey": "App.SiteManagement.Seo.DefaultImage.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "/img/logo/logo-512.png",
"DataType": "Text",
"SelectOptions": "{}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "7ce5fce9-deee-5170-9ac5-b4e1f4aefa1c",
"Code": "App.SiteManagement.Seo.TwitterSite",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 7,
"NameKey": "App.SiteManagement.Seo.TwitterSite",
"DescriptionKey": "App.SiteManagement.Seo.TwitterSite.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "",
"DataType": "Text",
"SelectOptions": "{}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "787c036d-e549-5207-b6c3-f4a34b7fdf6e",
"Code": "App.SiteManagement.Seo.Locales",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 8,
"NameKey": "App.SiteManagement.Seo.Locales",
"DescriptionKey": "App.SiteManagement.Seo.Locales.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "tr,en",
"DataType": "Text",
"SelectOptions": "{}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "4e2fd424-301c-55e6-b494-b9190759fe76",
"Code": "App.SiteManagement.Seo.Robots",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 9,
"NameKey": "App.SiteManagement.Seo.Robots",
"DescriptionKey": "App.SiteManagement.Seo.Robots.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "index, follow",
"DataType": "List",
"SelectOptions": "{\"index, follow\":\"App.SiteManagement.Seo.Robots.IndexFollow\",\"noindex, nofollow\":\"App.SiteManagement.Seo.Robots.NoIndexNoFollow\"}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "5859839b-32aa-579e-8d2a-290800bbb865",
"Code": "App.SiteManagement.Seo.GoogleSiteVerification",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 10,
"NameKey": "App.SiteManagement.Seo.GoogleSiteVerification",
"DescriptionKey": "App.SiteManagement.Seo.GoogleSiteVerification.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "",
"DataType": "Text",
"SelectOptions": "{}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "c20e0c12-10bf-5cb0-99f5-eb3825276694",
"Code": "App.SiteManagement.Seo.OrganizationSameAs",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 11,
"NameKey": "App.SiteManagement.Seo.OrganizationSameAs",
"DescriptionKey": "App.SiteManagement.Seo.OrganizationSameAs.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "",
"DataType": "Memo",
"SelectOptions": "{}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "2b871f1c-bd20-58eb-85d3-012e5dd835be",
"Code": "App.SiteManagement.Seo.SitemapEnabled",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 12,
"NameKey": "App.SiteManagement.Seo.SitemapEnabled",
"DescriptionKey": "App.SiteManagement.Seo.SitemapEnabled.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "true",
"DataType": "Bool",
"SelectOptions": "{}",
"IsVisibleToClients": true,
"IsInherited": false,
"IsEncrypted": false
},
{
"Id": "43c3ab04-da10-5880-86c5-e1b32099228a",
"Code": "App.SiteManagement.Seo.SitemapExcludePaths",
"MainGroupKey": "App.SiteManagement",
"SubGroupKey": "App.SiteManagement.Seo",
"Order": 13,
"NameKey": "App.SiteManagement.Seo.SitemapExcludePaths",
"DescriptionKey": "App.SiteManagement.Seo.SitemapExcludePaths.Description",
"RequiredPermissionName": "App.SiteManagement.Seo",
"Providers": "G|D",
"DefaultValue": "",
"DataType": "Memo",
"SelectOptions": "{}",
"IsVisibleToClients": false,
"IsInherited": false,
"IsEncrypted": false
}
]
}

View file

@ -39,6 +39,9 @@ export default [
'import/resolver': {
typescript: { project: './tsconfig.eslint.json', alwaysTryTypes: true },
},
// Vite'in sanal modulleri diskte yoktur; cozucu onlari bulamaz, tipleri
// `vite-env.d.ts` uzerinden gelir.
'import/core-modules': ['virtual:pwa-register'],
},
rules: {
...reactPlugin.configs.recommended.rules,
@ -83,4 +86,17 @@ export default [
},
},
prettier,
// Derleme araclari Node'da CommonJS olarak calisir: tarayici globalleri yerine Node
// globalleri gecerlidir ve Tailwind eklentisi `require` ile yuklenir (tailwind.config.ts
// Node tarafindan okunur, bundle'a girmez).
{
files: ['tailwind.config.ts', 'postcss.config.cjs', 'twSafelistGenerator/**/*.js'],
languageOptions: {
globals: globals.node,
sourceType: 'commonjs',
},
rules: {
'@typescript-eslint/no-require-imports': 'off',
},
},
]

View file

@ -4,9 +4,27 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Sözsoft" />
<!--
Tema rengi sistem temasina gore ayrilir; tek deger koyu modda tarayici cubugunu
acik birakip sayfanin ustunde beyaz bir serit gosteriyordu.
-->
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#111827" media="(prefers-color-scheme: dark)" />
<!--
Buradaki meta yalnizca ilk boyamanin ve JavaScript calistirmayan istemcilerin
gordugu varsayilandir. Gercek deger uygulama konfigurasyonundaki SEO sozlesmesinden
gelir ve `SeoHead` tarafindan yazilir (bkz. components/shared/SeoHead.tsx).
-->
<title>Sözsoft</title>
<meta name="description" content="Sözsoft" />
<meta name="robots" content="index, follow" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Sözsoft" />
<meta property="og:title" content="Sözsoft" />
<meta property="og:description" content="Sözsoft" />
<meta property="og:image" content="/img/logo/logo-512.png" />
<meta name="twitter:card" content="summary_large_image" />
</head>
<body>
<div id="root"></div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Some files were not shown because too many files have changed in this diff Show more