hotjob时间选择器和搜索下拉型输入框组件

This commit is contained in:
2026-08-07 10:16:44 +08:00
parent 69c77dc7ac
commit 94a58ea362
5 changed files with 224 additions and 30 deletions
+2 -2
View File
@@ -993,7 +993,7 @@ export function SidebarPanel({ sourceUrl, jobInfo, onClose }: SidebarPanelProps)
<div className="op-nonmember-desc"></div> <div className="op-nonmember-desc"></div>
<button <button
className="op-nonmember-btn" className="op-nonmember-btn"
onClick={() => { window.open("https://test.offerpai.com.cn/jobs", "_blank") }} onClick={() => { window.open("https://www.offerpai.com.cn", "_blank") }}
> >
</button> </button>
@@ -1053,7 +1053,7 @@ export function SidebarPanel({ sourceUrl, jobInfo, onClose }: SidebarPanelProps)
<circle cx="24" cy="24" r="20" fill="none" stroke="#52CAD1" strokeWidth="3" <circle cx="24" cy="24" r="20" fill="none" stroke="#52CAD1" strokeWidth="3"
strokeDasharray={`${(jobInfo.matchScore) / 100 * 2 * Math.PI * 20} ${2 * Math.PI * 20}`} strokeDasharray={`${(jobInfo.matchScore) / 100 * 2 * Math.PI * 20} ${2 * Math.PI * 20}`}
strokeLinecap="round" transform="rotate(-90 24 24)" /> strokeLinecap="round" transform="rotate(-90 24 24)" />
<text x="24" y="26" textAnchor="middle" fontSize="13" fontWeight="700" fill="#000"> <text x="24" y="28.6" textAnchor="middle" fontSize="13" fontWeight="700" fill="#000">
{jobInfo.matchScore}% {jobInfo.matchScore}%
</text> </text>
</svg> </svg>
+181 -17
View File
@@ -7,8 +7,9 @@
* 不要在此文件中自行编写选择器操作逻辑,必须引用 lib 中已封装的方法。 * 不要在此文件中自行编写选择器操作逻辑,必须引用 lib 中已封装的方法。
*/ */
import { fillMatchedField, isSearchPickerField, fillSearchPickerField, isTimePeriodField, isTimeSingleField, fillTimePeriodField, fillTimeSingleField } from "~lib/autofill" import { fillMatchedField, isSearchPickerField, fillSearchPickerField, forceSetValue, isTimePeriodField, isTimeSingleField, fillTimePeriodField, fillTimeSingleField } from "~lib/autofill"
import { delay } from "~utils/delay" import { delay } from "~utils/delay"
import { simulateClick } from "~utils/domEvent"
import { extractDomStructure, detectPageLanguage, isJobApplicationForm, buildSelector } from "~lib/dom" import { extractDomStructure, detectPageLanguage, isJobApplicationForm, buildSelector } from "~lib/dom"
import { matchFormFieldsInRange, matchMainFields } from "~lib/formMatcher" import { matchFormFieldsInRange, matchMainFields } from "~lib/formMatcher"
import { detectPickerField } from "~lib/pickerDetector" import { detectPickerField } from "~lib/pickerDetector"
@@ -23,6 +24,168 @@ import { setFieldHighlight, isRequiredField } from "~lib/formStyle"
import { get as storageGet, set as storageSet } from "~utils/storage" import { get as storageGet, set as storageSet } from "~utils/storage"
import { findLabelForInput } from "~lib/labelFinder" import { findLabelForInput } from "~lib/labelFinder"
/** Hotjob平台需要走全DOM搜索ant-select选项的字段标签名(这些字段弹出层在页面DOM末尾,通用逻辑定位不到) */
const HOTJOB_ANT_SELECT_LABELS = ["学校"]
/** Hotjob平台时间字段标签名(Ant DatePicker弹出层在DOM末尾,需要手动定位ant-calendar-picker-container */
const HOTJOB_DATE_LABELS = ["开始时间", "结束时间"]
/**
* Hotjob平台特殊处理:全DOM搜索 ant-select-dropdown-menu-item 选项并点击
* 原因:Hotjob的Ant Select弹出层渲染在页面DOM最末尾,不在input附近,
* 通用的弹出层检测逻辑(DOM差异对比)会误匹配到input内部的镜像span元素
* @param inputEl - 搜索输入框元素
* @param fillValue - 要填写的值(如学校名称)
* @returns 是否成功选中选项
*/
async function hotjobFillAntSelectField(
inputEl: HTMLInputElement | HTMLTextAreaElement,
fillValue: string
): Promise<boolean> {
if (!inputEl || !fillValue) return false
console.log(`OfferPie: [Hotjob-AntSelect] "${fillValue}" 开始填写`)
// 1. 点击input获取焦点并写入搜索值
inputEl.focus()
simulateClick(inputEl as HTMLElement)
await delay("mid")
forceSetValue(inputEl, fillValue)
// 2. 等待接口返回数据(Hotjob接口较慢,用max延时)
await delay("max")
// 3. 全DOM搜索 ant-select-dropdown-menu-item,文字匹配目标值
const allItems = document.querySelectorAll("li.ant-select-dropdown-menu-item")
let targetItem: HTMLElement | null = null
for (const item of Array.from(allItems)) {
const text = item.textContent?.trim()
if (text && text === fillValue) {
targetItem = item as HTMLElement
break
}
}
if (!targetItem) {
console.log(`OfferPie: ❌ [Hotjob-AntSelect] 未找到匹配选项 "${fillValue}"`)
return false
}
// 4. 用simulateClick点击选项(Ant Design监听mousedown/pointerdown
console.log(`OfferPie: [Hotjob-AntSelect] 找到选项,点击: tag=${targetItem.tagName} | class="${targetItem.className}"`)
simulateClick(targetItem)
await delay("mid")
console.log(`OfferPie: ✅ [Hotjob-AntSelect] 已选择 "${fillValue}"`)
return true
}
/**
* Hotjob平台特殊处理:时间/日期字段填写
* 原因:Hotjob的Ant DatePicker弹出层渲染在页面DOM最末尾(div.ant-calendar-picker-container),
* 通用的 analyzeCalendarPanel 全DOM搜索找不到面板(被input附近元素干扰)
* 流程:simulateClick展开面板 → 全DOM搜ant-calendar-picker-container定位面板 → 调fillDatePicker(field, panelHint)走集成式逻辑
* @param field - 匹配到的表单字段
* @returns 是否成功填写
*/
async function hotjobFillDateField(field: MatchedFormField): Promise<boolean> {
const { labelText, inputElement, fillValue } = field
if (!inputElement || !fillValue) return false
// 1. 如果fillValue只有年月(如2025-09),补成2025-09-01
let adjustedValue = fillValue
if (/^\d{4}[.\-\/]\d{1,2}$/.test(fillValue)) {
adjustedValue = fillValue.replace(/[.\-\/]/, "-") + "-01"
console.log(`OfferPie: [Hotjob-Date] "${labelText}" 只有年月,补日为: "${adjustedValue}"`)
}
field.fillValue = adjustedValue
// 2. simulateClick展开日期面板
simulateClick(inputElement as HTMLElement)
await delay("mid")
// 3. 全DOM搜索可见的 ant-calendar-picker-container,再往下找 ant-calendar-date-panel 作为精确面板定位
const allContainers = document.querySelectorAll("div.ant-calendar-picker-container")
let panelHint: HTMLElement | null = null
for (const el of Array.from(allContainers)) {
const htmlEl = el as HTMLElement
if (htmlEl.offsetHeight > 0 && htmlEl.offsetWidth > 0) {
// 优先找更精确的 ant-calendar-date-panel(包含头部年月按钮+日期格子)
const datePanel = htmlEl.querySelector("div.ant-calendar-date-panel") as HTMLElement | null
if (datePanel && datePanel.offsetHeight > 0) {
panelHint = datePanel
} else {
panelHint = htmlEl
}
break
}
}
if (!panelHint) {
console.log(`OfferPie: ❌ [Hotjob-Date] "${labelText}" 未找到 ant-calendar-picker-container`)
return false
}
console.log(`OfferPie: [Hotjob-Date] "${labelText}" 找到面板容器,调用 fillDatePicker`)
// 4. 解析目标年月,通过Ant Calendar专属类名导航到目标年月
const dateMatch = adjustedValue.match(/^(\d{4})[.\-\/](\d{1,2})/)
if (dateMatch) {
const targetYear = parseInt(dateMatch[1], 10)
const targetMonth = parseInt(dateMatch[2], 10)
// 读取面板当前显示的年月(从 ant-calendar-year-select 和 ant-calendar-month-select 获取)
const readPanelYearMonth = (): { year: number; month: number } => {
const yearEl = panelHint!.querySelector("a.ant-calendar-year-select")
const monthEl = panelHint!.querySelector("a.ant-calendar-month-select")
const yearText = yearEl?.textContent?.trim() || ""
const monthText = monthEl?.textContent?.trim() || ""
const year = parseInt(yearText, 10) || 0
const month = parseInt(monthText, 10) || 0
return { year, month }
}
let current = readPanelYearMonth()
console.log(`OfferPie: [Hotjob-Date] "${labelText}" 面板当前: ${current.year}${current.month}月 → 目标: ${targetYear}${targetMonth}`)
// 导航年份
const yearDiff = targetYear - current.year
if (yearDiff !== 0) {
const yearBtnClass = yearDiff > 0 ? "a.ant-calendar-next-year-btn" : "a.ant-calendar-prev-year-btn"
const yearBtn = panelHint!.querySelector(yearBtnClass) as HTMLElement | null
if (yearBtn) {
for (let i = 0; i < Math.abs(yearDiff) && i < 100; i++) {
yearBtn.click()
await delay("low")
}
await delay("low")
current = readPanelYearMonth()
console.log(`OfferPie: [Hotjob-Date] 年份导航完成,当前: ${current.year}${current.month}`)
}
}
// 导航月份
const monthDiff = targetMonth - current.month
if (monthDiff !== 0) {
const monthBtnClass = monthDiff > 0 ? "a.ant-calendar-next-month-btn" : "a.ant-calendar-prev-month-btn"
const monthBtn = panelHint!.querySelector(monthBtnClass) as HTMLElement | null
if (monthBtn) {
for (let i = 0; i < Math.abs(monthDiff) && i < 12; i++) {
monthBtn.click()
await delay("low")
}
await delay("low")
current = readPanelYearMonth()
console.log(`OfferPie: [Hotjob-Date] 月份导航完成,当前: ${current.year}${current.month}`)
}
}
}
// 5. 调用集成式选择器核心逻辑,传入panelHint让它在正确的容器内点击日期格子
const ok = await fillDatePicker(field, panelHint)
return ok
}
/** Hotjob模式自动填写的参数 */ /** Hotjob模式自动填写的参数 */
export interface AutoFillHotjobParams { export interface AutoFillHotjobParams {
/** 简历数据(接口获取的) */ /** 简历数据(接口获取的) */
@@ -315,6 +478,7 @@ export async function handleAutoFillHotjob(params: AutoFillHotjobParams): Promis
// 根据字段类型选择对应的填充方法(全部走已封装的统一入口) // 根据字段类型选择对应的填充方法(全部走已封装的统一入口)
if (isTimePeriodField(f.key)) { if (isTimePeriodField(f.key)) {
console.log(`OfferPie: [Hotjob-Debug] 进入 isTimePeriodField 分支: key="${f.key}" labelText="${f.labelText}"`)
const startDateVal = getResumeFieldValue(currentResumeData, f.section, dataIdx, "startDate") const startDateVal = getResumeFieldValue(currentResumeData, f.section, dataIdx, "startDate")
const endDateVal = getResumeFieldValue(currentResumeData, f.section, dataIdx, "endDate") const endDateVal = getResumeFieldValue(currentResumeData, f.section, dataIdx, "endDate")
let ok = await fillTimePeriodField(f, startDateVal, endDateVal, usedInputs) let ok = await fillTimePeriodField(f, startDateVal, endDateVal, usedInputs)
@@ -325,17 +489,23 @@ export async function handleAutoFillHotjob(params: AutoFillHotjobParams): Promis
} }
if (ok) { result.success++ } else { result.failed++ } if (ok) { result.success++ } else { result.failed++ }
} else if (isTimeSingleField(f.key)) { } else if (isTimeSingleField(f.key)) {
console.log(`OfferPie: [Hotjob-Debug] 进入 isTimeSingleField 分支: key="${f.key}" labelText="${f.labelText}" fillValue="${f.fillValue}"`)
if (!f.fillValue) { phaseAFields.push({ labelText: f.labelText, inputElement: f.inputElement, filled: false, fillValue: "", section: f.section, segmentIndex: segIdx }); result.skipped++; continue } if (!f.fillValue) { phaseAFields.push({ labelText: f.labelText, inputElement: f.inputElement, filled: false, fillValue: "", section: f.section, segmentIndex: segIdx }); result.skipped++; continue }
let ok = await fillTimeSingleField(f, f.fillValue, usedInputs) let ok: boolean
if (!ok) { // Hotjob平台:所有时间字段都走hotjobFillDateFieldAnt DatePicker弹出层在DOM末尾)
await detectPickerField(f, lang) console.log(`OfferPie: [Hotjob-Debug] 走 hotjobFillDateField`)
ok = await fillMatchedField(f) ok = await hotjobFillDateField(f)
}
if (ok) { result.success++ } else { result.failed++ } if (ok) { result.success++ } else { result.failed++ }
} else if (!f.fillValue) { } else if (!f.fillValue) {
phaseAFields.push({ labelText: f.labelText, inputElement: f.inputElement, filled: false, fillValue: "", section: f.section, segmentIndex: segIdx }); result.skipped++; continue phaseAFields.push({ labelText: f.labelText, inputElement: f.inputElement, filled: false, fillValue: "", section: f.section, segmentIndex: segIdx }); result.skipped++; continue
} else if (isSearchPickerField(f.key)) { } else if (isSearchPickerField(f.key)) {
const ok = await fillSearchPickerField(f) let ok: boolean
// Hotjob平台特殊字段:弹出层在DOM末尾,通用搜索逻辑定位不到,走全DOM搜li选项
if (HOTJOB_ANT_SELECT_LABELS.includes(f.labelText)) {
ok = await hotjobFillAntSelectField(f.inputElement, f.fillValue)
} else {
ok = await fillSearchPickerField(f)
}
if (ok) { result.success++ } else { result.failed++ } if (ok) { result.success++ } else { result.failed++ }
phaseAFields.push({ labelText: f.labelText, inputElement: f.inputElement, filled: ok, fillValue: f.fillValue, section: f.section, segmentIndex: segIdx }) phaseAFields.push({ labelText: f.labelText, inputElement: f.inputElement, filled: ok, fillValue: f.fillValue, section: f.section, segmentIndex: segIdx })
await delay("mid") await delay("mid")
@@ -1190,7 +1360,7 @@ async function b2FillDateTimeField(
labelText: string, labelText: string,
fillValue: string fillValue: string
): Promise<boolean> { ): Promise<boolean> {
// 构造 MatchedFormField(和阶段A一样,isPicker=true,不设 pickerDropdownElement // Hotjob平台:所有时间/日期字段都走hotjobFillDateFieldAnt DatePicker弹出层在DOM末尾,通用逻辑无法定位
const field: MatchedFormField = { const field: MatchedFormField = {
key: "", section: "main", resumeField: "", key: "", section: "main", resumeField: "",
sectionIndex: 0, labelText, sectionIndex: 0, labelText,
@@ -1205,17 +1375,11 @@ async function b2FillDateTimeField(
pickerDropdownElement: null, pickerDropdownSelector: "", pickerDropdownElement: null, pickerDropdownSelector: "",
fillValue, fillValue,
} }
const ok = await hotjobFillDateField(field)
// 和阶段A同样的方式:点击 input 展开日期面板,然后直接调 fillDatePicker
inputEl.focus()
;(inputEl as HTMLElement).click()
await delay("mid")
const ok = await fillDatePicker(field)
if (ok) { if (ok) {
console.log(`OfferPie: ✅ [B2-时间字段] "${labelText}" = "${fillValue}" 填写成功`) console.log(`OfferPie: ✅ [B2-时间字段] "${labelText}" = "${fillValue}" 填写成功 (Hotjob模式)`)
} else { } else {
console.log(`OfferPie: ❌ [B2-时间字段] "${labelText}" = "${fillValue}" 填写失败`) console.log(`OfferPie: ❌ [B2-时间字段] "${labelText}" = "${fillValue}" 填写失败 (Hotjob模式)`)
} }
return ok return ok
} }
+32 -7
View File
@@ -186,9 +186,31 @@ function isDayGridArea(el: HTMLElement): boolean {
* 2. 从日期格子向上找星期行(日一二三四五六) * 2. 从日期格子向上找星期行(日一二三四五六)
* 3. 从星期行继续向上到面板顶部,识别年月显示和可点击按钮 * 3. 从星期行继续向上到面板顶部,识别年月显示和可点击按钮
*/ */
function analyzeCalendarPanel(): CalendarPanelInfo | null { function analyzeCalendarPanel(panelHint?: HTMLElement): CalendarPanelInfo | null {
// ---- 步骤1:找到日期格子区域 ---- // ---- 步骤1:找到日期格子区域 ----
// 从常见的日历容器选择器开始搜索 let dayGridArea: HTMLElement | null = null
let panelRoot: HTMLElement | null = null
// 如果调用方传入了面板定位提示(panelHint),优先在提示元素内搜索日期格子
if (panelHint) {
const innerEls = panelHint.querySelectorAll("table, tbody, div")
for (const inner of Array.from(innerEls)) {
if (isVisible(inner) && isDayGridArea(inner as HTMLElement)) {
dayGridArea = inner as HTMLElement
panelRoot = panelHint
break
}
}
if (dayGridArea) {
console.log(`OfferPie: [datePicker] 通过 panelHint 找到日期格子区域: ${dayGridArea.tagName}.${(dayGridArea.className || "").toString().split(" ")[0]}`)
} else {
console.log(`OfferPie: [datePicker] panelHint 内未找到日期格子,fallback 到全 DOM 搜索`)
}
}
// 从常见的日历容器选择器开始搜索(panelHint 未命中时执行)
if (!dayGridArea) {
const panelSelectors = [ const panelSelectors = [
'[class*="picker-panel"]', '[class*="date-panel"]', '[class*="calendar"]', '[class*="picker-panel"]', '[class*="date-panel"]', '[class*="calendar"]',
'[class*="datepicker"]', '[class*="date-picker"]', '[class*="datepicker"]', '[class*="date-picker"]',
@@ -197,9 +219,6 @@ function analyzeCalendarPanel(): CalendarPanelInfo | null {
'[class*="dropdown"]', '[class*="overlay"]', '[class*="dropdown"]', '[class*="overlay"]',
] ]
let dayGridArea: HTMLElement | null = null
let panelRoot: HTMLElement | null = null
// 先尝试从已知面板选择器中找 // 先尝试从已知面板选择器中找
for (const sel of panelSelectors) { for (const sel of panelSelectors) {
const panels = document.querySelectorAll(sel) const panels = document.querySelectorAll(sel)
@@ -247,6 +266,8 @@ function analyzeCalendarPanel(): CalendarPanelInfo | null {
} }
} }
} // end if (!dayGridArea) — panelHint 未命中时的全 DOM 搜索
if (!dayGridArea || !panelRoot) { if (!dayGridArea || !panelRoot) {
console.warn("OfferPie: [datePicker] 未找到日期格子区域") console.warn("OfferPie: [datePicker] 未找到日期格子区域")
return null return null
@@ -1264,7 +1285,7 @@ function parseDateValue(fillValue: string): { year: number; month: number; day:
* *
* 【注意】按钮探测逻辑是通用适配的核心,不要简化或删除 * 【注意】按钮探测逻辑是通用适配的核心,不要简化或删除
*/ */
export async function fillDatePicker(field: MatchedFormField): Promise<boolean> { export async function fillDatePicker(field: MatchedFormField, panelHint?: HTMLElement): Promise<boolean> {
const { labelText, inputElement, fillValue } = field const { labelText, inputElement, fillValue } = field
if (!inputElement) return false if (!inputElement) return false
@@ -1289,6 +1310,10 @@ export async function fillDatePicker(field: MatchedFormField): Promise<boolean>
if (location.hostname.includes("mokahr.com") && (inputElement as HTMLInputElement).readOnly) { if (location.hostname.includes("mokahr.com") && (inputElement as HTMLInputElement).readOnly) {
console.log(`OfferPie: [datePicker] Moka适配: 跳过 forceSetValuereadonly input),等待弹出层渲染`) console.log(`OfferPie: [datePicker] Moka适配: 跳过 forceSetValuereadonly input),等待弹出层渲染`)
await delay("high") await delay("high")
// 【Hotjob适配】Hotjob 的 Ant DatePicker input 为 readonly,写值会触发面板关闭,跳过
} else if (location.hostname.includes("hotjob.cn") && (inputElement as HTMLInputElement).readOnly) {
console.log(`OfferPie: [datePicker] Hotjob适配: 跳过 forceSetValuereadonly input),等待弹出层渲染`)
await delay("mid")
} else { } else {
forceSetValue(inputElement, fillValue) forceSetValue(inputElement, fillValue)
await delay("high") await delay("high")
@@ -1302,7 +1327,7 @@ export async function fillDatePicker(field: MatchedFormField): Promise<boolean>
} }
// ---- 步骤3:分析日历面板结构 ---- // ---- 步骤3:分析日历面板结构 ----
const panel = analyzeCalendarPanel() const panel = analyzeCalendarPanel(panelHint)
if (!panel) { if (!panel) {
console.warn("OfferPie: [datePicker] 无法分析日历面板,尝试直接点击日期格子") console.warn("OfferPie: [datePicker] 无法分析日历面板,尝试直接点击日期格子")
// 兜底:尝试旧逻辑直接找日期格子 // 兜底:尝试旧逻辑直接找日期格子
+2 -2
View File
@@ -9,8 +9,8 @@ export type HighlightColor = "green" |"greenTwo" | "red" | "yellow"
/** 颜色映射表(淡色系,不影响文字可读性) */ /** 颜色映射表(淡色系,不影响文字可读性) */
const COLOR_MAP: Record<HighlightColor, string> = { const COLOR_MAP: Record<HighlightColor, string> = {
green: "#b7ffc5", // 淡绿色 green: "#b7ffc64d", // 淡绿色
greenTwo: "#b7ffc6", // 淡绿色2 greenTwo: "#b7ffc54d", // 淡绿色2
red: "#ffb7b7", // 淡红色 red: "#ffb7b7", // 淡红色
yellow: "transparent", // 透明色 yellow: "transparent", // 透明色
} }
+7 -2
View File
@@ -4,6 +4,7 @@
*/ */
import type { MatchedFormField } from "./types" import type { MatchedFormField } from "./types"
import { simulateClick } from "~utils/domEvent"
// ============ 模糊匹配 ============ // ============ 模糊匹配 ============
@@ -62,7 +63,10 @@ export function clickBestOptionInDropdown(dropdownEl: HTMLElement, fillValue: st
let bestMatch: { el: HTMLElement; text: string; score: number } | null = null let bestMatch: { el: HTMLElement; text: string; score: number } | null = null
for (const { el, text } of candidates) { for (const { el, text } of candidates) {
if (text.toLowerCase() === fillValue.toLowerCase()) { if (text.toLowerCase() === fillValue.toLowerCase()) {
el.click() // 【注意】必须用 simulateClick 触发完整事件链(pointerdown→mousedown→pointerup→mouseup→click
// 原生 .click() 只派发 click 事件,Ant Design 等组件库监听的是 mousedown/pointerdown,用 .click() 选项不会生效
console.log(`OfferPie: [clickBestOption] 即将点击元素: tag=${el.tagName} | class="${el.className}" | role="${el.getAttribute("role")}"`)
simulateClick(el)
console.log(`OfferPie: ✅ [弹出层] 点击选项 "${text}" (精确匹配)`) console.log(`OfferPie: ✅ [弹出层] 点击选项 "${text}" (精确匹配)`)
return true return true
} }
@@ -70,7 +74,8 @@ export function clickBestOptionInDropdown(dropdownEl: HTMLElement, fillValue: st
if (score > 0.2 && (!bestMatch || score > bestMatch.score)) bestMatch = { el, text, score } if (score > 0.2 && (!bestMatch || score > bestMatch.score)) bestMatch = { el, text, score }
} }
if (bestMatch) { if (bestMatch) {
bestMatch.el.click() // 【注意】同上,必须用 simulateClick 触发完整事件链,否则 Ant Design 等组件不响应
simulateClick(bestMatch.el)
console.log(`OfferPie: ✅ [弹出层] 点击选项 "${bestMatch.text}" (模糊匹配 score=${bestMatch.score.toFixed(2)})`) console.log(`OfferPie: ✅ [弹出层] 点击选项 "${bestMatch.text}" (模糊匹配 score=${bestMatch.score.toFixed(2)})`)
return true return true
} }