/**
* 掲載中一覧(list.js createBuildingCard)と同じ building-card HTML を共有。
* TOP 新着・条件検索・view=all で同一の画像集約・カード構造を使う。
*/
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
root.BuildingListCard = factory();
}
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
'use strict';
var LCI = typeof ListCardImages !== 'undefined' ? ListCardImages : null;
var PPI = typeof PropertyPublicImageUrl !== 'undefined' ? PropertyPublicImageUrl : null;
function text(v, fb) {
var s = v == null ? '' : String(v).trim();
return s || (fb == null ? '' : fb);
}
function escAttr(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(//g, '>');
}
function publicAsset(path) {
if (typeof globalThis !== 'undefined' && globalThis.RakusumoPublicPath && typeof globalThis.RakusumoPublicPath.asset === 'function') {
return globalThis.RakusumoPublicPath.asset(path);
}
var pre =
typeof globalThis !== 'undefined' && typeof globalThis.__RAKUSUMO_PUBLIC_PATH_PREFIX__ === 'string'
? globalThis.__RAKUSUMO_PUBLIC_PATH_PREFIX__
: '';
return (pre === '' ? '' : pre) + '/' + String(path || '').replace(/^\/+/, '');
}
function publicPathPrefix() {
if (typeof globalThis !== 'undefined' && globalThis.RakusumoPublicPath && typeof globalThis.RakusumoPublicPath.getPrefix === 'function') {
return globalThis.RakusumoPublicPath.getPrefix();
}
if (typeof globalThis !== 'undefined' && typeof globalThis.__RAKUSUMO_PUBLIC_PATH_PREFIX__ === 'string') {
return globalThis.__RAKUSUMO_PUBLIC_PATH_PREFIX__;
}
return '';
}
function publicSiteHref(segment) {
if (typeof globalThis !== 'undefined' && globalThis.RakusumoPublicPath && typeof globalThis.RakusumoPublicPath.href === 'function') {
return globalThis.RakusumoPublicPath.href(segment);
}
var pre = publicPathPrefix();
var seg = String(segment == null ? '' : segment).replace(/^\/+|\/+$/g, '');
if (!seg) return pre === '' ? '/' : pre + '/';
return (pre === '' ? '' : pre) + '/' + seg + '/';
}
var LISTING_ICON_BASE = publicAsset('images/icons/listing/');
function propertyNoImageFallbackUrl(kind) {
if (PPI && PPI.noImageFallbackUrl) return PPI.noImageFallbackUrl(kind);
var k =
PPI && PPI.normalizeNoImageKind ? PPI.normalizeNoImageKind(kind) : String(kind || 'property').trim() || 'property';
var path =
k === 'property-card'
? publicAsset('image/no-image-property-card.png')
: k === 'member-card'
? publicAsset('image/member-property-card.webp')
: publicAsset('image/no-image-property.png');
var attr =
k === 'property-card'
? 'data-property-card-no-image-v'
: k === 'member-card'
? 'data-member-card-no-image-v'
: 'data-property-no-image-v';
try {
var v = document.documentElement && document.documentElement.getAttribute(attr);
if (v) return path + '?v=' + encodeURIComponent(String(v).trim());
} catch (_e) {}
return path;
}
function resolvePropertyNoImageUrl(customUrl, kind) {
if (PPI && PPI.resolveNoImageUrl) return PPI.resolveNoImageUrl(customUrl, kind);
var u = text(customUrl, '');
return u || propertyNoImageFallbackUrl(kind);
}
function propertyNoImageImgOnerrorAttr(kind) {
if (PPI && PPI.imgOnerrorAttr) return PPI.imgOnerrorAttr(kind);
var k = String(kind || 'property').trim() || 'property';
var url = propertyNoImageFallbackUrl(k).replace(/'/g, "\\'");
return (
' data-no-image-kind="' +
k +
'" onerror="this.onerror=null;this.src=\'' +
url +
'\';this.classList.add(\'property-no-image-img\');"'
);
}
/** 参考デザインで使用するアイコンのみ */
var LISTING_ICON_FILES = {
address: 'juusyo.png',
station: 'eki.png',
builtYm: 'tikunensu.png',
structure: 'kouzou1.png',
floors: 'kaisu1.png',
layout: 'madori1.png',
area: 'menseki1.png',
deposit: 'sikikin.png',
keyMoney: 'reikin.png',
mgmtFee: 'kanrihi.png',
buildingRooms: 'kouzou1.png',
};
function listingIconImg(key, extraClass) {
var file = LISTING_ICON_FILES[key];
if (!file) return '';
var cls = 'listing-card__icon' + (extraClass ? ' ' + extraClass : '');
return (
' '
);
}
/** アイコン+文字(許可されたアイコンのみ) */
function listingInlineRow(iconKey, innerHtml, mod) {
var icon = iconKey ? listingIconImg(iconKey, '') : '';
return (
'' +
icon +
innerHtml +
' '
);
}
function pickRowField(row, keys) {
if (!row) return '';
for (var i = 0; i < keys.length; i++) {
var v = text(row[keys[i]], '');
if (v) return v;
}
var norm = row.normalized && typeof row.normalized === 'object' ? row.normalized : {};
for (var j = 0; j < keys.length; j++) {
var nv = text(norm[keys[j]], '');
if (nv) return nv;
}
return '';
}
function parsePrimaryTransport(row, t) {
var transports = Array.isArray(row.transports) ? row.transports : [];
var a = transports[0] || {};
var line = t(a.line, '');
var station = t(a.station, '');
if (station && station.indexOf('駅') < 0) station += '駅';
var walk = a.walkMinutes ? '徒歩' + a.walkMinutes + '分' : '';
var busParts = [];
if (a.busMinutes) busParts.push('バス' + a.busMinutes + '分');
if (a.busStop) busParts.push(t(a.busStop, ''));
if (a.busStopWalkMinutes) busParts.push('徒歩' + a.busStopWalkMinutes + '分');
return {
stationText: [line, station].filter(Boolean).join(' '),
walk: walk,
bus: busParts.join(' '),
};
}
function renderBuildingAccessOneLineHtml(rep, ctx, h) {
var t = ctx.text || text;
var tr = parsePrimaryTransport(rep, t);
var parts = [];
if (tr.stationText) parts.push(tr.stationText);
if (tr.walk) parts.push(tr.walk);
if (tr.bus) parts.push(tr.bus);
if (!parts.length) {
var access = ctx.firstTransport ? t(ctx.firstTransport(rep), '—') : '—';
return h(access);
}
return h(parts.join(' '));
}
function resolveBuildingTitleLengthClass(buildingName) {
var len = String(buildingName == null ? '' : buildingName).trim().length;
if (len >= 28) return 'building-card__title--xlong';
if (len >= 18) return 'building-card__title--long';
return '';
}
function renderBuildingTitleHtml(buildingName, titleClass, detailUrl, h, a) {
var titleInner = detailUrl
? '' + h(buildingName) + ' '
: h(buildingName);
return '
' + titleInner + ' ';
}
/* 新着バッジ判定用: 公開日文字列の優先順位(PHP properties_row_own_site_first_published_at_string と同じ並び)。
自社サイト初回公開 instant → 登録日時 → 更新日時。新しい保存field・APIは追加しない(既存項目を読むだけ)。 */
function firstPublishedAtStringForNewBadge(rep) {
var r = rep || {};
var instantCandidates = [r.propertyPublishedAt, r.property_published_at, r.published_at, r.publishedAt];
for (var i = 0; i < instantCandidates.length; i++) {
var s = instantCandidates[i] == null ? '' : String(instantCandidates[i]).trim();
if (s) return s;
}
var createdCandidates = [r.created_at, r.createdAt];
for (var j = 0; j < createdCandidates.length; j++) {
var c = createdCandidates[j] == null ? '' : String(createdCandidates[j]).trim();
if (c) return c;
}
var updatedCandidates = [r.updated_at, r.updatedAt];
for (var k = 0; k < updatedCandidates.length; k++) {
var u = updatedCandidates[k] == null ? '' : String(updatedCandidates[k]).trim();
if (u) return u;
}
return '';
}
/* PHP properties_parse_sortable_timestamp と同じ考え方: ATBBカレンダー日付(YYYY/M/D)は00:00:00起点、それ以外はDate.parseへ委譲 */
function parseSortableTimestampForNewBadge(s) {
var t = s == null ? '' : String(s).trim();
if (!t) return 0;
var atbb = t.match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/);
if (atbb) {
var y = atbb[1];
var m = String(atbb[2]).padStart(2, '0');
var d = String(atbb[3]).padStart(2, '0');
var parsedAtbb = Date.parse(y + '-' + m + '-' + d + 'T00:00:00');
return isNaN(parsedAtbb) ? 0 : Math.floor(parsedAtbb / 1000);
}
var parsed = Date.parse(t);
return isNaN(parsed) ? 0 : Math.floor(parsed / 1000);
}
/* 新着バッジ表示判定: 公開日から days 日以内のみ true。手動トグルの ownSiteSettings.isNew とは別判定・非置換。 */
function isRecentlyPublished(rep, days) {
var d = typeof days === 'number' ? days : 3;
var ts = parseSortableTimestampForNewBadge(firstPublishedAtStringForNewBadge(rep));
if (!ts) return false;
var diffSec = Math.floor(Date.now() / 1000) - ts;
if (diffSec < 0) return false;
return diffSec <= d * 86400;
}
/** 「新着」バッジ(写真左上)。公開日から3日を超えたら自動的に空文字=非表示。 */
function renderNewBadgeHtml(rep) {
if (!isRecentlyPublished(rep, 3)) return '';
return '新着 ';
}
function renderBuildingHeadListingHtml(photoHtml, buildingName, typeDisp, typeMod, address, rep, detailUrl, ctx, h, a, rooms) {
var titleLenClass = resolveBuildingTitleLengthClass(buildingName);
var titleClass = 'building-card__title' + (titleLenClass ? ' ' + titleLenClass : '');
var typeBadge = typeDisp ? renderTypeBadgeInInfoHtml(typeDisp, typeMod, h, a) : '';
var specsInner = renderBuildingSpecsInlineHtml(rep, ctx, rooms);
return (
'' +
'
' +
(typeBadge ? '
' + typeBadge + '
' : '') +
'
' +
renderBuildingTitleHtml(buildingName, titleClass, detailUrl, h, a) +
'
' +
'
' +
'
' +
photoHtml +
renderNewBadgeHtml(rep) +
'
' +
'
'
);
}
/** true / 1 / "1" / "true" のみを会員限定フラグの成立値として扱う("0"等の文字列誤判定を避ける)。 */
function normalizeMemberOnlyFlag(v) {
return v === true || v === 1 || v === '1' || v === 'true';
}
function isMemberOnlyRow(row) {
row = row || {};
if (normalizeMemberOnlyFlag(row._memberRedacted)) return true;
var own = row.ownSiteSettings || {};
return normalizeMemberOnlyFlag(own.membersOnly);
}
function isMemberOnlyGroup(group) {
group = group || {};
var rep = group.representative || {};
if (isMemberOnlyRow(rep)) return true;
var rooms = group.rooms || [];
for (var i = 0; i < rooms.length; i++) {
if (isMemberOnlyRow(rooms[i])) return true;
}
return false;
}
function memberPublicHref(segment) {
return publicSiteHref(segment);
}
function memberCoarseAreaFromRow(row) {
row = row || {};
if (row.memberCoarseArea) return text(row.memberCoarseArea, '');
var pref = text(row.prefecture || row.pref || row['都道府県'], '');
var city = text(row.city || row['市区町村'], '');
if (pref && city) return pref + city;
return '';
}
function memberSummaryAddress(rep, ctx, t) {
var addr = '';
if (ctx && typeof ctx.formatCardAddress === 'function') {
addr = t(ctx.formatCardAddress(rep), '');
}
if (!addr) addr = t(rep.address || rep['所在地'], '');
if (!addr) addr = memberCoarseAreaFromRow(rep);
if (!addr || addr === '—' || addr === '-' || addr === '-') return '';
return addr;
}
function memberGuestReturnPath() {
return memberPublicHref('search') + '?membersOnly=1';
}
function memberAuthHrefWithRedirect(baseHref, redirectUrl) {
var base = text(baseHref, '');
var redirect = text(redirectUrl, '');
if (!base) return '';
// 同一サイト内の公開パス配下のみ。外部・javascript:・プロトコル相対は付けない
var pre = publicPathPrefix();
var allowedPrefix = pre === '' ? '/' : pre + '/';
if (
!redirect ||
redirect.indexOf(allowedPrefix) !== 0 ||
redirect.indexOf('//') !== -1 ||
redirect.indexOf('..') !== -1 ||
/^[a-z][a-z0-9+.\-]*:/i.test(redirect)
) {
return base;
}
var sep = base.indexOf('?') >= 0 ? '&' : '?';
return base + sep + 'redirect=' + encodeURIComponent(redirect);
}
function renderMemberGuestRoomColHtml(r, ctx) {
var h = ctx.escHtml || escHtml;
var floorLabel = ctx.formatRoomFloorLabel
? ctx.formatRoomFloorLabel(r)
: formatRoomFloorLabel(r, ctx.text || text);
var floorHtml = floorLabel ? '' + h(floorLabel) + '
' : '';
return '' + floorHtml + '
';
}
function renderMemberGuestConditionGridHtml(r, ctx) {
var h = ctx.escHtml || escHtml;
var t = ctx.text || text;
var layout = t(r.layout, '—');
var area = ctx.formatAreaList ? ctx.formatAreaList(r.area) : t(r.area, '—');
var dep = feeValueOnly(ctx.formatDepositLabel ? ctx.formatDepositLabel(r) : '', '敷金');
var key = feeValueOnly(ctx.formatKeyMoneyLabel ? ctx.formatKeyMoneyLabel(r) : '', '礼金');
// 未公開の敷金・礼金は空欄ではなく「会員限定」と明示(枠が不自然にならない)
if (!dep) dep = '会員限定';
if (!key) key = '会員限定';
return (
'' +
renderPropertyConditionGridItemHtml('間取り', layout, h) +
renderPropertyConditionGridItemHtml('専有面積', area, h) +
renderPropertyConditionGridItemHtml('敷金', dep, h) +
renderPropertyConditionGridItemHtml('礼金', key, h) +
'
'
);
}
function renderMemberTeaserHtml(group, ctx) {
ctx = ctx || {};
group = enrichGroup(group || {});
var h = ctx.escHtml || escHtml;
var a = ctx.escAttr || escAttr;
var t = ctx.text || text;
var rep = group.representative || {};
var roomsSrc = group.rooms && group.rooms.length ? group.rooms : [rep];
var r = roomsSrc[0] || rep;
var publicTitle = t(rep.memberLabel, '会員限定物件') || '会員限定物件';
var buildingName = publicTitle;
var address = memberSummaryAddress(rep, ctx, t);
var typeRaw =
rep.property_type ||
rep.propertyType ||
rep.type ||
rep['物件種別'] ||
rep['建物種別'] ||
rep['種別'] ||
rep.building_type;
var typeDisp = ctx.formatPropertyType ? ctx.formatPropertyType(typeRaw) : t(typeRaw, '');
var typeMod = propertyTypeBadgeModifier(typeDisp || typeRaw);
// 物件ID付き詳細URLは未ログイン応答に載せない(return は会員一覧)
var returnPath = memberGuestReturnPath();
var registerHref = memberAuthHrefWithRedirect(
t(rep.memberRegisterUrl, '') || memberPublicHref('member/register'),
returnPath,
);
var loginHref = memberAuthHrefWithRedirect(
t(rep.memberLoginUrl, '') || memberPublicHref('member/login'),
returnPath,
);
var noImageUrl = resolvePropertyNoImageUrl(t(ctx.noImageUrl, ''));
// 会員限定メイン画像は管理画面「物件共通画像設定 > 物件一覧用 会員限定物件画像」を必ず使用する。
// rep.mainImageUrl / memberOnlyTeaserImageUrl は enrich で間取り図等が昇格している可能性が
// あるため、未ログインカードのメイン画像としては一切参照しない(漏えい・間取り図昇格防止)。
// フォールバック順: 1.設定URL(ctx.memberPropertyImageUrl) → 2.同梱member-property-card.webp
// → 3.onerror時は member-card 用 NO IMAGE 経路(既存 onerror 属性)。
var memberFallback =
typeof propertyNoImageFallbackUrl === 'function'
? propertyNoImageFallbackUrl('member-card')
: publicAsset('image/member-property-card.webp');
if (ctx && ctx.memberPropertyImageUrl) {
var customMember = String(ctx.memberPropertyImageUrl || '').trim();
if (customMember) memberFallback = customMember;
}
var photoSrc = memberFallback;
var photoHtml =
'' +
'
' +
'
' +
'
';
var memberBadge =
'' + h(publicTitle) + ' ';
var typeBadge = typeDisp ? renderTypeBadgeInInfoHtml(typeDisp, typeMod, h, a) : '';
var titleLenClass = resolveBuildingTitleLengthClass(buildingName);
var titleClass = 'building-card__title' + (titleLenClass ? ' ' + titleLenClass : '');
var specsInner = renderBuildingSpecsInlineHtml(rep, ctx, [r]);
var addressLine = address
? '' +
listingInlineRow('address', '' + h(address) + ' ', 'address') +
'
'
: '';
var accessInner = renderBuildingAccessOneLineHtml(rep, ctx, h);
var accessLine =
accessInner && accessInner !== '—'
? '' +
listingInlineRow('station', '' + accessInner + ' ', 'access') +
'
'
: '';
var headHtml =
'' +
'
' +
'
' +
memberBadge +
(typeBadge ? '
' + typeBadge + '
' : '') +
'
' +
'
' +
renderBuildingTitleHtml(buildingName, titleClass, '', h, a) +
'
' +
'
' +
photoHtml +
'
' +
'
';
// 公開許可済み間取り図は専用フィールド floorPlanImageUrl を第一候補とする
// (redact後の images[] は会員共通画像のみのため、images経由では取得しない)
var roomImg =
t(rep.floorPlanImageUrl || r.floorPlanImageUrl, '') || pickRoomFloorplanImage(r, '');
var roomThumbHtml = renderCardImageSliderHtml(roomImg, publicTitle, roomImg ? [roomImg] : [], {
wrapperBase: 'room-row__thumb',
imageKinds: roomImg ? ['floorplan'] : [],
roomThumbNoSlider: true,
noImageUrl: noImageUrl,
});
var rentColHtml = ctx.formatRentBlock ? ctx.formatRentBlock(r) : renderRentColHtml(r, ctx);
var conditionGridHtml = renderMemberGuestConditionGridHtml(r, ctx);
var roomColHtml = renderMemberGuestRoomColHtml(r, ctx);
var roomSummaryHtml = renderRoomSummaryHtml('', r, ctx);
var ctaDetailClass = t(ctx.ctaDetailClass, 'list-cta list-cta--line');
var ctaInquiryClass = t(ctx.ctaInquiryClass, 'list-cta list-cta--go');
var roomRowHtml =
'' +
roomColHtml +
'
' +
'
' +
roomThumbHtml +
'
' +
'
' +
roomSummaryHtml +
rentColHtml +
'
' +
conditionGridHtml +
'
' +
renderListCtaLink(a, h, ctaInquiryClass + ' building-card__member-cta-register', registerHref, '無料会員登録', 'inquiry') +
renderListCtaLink(a, h, ctaDetailClass + ' building-card__member-cta-login', loginHref, '会員ログイン', 'detail') +
'
';
var extraArticleClass = t(ctx.extraArticleClass, '');
var articleCls =
'building-card building-card--member-teaser building-card--member-summary building-card--listing' +
(extraArticleClass ? ' ' + extraArticleClass : '');
return (
'' +
headHtml +
'' +
renderBuildingRoomsHeadHtml() +
roomRowHtml +
'
'
);
}
function sanitizeUrl(u) {
u = text(u, '');
if (!u) return '';
if (PPI && PPI.sanitize) return PPI.sanitize(u);
return u;
}
function enrichBuiltYmFields(row) {
if (!row || typeof row !== 'object') return row;
var ym = String(row.built_ym || row.builtYm || row['築年月'] || '').trim();
var month = String(row.built_month || row.builtMonth || '').trim();
if (!month && ym) {
var iso = ym.match(/(19|20)\d{2}[-/.](\d{1,2})/);
if (iso) {
row.built_month = iso[2];
} else {
var jp = ym.match(/(19|20)\d{2}\s*年\s*(\d{1,2})\s*月/u);
if (jp) row.built_month = jp[2];
}
}
if (!ym && (row.built_year || row.builtYear)) {
var y = String(row.built_year || row.builtYear || '').trim();
if (y) row.built_ym = y;
}
return row;
}
function enrichRow(row) {
row = enrichBuiltYmFields(row);
if (LCI && LCI.enrichListRowForCard) return LCI.enrichListRowForCard(row);
return row;
}
function enrichGroup(group) {
group = group || { representative: {}, rooms: [] };
if (group.representative) group.representative = enrichRow(group.representative);
if (group.rooms) {
group.rooms = group.rooms.map(function (r) {
return enrichRow(r);
});
}
return group;
}
function normalizeImageKind(kindText) {
var t = text(kindText, '').toLowerCase();
if (!t) return 'other';
if (LCI && LCI.classifyListImageKind && kindText && typeof kindText === 'object') {
return LCI.classifyListImageKind(kindText);
}
var known = ['exterior', 'building', 'facade', 'appearance', 'room', 'living', 'interior', 'common', 'other', 'floorplan'];
if (known.indexOf(t) >= 0) return t;
if (t.indexOf('外観') >= 0 || t.indexOf('exterior') >= 0) return 'exterior';
if (t.indexOf('建物') >= 0 || t.indexOf('building') >= 0) return 'building';
if (t.indexOf('間取') >= 0 || t.indexOf('floorplan') >= 0 || t.indexOf('madori') >= 0) return 'floorplan';
return 'other';
}
function collectCardImages(row) {
var out = [];
var seen = Object.create(null);
var items = LCI && LCI.collectCardImageItemsForRow ? LCI.collectCardImageItemsForRow(row) : [];
items.forEach(function (item) {
var src = sanitizeUrl(text(item && item.src, ''));
if (!src || seen[src]) return;
seen[src] = true;
var kind =
LCI && LCI.classifyListImageKind && item.raw
? LCI.classifyListImageKind(item.raw)
: normalizeImageKind(text(item && item.kind, ''));
out.push({ src: src, kind: kind, raw: item.raw });
});
return out;
}
function collectOrderedUrlsForBuildingGroup(group) {
if (LCI && LCI.collectOrderedUrlsForBuildingGroup) {
return LCI.collectOrderedUrlsForBuildingGroup(group);
}
var ordered = [];
var seen = Object.create(null);
function pushUrl(u) {
u = sanitizeUrl(text(u, ''));
if (!u || seen[u]) return;
seen[u] = true;
ordered.push(u);
}
var rep = group && group.representative;
if (rep && LCI && LCI.collectOrderedUrlsForRow) {
LCI.collectOrderedUrlsForRow(rep).forEach(pushUrl);
} else if (rep) {
collectCardImages(rep).forEach(function (it) {
pushUrl(it.src);
});
}
(group && group.rooms ? group.rooms : []).forEach(function (r) {
if (LCI && LCI.collectOrderedUrlsForRow) {
LCI.collectOrderedUrlsForRow(r).forEach(pushUrl);
} else {
collectCardImages(r).forEach(function (it) {
pushUrl(it.src);
});
}
});
return ordered;
}
function urlKindOverlayForBuildingGroup(group) {
if (LCI && LCI.urlKindOverlayForBuildingGroup) {
return LCI.urlKindOverlayForBuildingGroup(group);
}
var o = Object.create(null);
collectCardImages(group.representative).forEach(function (it) {
if (it.src) o[it.src] = it.kind || 'other';
});
(group.rooms || []).forEach(function (r) {
collectCardImages(r).forEach(function (it) {
if (it.src) o[it.src] = it.kind || 'other';
});
});
return o;
}
function urlKindOverlayFromRow(row) {
if (LCI && LCI.urlKindOverlayFromRow) return LCI.urlKindOverlayFromRow(row);
var o = Object.create(null);
collectCardImages(row).forEach(function (it) {
if (it.src) o[it.src] = it.kind || 'other';
});
return o;
}
function collectOrderedUrlsForRow(row) {
if (LCI && LCI.collectOrderedUrlsForRow) return LCI.collectOrderedUrlsForRow(row);
return collectCardImages(row)
.map(function (it) {
return it.src;
})
.filter(Boolean);
}
function buildUrlKindMapFromRow(row) {
if (LCI && LCI.urlKindOverlayFromRow) return LCI.urlKindOverlayFromRow(row);
return urlKindOverlayFromRow(row);
}
function mergeCardSliderSlides(mainImage, orderedUrls, row, urlKindOverlay) {
var urls = [];
var kinds = [];
var kindMap = Object.assign(Object.create(null), buildUrlKindMapFromRow(row), urlKindOverlay || {});
var seen = Object.create(null);
function kindOf(u) {
return kindMap[u] || 'other';
}
function push(u) {
u = sanitizeUrl(text(u, ''));
if (!u || seen[u]) return;
seen[u] = true;
urls.push(u);
kinds.push(kindOf(u));
}
push(mainImage);
if (Array.isArray(orderedUrls)) {
for (var j = 0; j < orderedUrls.length; j++) push(orderedUrls[j]);
}
collectCardImages(row).forEach(function (it) {
push(it.src);
});
return { urls: urls, kinds: kinds };
}
function rotateSlides(urls, kinds) {
if (LCI && LCI.rotateSlidesByPreferredKind) {
return LCI.rotateSlidesByPreferredKind(urls, kinds);
}
return { urls: urls, kinds: kinds };
}
var BUILDING_KIND_PREF = [
'exterior',
'building',
'facade',
'appearance',
'room',
'living',
'interior',
'common',
'other',
'floorplan',
];
function pickByKind(items, preferred) {
if (!Array.isArray(items) || !items.length) return '';
var i;
for (i = 0; i < preferred.length; i++) {
var k = preferred[i];
var hit = items.find(function (x) {
return normalizeImageKind(text(x && x.kind, '')) === k && text(x && x.src, '');
});
if (hit) return sanitizeUrl(hit.src);
}
var any = items.find(function (x) {
return text(x && x.src, '');
});
return any ? sanitizeUrl(any.src) : '';
}
function pickBuildingImageForGroup(group) {
var all = [];
if (group && group.representative) {
all = all.concat(collectCardImages(group.representative));
}
(group.rooms || []).forEach(function (r) {
all = all.concat(collectCardImages(r));
});
return pickByKind(all, BUILDING_KIND_PREF);
}
function pickRoomFloorplanImage(row, fallback) {
var items = collectCardImages(row);
var src = pickByKind(items, ['floorplan', 'madori', '間取り', 'other', 'interior']);
return src || fallback || '';
}
function cardPhotoSliderInnerClass(wrapperBase) {
var w = text(wrapperBase, 'building-card__photo');
if (w.indexOf('building-card__photo') >= 0) {
return 'building-card__photo-inner card-photo-slider-inner';
}
if (w.indexOf('room-row__thumb') >= 0) {
return 'list-card-thumb-inner card-photo-slider-inner';
}
return 'card-photo-slider-inner';
}
function renderCardImageSliderHtml(mainImage, title, mergedUrls, opts) {
opts = opts || {};
var roomThumbNoSlider = !!opts.roomThumbNoSlider;
var kinds =
Array.isArray(opts.imageKinds) && opts.imageKinds.length === mergedUrls.length
? opts.imageKinds
: mergedUrls.map(function () {
return 'photo';
});
var firstImage = sanitizeUrl(text(mainImage, ''));
if (!firstImage && mergedUrls.length) firstImage = mergedUrls[0];
var noImageKind = text(opts.noImageKind, 'property') || 'property';
var noImageUrl = resolvePropertyNoImageUrl(opts.noImageUrl, noImageKind);
var isNoImageFallback = false;
if (!firstImage) {
firstImage = noImageUrl;
mergedUrls = [firstImage];
kinds = ['photo'];
isNoImageFallback = true;
}
var hasMany = mergedUrls.length > 1 && !roomThumbNoSlider;
var wrapperBase = text(opts.wrapperBase, 'building-card__photo');
var firstKind = kinds[0] === 'floorplan' ? 'floorplan' : 'photo';
var wrapCls = wrapperBase + (firstKind === 'floorplan' ? ' is-floorplan' : '') + (hasMany ? ' js-card-slider' : '');
var alt = isNoImageFallback ? '物件画像は準備中です' : text(title, '物件画像');
var imgExtraClass = isNoImageFallback ? ' property-no-image-img' : '';
var imgOnerror = propertyNoImageImgOnerrorAttr(noImageKind);
var inner = '';
if (firstImage) {
var imgHtml =
'';
if (roomThumbNoSlider) {
inner = imgHtml;
} else if (hasMany) {
inner =
' ' +
'' +
imgHtml +
'
' +
' ';
} else {
inner = imgHtml;
}
} else {
inner = '
';
}
return '' + inner + '
';
}
function renderTypeBadgeOnPhotoHtml(typeDisp, typeMod, h, a) {
if (!typeDisp) return '';
var cls =
'building-card__type-badge building-card__type-badge--on-photo' +
(typeMod ? ' building-card__type-badge--' + typeMod : '');
return '' + h(typeDisp) + ' ';
}
function injectTypeBadgeIntoPhotoHtml(photoHtml, typeDisp, typeMod, escFns) {
escFns = escFns || {};
var badge = renderTypeBadgeOnPhotoHtml(
typeDisp,
typeMod,
escFns.escHtml || escHtml,
escFns.escAttr || escAttr,
);
if (!badge || !photoHtml) return photoHtml;
var closeIdx = photoHtml.lastIndexOf('');
if (closeIdx < 0) return photoHtml;
return photoHtml.slice(0, closeIdx) + badge + photoHtml.slice(closeIdx);
}
function propertyTypeBadgeModifier(rawOrLabel) {
var s = String(rawOrLabel == null ? '' : rawOrLabel).replace(/\s+/g, '');
if (!s || s === '-' || s === '―') return '';
if (s.indexOf('マンション') >= 0) return 'mansion';
if (s.indexOf('アパート') >= 0) return 'apartment';
if (s.indexOf('テラス') >= 0) return 'terrace';
if (s.indexOf('戸建') >= 0) return 'house';
return '';
}
function normalizeFloorDigits(value) {
var s = String(value == null ? '' : value).trim();
if (!s || s === '0') return '';
return s.replace(/階|部分/g, '').trim();
}
/** 建物階数のみ(部屋所在階・「2階2階部分」混在は除外) */
function parseBuildingFloorsCount(value) {
var s = String(value == null ? '' : value).trim();
if (!s || s === '0') return '';
var mBuild = s.match(/(\d{1,2})\s*階\s*建/u);
if (mBuild) return mBuild[1];
if (/部分/u.test(s) || (s.match(/階/g) || []).length > 1) {
return '';
}
var mSingle = s.match(/^(\d{1,2})\s*階$/u);
if (mSingle) return mSingle[1];
var digits = s.replace(/[^\d]/g, '');
if (/^\d{1,2}$/.test(digits)) {
var n = parseInt(digits, 10);
if (n >= 1 && n <= 60) return String(n);
}
return '';
}
/** 部屋所在階(先頭の N階 のみ) */
function parseRoomFloorCount(value) {
var s = String(value == null ? '' : value).trim();
if (!s || s === '0') return '';
var m = s.match(/(\d{1,2})\s*階/u);
if (m) return m[1];
if (/^\d{1,2}$/.test(s)) return s;
return '';
}
function sanitizeBuiltYmRaw(rep, t) {
var year = t(rep.built_year || rep.builtYear || '', '');
var month = t(rep.built_month || rep.builtMonth || '', '');
var raw = t(rep.built_ym || rep.builtYm || '', '');
if (!raw && year) {
raw = year + (month ? '年' + month : '');
}
if (!raw) raw = t(rep['築年月'] || '', '');
raw = String(raw).trim();
if (!raw) return '';
if (month && !/\d{1,2}\s*月/u.test(raw) && !/\d{4}[-/.]\d{1,2}/u.test(raw)) {
var yOnly = raw.match(/(\d{4})\s*年?/u);
if (yOnly) {
raw = yOnly[1] + '年' + month;
}
}
raw = raw.replace(/\s*[·・]\s*.*$/u, '').trim();
raw = raw.replace(/\s*[\d0-9]+\s*階.*$/u, '').trim();
return raw;
}
function formatBuiltYmDisplay(rep, t) {
var raw = sanitizeBuiltYmRaw(rep, t);
if (!raw || raw === '—' || raw === '-') return '—';
var m = String(raw).match(/(\d{4})\s*[年/\-.]?\s*(\d{1,2})?/u);
if (m) {
var monthPart =
m[2] !== undefined && String(m[2]).trim() !== ''
? String(parseInt(m[2], 10)).padStart(2, '0')
: '';
return m[1] + '年' + (monthPart !== '' ? monthPart + '月' : '');
}
return raw;
}
function formatStructureLabel(rep, t) {
return t(
rep.structure ||
rep.buildingStructure ||
rep.building_structure ||
rep.structureType ||
rep['構造'] ||
'',
'—',
);
}
function formatBuildingFloorsLabel(rep, t) {
var raw =
rep.building_floors || rep.buildingFloors || rep.total_floors || rep.totalFloors || rep['建物階建'];
var bf = parseBuildingFloorsCount(t(raw, ''));
if (!bf) return '—';
return bf + '階建';
}
var BUILDING_META_BUILT_KEYS = ['built_ym', 'builtYm', 'built_year', 'builtYear', '築年月', '築年'];
var BUILDING_META_STRUCTURE_KEYS = [
'structure',
'buildingStructure',
'building_structure',
'structureType',
'構造',
];
var BUILDING_META_FLOORS_KEYS = [
'building_floors',
'buildingFloors',
'total_floors',
'totalFloors',
'建物階建',
];
function isPresentBuildingMetaRaw(v) {
var s = v == null ? '' : String(v).trim();
return s !== '' && s !== '—' && s !== '-' && s !== '-';
}
function pickFirstPresentField(row, keys) {
if (!row) return '';
for (var i = 0; i < keys.length; i++) {
var v = row[keys[i]];
if (isPresentBuildingMetaRaw(v)) return v;
}
return '';
}
function pickFirstPresentFieldFromRooms(rooms, keys) {
if (!Array.isArray(rooms)) return '';
for (var i = 0; i < rooms.length; i++) {
var v = pickFirstPresentField(rooms[i], keys);
if (v !== '') return v;
}
return '';
}
/**
* 代表行に築年・構造・階数が無いとき、同じ建物の rooms から最初の有効値を補完する。
*/
function resolveBuildingMetaRow(rep, rooms) {
var merged = Object.assign({}, rep || {});
if (!isPresentBuildingMetaRaw(pickFirstPresentField(merged, BUILDING_META_BUILT_KEYS))) {
var built = pickFirstPresentFieldFromRooms(rooms, BUILDING_META_BUILT_KEYS);
if (built !== '') merged.built_ym = built;
}
if (!isPresentBuildingMetaRaw(pickFirstPresentField(merged, BUILDING_META_STRUCTURE_KEYS))) {
var structure = pickFirstPresentFieldFromRooms(rooms, BUILDING_META_STRUCTURE_KEYS);
if (structure !== '') merged.structure = structure;
}
if (!isPresentBuildingMetaRaw(pickFirstPresentField(merged, BUILDING_META_FLOORS_KEYS))) {
var floors = pickFirstPresentFieldFromRooms(rooms, BUILDING_META_FLOORS_KEYS);
if (floors !== '') merged.building_floors = floors;
}
return merged;
}
function formatRoomFloorLabel(r, t) {
var floor = parseRoomFloorCount(
t(r.floor || r.current_floor || r.currentFloor || r['所在階'] || '', ''),
);
if (!floor) return '';
return floor + '階';
}
function formatRoomNoLarge(roomRaw) {
return formatRoomTitleInGroup(roomRaw);
}
function feeValueOnly(label, prefix) {
if (!label || label === '—' || label === '-' || label === '-') return '';
var s = String(label).trim();
if (s.indexOf(prefix) === 0) {
var rest = s.slice(prefix.length).trim();
if (!rest || rest === '—' || rest === '-' || rest === '-') return '';
return rest;
}
return s;
}
function splitRentManYenDisplay(rentMain) {
var s = text(rentMain, '');
if (!s || s === '—' || s === '要確認') {
return { main: s || '—', unit: '', hasMonthly: false };
}
var m = s.match(/^(.+?)(万円)$/u);
if (m) return { main: m[1], unit: m[2], hasMonthly: true };
return { main: s, unit: '', hasMonthly: false };
}
function splitFeeMonthsDisplay(value) {
var s = text(value, '');
if (!s || s === '—') return { main: s || '—', unit: '' };
var m = s.match(/^(.+?)(ヶ月|か月)$/u);
if (m) return { main: m[1], unit: m[2] };
return { main: s, unit: '' };
}
function renderFeeCardValueHtml(value, h) {
var parts = splitFeeMonthsDisplay(value);
if (!parts.unit) {
return '' + h(parts.main) + ' ';
}
return (
'' +
'' +
h(parts.main) +
' ' +
'' +
h(parts.unit) +
' '
);
}
function renderBuildingSpecsInlineHtml(rep, ctx, rooms) {
return renderPropertyBuildingMetaHtml(rep, ctx, rooms);
}
function renderPropertyBuildingMetaItemHtml(label, value, iconKey, h) {
return (
'' +
listingIconImg(iconKey, 'property-building-meta__icon') +
'' +
h(label) +
' ' +
'' +
h(value) +
'
'
);
}
function renderPropertyBuildingMetaHtml(rep, ctx, rooms) {
var h = ctx.escHtml || escHtml;
var t = ctx.text || text;
var metaRow = resolveBuildingMetaRow(rep, rooms);
var builtDisp = ctx.formatBuiltYmDisplay
? ctx.formatBuiltYmDisplay(metaRow)
: formatBuiltYmDisplay(metaRow, t);
var structureDisp = ctx.formatStructureLabel
? ctx.formatStructureLabel(metaRow)
: formatStructureLabel(metaRow, t);
var floorsDisp = ctx.formatBuildingFloorsLabel
? ctx.formatBuildingFloorsLabel(metaRow)
: formatBuildingFloorsLabel(metaRow, t);
var parts = [];
if (builtDisp && builtDisp !== '—' && builtDisp !== '-' && builtDisp !== '-') {
parts.push(renderPropertyBuildingMetaItemHtml('築年', builtDisp, 'builtYm', h));
}
if (structureDisp && structureDisp !== '—' && structureDisp !== '-' && structureDisp !== '-') {
parts.push(renderPropertyBuildingMetaItemHtml('構造', structureDisp, 'structure', h));
}
if (floorsDisp && floorsDisp !== '—' && floorsDisp !== '-' && floorsDisp !== '-') {
parts.push(renderPropertyBuildingMetaItemHtml('階数', floorsDisp, 'floors', h));
}
if (!parts.length) return '';
return '' + parts.join('') + '
';
}
function renderBuildingSpecsHtml(rep, ctx, rooms) {
var inner = renderBuildingSpecsInlineHtml(rep, ctx, rooms);
if (!inner) return '';
return '' + inner + '
';
}
function renderTypeBadgeInInfoHtml(typeDisp, typeMod, h, a) {
if (!typeDisp) return '';
var cls =
'building-card__type-badge building-card__type-badge--on-info' +
(typeMod ? ' building-card__type-badge--' + typeMod : '');
return '' + h(typeDisp) + ' ';
}
function renderFeeCardInnerHtml(kind, iconKey, label, value, h) {
return (
'' +
listingInlineRow(iconKey, '' + h(label) + ' ', 'fee') +
renderFeeCardValueHtml(value, h) +
'
'
);
}
function renderDepositCardHtml(r, ctx) {
var h = ctx.escHtml || escHtml;
var dep = ctx.formatDepositLabel ? ctx.formatDepositLabel(r) : '';
return renderFeeCardInnerHtml('deposit', 'deposit', '敷金', feeValueOnly(dep, '敷金'), h);
}
function renderKeyMoneyCardHtml(r, ctx) {
var h = ctx.escHtml || escHtml;
var key = ctx.formatKeyMoneyLabel ? ctx.formatKeyMoneyLabel(r) : '';
return renderFeeCardInnerHtml('key', 'keyMoney', '礼金', feeValueOnly(key, '礼金'), h);
}
function renderRentColHtml(r, ctx) {
var h = ctx.escHtml || escHtml;
var rentMain = ctx.formatRent ? ctx.formatRent(r.rent) : '';
var mgmt = ctx.formatManagementFeeLabel ? ctx.formatManagementFeeLabel(r) : '';
var mgmtVal = feeValueOnly(mgmt, '管理費');
var rentParts = splitRentManYenDisplay(rentMain);
var mgmtBlock =
mgmt && mgmt !== '—'
? '管理費 ' +
h(mgmtVal) +
'
'
: '';
return (
'' +
'
賃料 ' +
'
' +
'' +
h(rentParts.main) +
' ' +
(rentParts.unit ? '' + h(rentParts.unit) + ' ' : '') +
(rentParts.hasMonthly ? ' / 月 ' : '') +
'
' +
mgmtBlock +
'
'
);
}
function renderLayoutColHtml(r, ctx) {
var h = ctx.escHtml || escHtml;
var t = ctx.text || text;
var layout = t(r.layout, '—');
var area = ctx.formatAreaList ? ctx.formatAreaList(r.area) : t(r.area, '—');
return (
'' +
listingInlineRow('layout', '' + h(layout) + ' ', 'layout') +
listingInlineRow('area', '' + h(area) + ' ', 'area') +
'
'
);
}
function toHalfWidthRoomDisplay(s) {
return String(s == null ? '' : s).replace(/[0-9A-Za-z]/g, function (c) {
var code = c.charCodeAt(0);
if (code >= 0xff10 && code <= 0xff19) return String.fromCharCode(code - 0xfee0);
if (code >= 0xff21 && code <= 0xff3a) return String.fromCharCode(code - 0xfee0);
if (code >= 0xff41 && code <= 0xff5a) return String.fromCharCode(code - 0xfee0);
return c;
});
}
function roomNoFitLen(number, suffix) {
/* 半角化後の表示文字列(number + suffix)— PHP rssg_blc_room_no_fit_len と同一 */
var display = String(number) + String(suffix);
return display.length;
}
function roomNoFitAttr(number, suffix) {
var len = roomNoFitLen(number, suffix);
return len > 0 ? ' data-room-len="' + len + '"' : '';
}
function parseRoomNoDisplayParts(roomRaw) {
var full = formatRoomTitleInGroup(roomRaw);
if (!full) return { number: '—', suffix: '' };
var i = full.indexOf('号室');
if (i >= 0) {
return { number: toHalfWidthRoomDisplay(full.slice(0, i)), suffix: '号室' };
}
return { number: toHalfWidthRoomDisplay(full), suffix: '' };
}
function renderRoomNoInnerHtml(parts, h) {
var suffixHtml =
parts.suffix !== ''
? '' + h(parts.suffix) + ' '
: '';
return (
'' +
h(parts.number) +
' ' +
suffixHtml
);
}
/** PC/SP 共通の号室ブロック(最終DOM正本) */
function renderRoomNoBlockHtml(parts, h) {
return (
'' +
renderRoomNoInnerHtml(parts, h) +
'
'
);
}
function renderRoomColHtml(roomRaw, r, ctx) {
var h = ctx.escHtml || escHtml;
var parts = parseRoomNoDisplayParts(roomRaw);
var floorLabel = ctx.formatRoomFloorLabel
? ctx.formatRoomFloorLabel(r)
: formatRoomFloorLabel(r, ctx.text || text);
var floorHtml = floorLabel
? '' + h(floorLabel) + '
'
: '';
return (
'' +
renderRoomNoBlockHtml(parts, h) +
floorHtml +
'
'
);
}
/** SP基本情報帯(築年・構造・階数)— PCでは非表示 */
function renderRoomBasicBandHtml(rep, r, roomRaw, ctx, rooms) {
var meta = renderPropertyBuildingMetaHtml(rep, ctx, rooms);
if (!meta) return '';
return '' + meta + '
';
}
/** @deprecated renderRoomSummaryHtml / renderRoomColHtml が renderRoomNoInnerHtml を使用。互換用 */
function renderRoomNoHtml(roomFull, h) {
var full = String(roomFull || '').trim();
if (!full) return '';
var i = full.indexOf('号室');
var parts =
i >= 0
? { number: toHalfWidthRoomDisplay(full.slice(0, i)), suffix: '号室' }
: { number: toHalfWidthRoomDisplay(full), suffix: '' };
return renderRoomNoInnerHtml(parts, h);
}
function renderRoomSummaryHtml(roomRaw, r, ctx) {
var h = ctx.escHtml || escHtml;
var t = ctx.text || text;
var hasRoom = formatRoomTitleInGroup(roomRaw) !== '';
var parts = parseRoomNoDisplayParts(roomRaw);
var floorLabel = ctx.formatRoomFloorLabel
? ctx.formatRoomFloorLabel(r)
: formatRoomFloorLabel(r, t);
if (!hasRoom && !floorLabel) return '';
return (
'' +
(hasRoom ? renderRoomNoBlockHtml(parts, h) : '') +
(floorLabel ? '' + h(floorLabel) + ' ' : '') +
'
'
);
}
function conditionGridIconKeyForLabel(label) {
if (label === '間取り') return 'layout';
if (label === '専有面積') return 'area';
if (label === '敷金') return 'deposit';
if (label === '礼金') return 'keyMoney';
return '';
}
function conditionGridItemModForLabel(label) {
if (label === '間取り') return 'layout';
if (label === '専有面積') return 'area';
if (label === '敷金') return 'deposit';
if (label === '礼金') return 'keymoney';
return 'other';
}
/** "1ヶ月"のような文字列を数字部分と単位部分に分割(数字だけ大きく表示するため)。数字が先頭にない場合は分割しない */
function splitLeadingNumberUnit(value) {
var s = value == null ? '' : String(value);
var m = s.match(/^([0-9]+(?:[.,][0-9]+)?)([\s\S]*)$/);
if (!m || !m[1]) return { num: s, unit: '' };
return { num: m[1], unit: m[2] };
}
function renderPropertyConditionGridItemHtml(label, value, h) {
var iconKey = conditionGridIconKeyForLabel(label);
var mod = conditionGridItemModForLabel(label);
var raw = value == null ? '' : String(value).trim();
if (raw === '—' || raw === '-' || raw === '-' || raw === 'null' || raw === 'undefined') raw = '';
var itemCls = 'property-condition-grid__item property-condition-grid__item--' + mod;
if (label === '敷金' || label === '礼金') {
// 未登録は推測せず値部分を空欄(0円 / なし / — を出さない)
var parts = raw ? splitLeadingNumberUnit(raw) : { num: '', unit: '' };
var valueInner = parts.unit
? '' +
h(parts.num) +
' ' +
h(parts.unit) +
' '
: h(raw);
return (
'' +
listingInlineRow(iconKey, '' + h(label) + ' ', 'fee-head') +
'' +
valueInner +
'
'
);
}
var val = h(raw || '—');
return (
'' +
'' +
h(label) +
' ' +
listingInlineRow(iconKey, '' + val + ' ', 'condition') +
'
'
);
}
function renderPropertyConditionGridHtml(r, ctx) {
var h = ctx.escHtml || escHtml;
var t = ctx.text || text;
var layout = t(r.layout, '—');
var area = ctx.formatAreaList ? ctx.formatAreaList(r.area) : t(r.area, '—');
var dep = ctx.formatDepositLabel ? ctx.formatDepositLabel(r) : '';
var key = ctx.formatKeyMoneyLabel ? ctx.formatKeyMoneyLabel(r) : '';
return (
'' +
renderPropertyConditionGridItemHtml('間取り', layout, h) +
renderPropertyConditionGridItemHtml('専有面積', area, h) +
renderPropertyConditionGridItemHtml('敷金', feeValueOnly(dep, '敷金'), h) +
renderPropertyConditionGridItemHtml('礼金', feeValueOnly(key, '礼金'), h) +
'
'
);
}
/** @deprecated layout-under — property-condition-grid へ移行。互換のため残置 */
function renderLayoutUnderThumbHtml(r, ctx) {
var h = ctx.escHtml || escHtml;
var t = ctx.text || text;
var layout = t(r.layout, '—');
var area = ctx.formatAreaList ? ctx.formatAreaList(r.area) : t(r.area, '—');
return (
'' +
'
' +
'間取り ' +
listingInlineRow('layout', '' + h(layout) + ' ', 'layout') +
'
' +
'
' +
'専有面積 ' +
listingInlineRow('area', '' + h(area) + ' ', 'area') +
'
'
);
}
function renderFavoriteButtonHtml(id, detailUrl, title, imageUrl, rentText, address, a) {
return (
'' +
' '
);
}
function renderListCtaLink(a, h, className, href, label, iconKind) {
var inquiryIcon =
' ';
var detailIcon =
' ';
var inner =
iconKind === 'inquiry'
? inquiryIcon + '' + h(label) + ' '
: '' + h(label) + ' ' + detailIcon;
return '' + inner + ' ';
}
function renderBuildingRoomsHeadHtml() {
return (
'' +
listingIconImg('buildingRooms', 'building-rooms__head-icon listing-card__icon--building-rooms') +
'募集住戸 ' +
'
'
);
}
function renderHtml(group, ctx) {
ctx = ctx || {};
group = enrichGroup(group);
if (isMemberOnlyGroup(group) && !(ctx.memberSessionLoggedIn || (typeof window !== 'undefined' && window.__rakusumoMemberLoggedIn))) {
return renderMemberTeaserHtml(group, ctx);
}
var h = ctx.escHtml || escHtml;
var a = ctx.escAttr || escAttr;
var t = ctx.text || text;
var noImageUrl = resolvePropertyNoImageUrl(t(ctx.noImageUrl, ''));
var rep = group.representative || {};
var rooms = group.rooms || [];
var buildingName = t(rep.buildingName || rep['建物名'] || rep.title, '建物名未設定');
var address = (ctx.formatCardAddress ? ctx.formatCardAddress(rep) : '') || '—';
var typeRaw =
rep.property_type ||
rep.propertyType ||
rep.type ||
rep.structureType ||
rep['物件種別'] ||
rep['建物種別'] ||
rep['種別'] ||
rep.building_type;
var typeDisp = ctx.formatPropertyType ? ctx.formatPropertyType(typeRaw) : '';
var typeMod = propertyTypeBadgeModifier(typeDisp || typeRaw);
var extraArticleClass = t(ctx.extraArticleClass, '');
var roomRowClass = t(ctx.roomRowClass, '');
var ctaDetailClass = t(ctx.ctaDetailClass, 'list-cta list-cta--line');
var ctaInquiryClass = t(ctx.ctaInquiryClass, 'list-cta list-cta--go');
var ctaDetailLabel = t(ctx.ctaDetailLabel, '詳細を見る');
var ctaInquiryLabel = t(ctx.ctaInquiryLabel, '無料で空室確認');
var buildingImg = pickBuildingImageForGroup(group);
var mergedBuildingSlides = mergeCardSliderSlides(
buildingImg,
collectOrderedUrlsForBuildingGroup(group),
rep,
urlKindOverlayForBuildingGroup(group),
);
var mergedBuilding = mergedBuildingSlides.urls;
var mergedBuildingKinds = mergedBuildingSlides.kinds;
var rotB = rotateSlides(mergedBuilding, mergedBuildingKinds);
mergedBuilding = rotB.urls;
mergedBuildingKinds = rotB.kinds;
var buildingImgShown = mergedBuilding.length ? mergedBuilding[0] : buildingImg;
var buildingPhotoHtml = renderCardImageSliderHtml(buildingImgShown, buildingName, mergedBuilding, {
wrapperBase: 'building-card__photo',
imageKinds: mergedBuildingKinds,
noImageUrl: noImageUrl,
});
var repId = t(rep.id, '');
var buildingDetailUrl = ctx.propertyUrlById
? ctx.propertyUrlById(repId)
: publicSiteHref('properties/' + encodeURIComponent(repId));
var roomRows = rooms
.map(function (r) {
var id = t(r.id, '');
var roomRaw = ctx.roomNoRawFromRow ? ctx.roomNoRawFromRow(r) : '';
var displayTitle = formatRoomTitleInGroup(roomRaw);
if (!displayTitle && ctx.formatPropertyHeadline) {
var baseForRoom = t(r.buildingName || r['建物名'] || buildingName, buildingName);
displayTitle = ctx.formatPropertyHeadline(baseForRoom, roomRaw);
}
if (!displayTitle) displayTitle = '—';
var roomImg = pickRoomFloorplanImage(r, buildingImg);
var detailUrl = ctx.propertyUrlById ? ctx.propertyUrlById(id) : publicSiteHref('properties/' + encodeURIComponent(id));
var inquiryUrl = detailUrl + '#pd-inquiry';
var roomOrdered = collectOrderedUrlsForRow(r);
var roomOverlay = urlKindOverlayFromRow(r);
var mergedRoomSlides = mergeCardSliderSlides(roomImg, roomOrdered, r, roomOverlay);
var roomThumbHtml = renderCardImageSliderHtml(roomImg, displayTitle, mergedRoomSlides.urls, {
wrapperBase: 'room-row__thumb',
imageKinds: mergedRoomSlides.kinds,
roomThumbNoSlider: true,
noImageUrl: noImageUrl,
});
var rowCls = 'room-row' + (roomRowClass ? ' ' + roomRowClass : '');
if (ctx.isStationFeaturedRoom && ctx.isStationFeaturedRoom(r)) {
rowCls += ' room-row--featured';
}
var rentColHtml = ctx.formatRentBlock ? ctx.formatRentBlock(r) : renderRentColHtml(r, ctx);
var conditionGridHtml = renderPropertyConditionGridHtml(r, ctx);
var roomColHtml = renderRoomColHtml(roomRaw, r, ctx);
// 2026-07: 築年・構造・階数は建物情報のため号室ごとに繰り返さない(Item 5)。
// 建物共通帯(building-card__meta-band、renderBuildingHeadListingHtml 側)へ統一したため、
// ここでは room-row__basic-band を描画しない(renderRoomBasicBandHtml は互換のため残置・未使用)。
var basicBandHtml = '';
var roomSummaryHtml = renderRoomSummaryHtml(roomRaw, r, ctx);
var favHtml = renderFavoriteButtonHtml(
id,
detailUrl,
displayTitle,
roomImg || noImageUrl,
ctx.formatRent ? ctx.formatRent(r.rent) : '',
address,
a,
);
var favColHtml = '' + favHtml + '
';
return (
'' +
basicBandHtml +
roomColHtml +
'
' +
'
' +
roomThumbHtml +
'
' +
'
' +
roomSummaryHtml +
rentColHtml +
'
' +
conditionGridHtml +
'
' +
renderListCtaLink(a, h, ctaDetailClass, detailUrl, ctaDetailLabel, 'detail') +
renderListCtaLink(a, h, ctaInquiryClass, inquiryUrl, ctaInquiryLabel, 'inquiry') +
'
' +
favColHtml +
'
'
);
})
.join('');
var hasFeaturedRoom =
ctx.isStationFeaturedRoom &&
rooms.some(function (r) {
return ctx.isStationFeaturedRoom(r);
});
var articleCls = 'building-card' + (extraArticleClass ? ' ' + extraArticleClass : '');
if (hasFeaturedRoom) {
articleCls += ' building-card--has-featured';
}
return (
'' +
renderBuildingHeadListingHtml(
buildingPhotoHtml,
buildingName,
typeDisp,
typeMod,
address,
rep,
buildingDetailUrl,
ctx,
h,
a,
rooms,
) +
'' +
renderBuildingRoomsHeadHtml() +
roomRows +
'
'
);
}
/**
* TOP 新着 property-card 用: 掲載中一覧と同じ URL 集約(1 行 = 1 棟グループ)
* @returns {{ html: string, urls: string[], kinds: string[] }}
*/
/** 建物グループ内の部屋カード見出し(号室のみ。例: 103号室) */
function formatRoomTitleInGroup(roomRaw) {
if (typeof PropertyDisplayFormat !== 'undefined' && PropertyDisplayFormat.formatRoomSuffixLabel) {
return PropertyDisplayFormat.formatRoomSuffixLabel(roomRaw);
}
var raw = text(roomRaw, '').replace(/\s+/g, '').replace(/[,、,。]+$/u, '');
if (!raw) return '';
var m = raw.match(/^[0-90-9][0-90-9A-Za-zA-Z\-ー]*/u);
if (m) return m[0] + '号室';
var i = raw.indexOf('号室');
if (i >= 0) return raw.slice(0, i + 2);
return raw + '号室';
}
function buildListingRoomTagsHtml() {
return '';
}
function aggregateBuildingTagsHtml() {
return '';
}
function formatRoomInfoChipsHtml() {
return '';
}
/**
* 条件検索・掲載中一覧で共通の building-card 描画コンテキスト
* @param {object} ext list.js / search.js のフォーマッタ等
*/
function buildListingCardCtx(ext) {
ext = ext || {};
var h = ext.escHtml || escHtml;
var a = ext.escAttr || escAttr;
function formatRentBlockUnified(r) {
return renderRentColHtml(r, {
escHtml: h,
formatRent: ext.formatRent,
formatManagementFeeLabel: ext.formatManagementFeeLabel,
});
}
function formatFeeCardsUnified(r) {
return renderDepositCardHtml(r, {
escHtml: h,
formatDepositLabel: ext.formatDepositLabel,
formatKeyMoneyLabel: ext.formatKeyMoneyLabel,
}) + renderKeyMoneyCardHtml(r, {
escHtml: h,
formatDepositLabel: ext.formatDepositLabel,
formatKeyMoneyLabel: ext.formatKeyMoneyLabel,
});
}
function formatLayoutAreaColUnified(r) {
return renderLayoutColHtml(r, {
escHtml: h,
text: ext.text || text,
formatAreaList: ext.formatAreaList,
});
}
function formatRoomFloorUnified(r) {
return formatRoomFloorLabel(r, ext.text || text);
}
function formatBuiltYmUnified(repRow) {
return formatBuiltYmDisplay(repRow, ext.text || text);
}
function formatStructureUnified(repRow) {
return formatStructureLabel(repRow, ext.text || text);
}
function formatBuildingFloorsUnified(repRow) {
return formatBuildingFloorsLabel(repRow, ext.text || text);
}
return {
escHtml: h,
escAttr: a,
text: ext.text || text,
formatCardAddress: ext.formatCardAddress,
firstTransport: ext.firstTransport,
formatFloorSummaryForGroup: ext.formatFloorSummaryForGroup,
formatPropertyType: ext.formatPropertyType,
formatPropertyHeadline: ext.formatPropertyHeadline,
formatBuiltYmDisplay: formatBuiltYmUnified,
formatStructureLabel: formatStructureUnified,
formatBuildingFloorsLabel: formatBuildingFloorsUnified,
formatRoomFloorLabel: formatRoomFloorUnified,
formatRent: ext.formatRent,
formatManagementFeeLabel: ext.formatManagementFeeLabel,
formatDepositLabel: ext.formatDepositLabel,
formatKeyMoneyLabel: ext.formatKeyMoneyLabel,
formatFloorSummary: ext.formatFloorSummary,
formatAreaList: ext.formatAreaList,
roomNoRawFromRow: ext.roomNoRawFromRow,
propertyUrlById: ext.propertyUrlById,
extraArticleClass: 'building-card--listing',
roomRowClass: 'room-row--listing',
ctaDetailClass: 'list-cta list-cta--detail-sub',
ctaInquiryClass: 'list-cta list-cta--vacancy',
ctaDetailLabel: '詳細を見る',
ctaInquiryLabel: '無料空室確認',
formatRentBlock: formatRentBlockUnified,
formatFeeCards: formatFeeCardsUnified,
formatLayoutAreaCol: formatLayoutAreaColUnified,
walkMinutes: ext.walkMinutes || null,
isStationFeaturedRoom: ext.isStationFeaturedRoom || null,
noImageUrl: resolvePropertyNoImageUrl(ext.noImageUrl),
memberPropertyImageUrl: text(ext.memberPropertyImageUrl, ''),
memberSessionLoggedIn: !!(ext.memberSessionLoggedIn),
memberRegisterUrl: text(ext.memberRegisterUrl, ''),
memberLoginUrl: text(ext.memberLoginUrl, ''),
};
}
function buildListingPhotoForRow(row, title, opts) {
opts = opts || {};
row = enrichRow(row);
var group = { representative: row, rooms: [row] };
var buildingImg = pickBuildingImageForGroup(group);
var mergedBuildingSlides = mergeCardSliderSlides(
buildingImg,
collectOrderedUrlsForBuildingGroup(group),
row,
urlKindOverlayForBuildingGroup(group),
);
var mergedList = mergedBuildingSlides.urls;
var slideKinds = mergedBuildingSlides.kinds;
var rotDeck = rotateSlides(mergedList, slideKinds);
mergedList = rotDeck.urls;
slideKinds = rotDeck.kinds;
var img = mergedList.length ? mergedList[0] : buildingImg;
var wrapperBase = text(opts.wrapperBase, 'thumb-wrap list-card-thumb');
if (!img) wrapperBase += ' list-card-thumb--empty';
var noImageUrl = resolvePropertyNoImageUrl(opts.noImageUrl);
return {
html: renderCardImageSliderHtml(img, title, mergedList, {
wrapperBase: wrapperBase,
imgClass: text(opts.imgClass, 'list-card-image'),
decoding: !!opts.decoding,
imageKinds: slideKinds,
noImageUrl: noImageUrl,
}),
urls: mergedList,
kinds: slideKinds,
};
}
return {
renderHtml: renderHtml,
renderMemberTeaserHtml: renderMemberTeaserHtml,
isMemberOnlyGroup: isMemberOnlyGroup,
isMemberOnlyRow: isMemberOnlyRow,
normalizeMemberOnlyFlag: normalizeMemberOnlyFlag,
enrichGroup: enrichGroup,
buildListingPhotoForRow: buildListingPhotoForRow,
buildListingCardCtx: buildListingCardCtx,
buildListingRoomTagsHtml: buildListingRoomTagsHtml,
aggregateBuildingTagsHtml: aggregateBuildingTagsHtml,
formatRoomInfoChipsHtml: formatRoomInfoChipsHtml,
formatRoomTitleInGroup: formatRoomTitleInGroup,
injectTypeBadgeIntoPhotoHtml: injectTypeBadgeIntoPhotoHtml,
propertyTypeBadgeModifier: propertyTypeBadgeModifier,
noImageFallbackUrl: propertyNoImageFallbackUrl,
resolveNoImageUrl: resolvePropertyNoImageUrl,
};
});