/** * 掲載中一覧(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) + '
' + '
' + '
' + listingInlineRow('address', '' + h(address) + '', 'address') + '
' + '
' + listingInlineRow( 'station', '' + renderBuildingAccessOneLineHtml(rep, ctx, h) + '', 'access', ) + '
' + /* 2026-07: 築年・構造・階数の建物共通帯(Item 5)。building-card__meta-band を追加し、 PC/SP 共通で常に表示する(従来の building-card__summary-line--specs クラスは 既存CSSの order/grid-area 等の配置ルールをそのまま使うため残す)。 */ (specsInner ? '
' + specsInner + '
' : '') + '
' ); } /** 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 + '
' + '
' + addressLine + accessLine + (specsInner ? '
' + specsInner + '
' : '') + '
'; // 公開許可済み間取り図は専用フィールド 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 = '' +
        escAttr(alt) +
        ''; 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, }; });