サイトメニューに任意のリンクを追加する
サイトメニュー(フォルダを開いたときのパネルの並び)に出るのは、そのフォルダの子サイトだけです。よく使う別フォルダのサイトや外部の URL へのパネルを、管理者が登録して追加できるようにする改修の設計メモです。本体の標準機能ではありません。
サイトメニューの見た目を CSS で変える方法は ナビゲーションとテーマ にあります。
前提にした現行実装(1.5.8.1)
サイトメニューの出力
SiteUtilities.SiteMenu() が <div id="SiteMenu"> の中に、親へ戻るパネルの ul.nav-sites と、子サイトの ul.nav-sites.sortable を出します(SiteUtilities.cs)。子サイトの一覧は Menu() が Sites テーブルから SiteId・Title・ReferenceType・SiteSettings を取り、並び順(OrderModel)で並べます(SiteUtilities.cs)。
パネル 1 つは SiteMenu()(li の出力)と SiteMenuInnerElements()(アイコン・タイトル・件数など)です(SiteUtilities.cs)。
<li class="nav-site results has-image" data-value="{siteId}" data-type="Results">
<a href="/items/{siteId}/index">
<div class="site-icon"><img src="/items/{siteId}/binaries/SiteImageThumbnail/?{更新日時}"></div>
<span class="title">{サイト名}</span>
…件数・期限超過などの表示…
</a>
</li>- テーマ 2.0 以降では、サイト画像があれば
div.site-iconにサムネイル、無ければ種別ごとの SVG(icon-site-results.svgなど)を出します。 - サイト画像は
Binariesテーブルに保存され、サムネイルは/items/{siteId}/binaries/SiteImageThumbnail/…で取れます。 sortableのulはドラッグで並べ替えられます。
参考にするアナウンス機能
アナウンスは Service.json の AnnouncementSiteId で指定したテーブルを IssueCollection で読みます。表示の条件はページの種類(ログイン画面・トップ・その他)とチェック項目だけで、組織・グループ・ユーザー単位の出し分けはありません。また、取得に権限の条件は入りません(HtmlHeaders.cs、IssueCollection.cs)。詳細は アナウンス機能の改善 にあります。
設計
方針
| 項目 | アナウンス | 任意リンク(追加) |
|---|---|---|
| テーブル | 期限付きテーブル | 記録テーブル |
| パラメータ | AnnouncementSiteId | CustomSiteNavSiteId(追加) |
| 表示先 | 画面上部 | サイトメニューのパネル |
| 表示制御 | ページの種類 | 組織・グループ・ユーザー |
| 画像 | なし | あり |
Implem.ParameterAccessor/Parts/Service.cs に public long CustomSiteNavSiteId { get; set; } を、App_Data/Parameters/Service.json に "CustomSiteNavSiteId": 0 を足します。
マスタの項目
| 用途 | 項目 | 例 |
|---|---|---|
| パネルの名前 | Title | 社内ポータル |
| 説明(ツールチップ) | Body | 全社共通の情報ポータル |
| リンク先 | ClassA | 12345(サイト ID)、/items/12345、https://… |
| 表示順 | NumA | 100 |
| 有効・無効 | Status | 100(CompletionCode 未満で有効) |
| 対象の組織 | ClassB | 選択肢 [[Depts]]、複数選択 |
| 対象のグループ | ClassC | 選択肢 [[Groups]]、複数選択 |
| 対象のユーザー | ClassD | 選択肢 [[Users]]、複数選択 |
| アイコンの種類 | ClassE | issues・results・wikis |
| サムネイル | 添付ファイル | 画像 1 つ |
組織・グループ・ユーザーを分類項目の選択肢([[Depts]] など)にすると、標準の選択 UI で選べます。ほかの案(ID をカンマ区切りで書く、チェック項目、説明項目に JSON)より入力しやすいです。選択肢の書き方は リンク項目の [[]] 形式と JSON 形式 を参照してください。
表示順は NumA を 100・200・300 のように間を空けて付けると、あとから間に入れやすくなります。
表示の判定
図を読み込み中…
組織・グループ・ユーザーは OR で判定します。3 つとも空なら全員に出します。
複数選択の分類項目は、値を JSON 配列の文字列(例: ["1","2"])で持ちます(Column.cs)。判定では JSON として読みます。
private static bool IsVisibleCustomSiteNav(Context context, ResultModel resultModel)
{
var deptIds = Ids(resultModel.GetClass("ClassB"));
var groupIds = Ids(resultModel.GetClass("ClassC"));
var userIds = Ids(resultModel.GetClass("ClassD"));
if (!deptIds.Any() && !groupIds.Any() && !userIds.Any()) return true;
return deptIds.Contains(context.DeptId)
|| context.Groups?.Any(groupIds.Contains) == true
|| userIds.Contains(context.UserId);
}
private static List<int> Ids(string value)
{
return (value?.Deserialize<List<string>>() ?? new List<string>())
.Select(o => o.ToInt())
.Where(o => o > 0)
.ToList();
}context.DeptId は int、context.Groups は所属グループ ID の List<int> です(Context.cs)。
権限
ResultCollection で引くと権限の条件は入りません(アナウンスと同じ)。表示の出し分けは上の判定だけで行い、マスタのサイト自体は編集できる人を管理者に絞ります。
WARNING
この判定はパネルを出すかどうかだけです。リンク先のサイトの権限は変わりません。パネルが出ても、権限が無ければリンク先は開けません。
サムネイル画像
| 方式 | 長所 | 短所 |
|---|---|---|
| 1. マスタの添付ファイルの画像を使う(推奨) | 追加の保存先が要らない | 添付の中から画像を選ぶ処理が要る |
2. ClassE などに画像の URL を書く | 実装が簡単、外部の画像も使える | 外部参照の安全面(CSP など)を考える必要がある |
3. Binaries にサイト画像と同じ形で保存 | サイト画像と同じ仕組み | BinaryType の追加が要る |
| 4. 説明項目に Base64 で埋め込む | DB だけで完結 | データが大きくなる |
添付ファイルは /binaries/{guid}/show で取れます(BinariesController.cs)。添付の中で最初の画像(ContentType が image/ で始まるもの)をサムネイルにします。
パネルの出力
既存の nav-site と同じ構造にして、既存の CSS をそのまま効かせます。並びは NumA で決めるので、sortable は付けず別の ul にします。
<ul class="nav-sites custom-site-nav">
<li class="nav-site results has-image custom-link" data-value="custom-1">
<a href="/items/67890">
<div class="site-icon"><img src="/binaries/{guid}/show"></div>
<span class="title">社内ポータル</span>
</a>
</li>
</ul>出力メソッド(案)
private static HtmlBuilder CustomSiteNav(this HtmlBuilder hb, Context context)
{
var siteId = Parameters.Service.CustomSiteNavSiteId;
if (siteId <= 0) return hb;
var ss = SiteSettingsUtilities.Get(context: context, siteId: siteId);
var results = new ResultCollection(
context: context,
ss: ss,
where: Rds.ResultsWhere()
.SiteId(siteId)
.Status(_operator: $"<{Parameters.General.CompletionCode}"),
orderBy: Rds.ResultsOrderBy().NumA())
.Where(o => IsVisibleCustomSiteNav(context: context, resultModel: o))
.ToList();
return hb.Nav(
css: "cf",
_using: results.Any(),
action: () => hb.Ul(
css: "nav-sites custom-site-nav",
action: () => results.ForEach(resultModel => hb
.CustomSiteNavItem(context: context, resultModel: resultModel))));
}
private static HtmlBuilder CustomSiteNavItem(
this HtmlBuilder hb, Context context, ResultModel resultModel)
{
var url = resultModel.GetClass("ClassA");
var iconType = resultModel.GetClass("ClassE");
var thumbnailUrl = ThumbnailUrl(context: context, resultModel: resultModel);
var hasImage = thumbnailUrl != null;
var external = url?.StartsWith("http") == true;
var href = url?.StartsWith("/") == true || external
? url
: Locations.ItemIndex(context: context, id: url.ToLong());
return hb.Li(
attributes: new HtmlAttributes()
.Class(Css.Class(
"nav-site " + (iconType ?? "sites").ToLower()
+ (hasImage ? " has-image" : string.Empty),
" custom-link"))
.DataValue($"custom-{resultModel.ResultId}"),
action: () => hb
.A(
attributes: new HtmlAttributes()
.Href(href)
.Target("_blank", _using: external)
.Rel("noopener noreferrer", _using: external),
action: () => hb
.CustomSiteNavIcon(
context: context,
iconType: iconType,
thumbnailUrl: thumbnailUrl)
.Span(css: "title", action: () => hb
.Text(resultModel.Title.Value))));
}リンク先の書き方ごとの扱いです。
ClassA | 移動先 |
|---|---|
| 数字(サイト ID) | /items/{siteId}/index |
/items/… | そのまま |
https://… | 外部。target="_blank" と rel="noopener noreferrer" を付けて別タブで開く |
改修箇所
| ファイル | 内容 |
|---|---|
Implem.ParameterAccessor/Parts/Service.cs | CustomSiteNavSiteId |
App_Data/Parameters/Service.json | CustomSiteNavSiteId |
Models/Sites/SiteUtilities.cs | SiteMenu() からの呼び出し、表示判定、サムネイル、パネルの出力 |
wwwroot/src/styles/style.scss・legacy.scss | .custom-site-nav・.custom-link |
SiteMenu() まわりのメソッドには /// Fixed: のコメントが付いていて、CodeDefiner の生成対象ではありません。パラメータの追加も生成コードには影響しません。
考慮事項
- 負荷: サイトメニューを出すたびにマスタを引きます。数件〜数十件なら都度のクエリで足ります。多い場合はキャッシュを考えますが、マスタ更新時の無効化が必要です。