upstream: integrar 0.9.5 en 34 ficheros mas y portar los modulos nuevos
Segunda tanda. Resueltas por merge de tres vias 15 divergencias que no solapaban, y aplicada la version de 0.9.5 en 19 vistas y modelos donde la rama solo arrastraba codigo antiguo (firmas viejas de renderProductForm, renderJobForm, listTags sin search, y el import sin renderSpreadEditWarning). Se preserva la eliminacion de housing donde la rama lo habia descartado: modules_view, blockchain_view, search_model y tags_model. Modulos nuevos del dev portados: blog, data, gallery, mentions, polls, workflows, comments, recurrence y media_gallery, mas contentPdf, pdfDocument y content_favorites. main_views recupera renderEngagement, spreadsFor y renderSpreadEditWarning, que la rama habia eliminado y que las vistas de 0.9.5 necesitan. Sin ellas polls_view lanzaba TypeError al editar. El overlay de i18n pasa de 15 a 41 claves (46 en castellano): incorpora las 9 que upstream tenia en 0.9.1 y elimino en 0.9.5 mientras nuestro codigo las sigue usando (menuBlogs, spreadHint, chatShareUrl, forumFilterHot, publishBlog...) y las 17 de Karvan, que nunca existieron y tiraban de texto de reserva en ingles; ahora estan traducidas al castellano. Verificado con la app arrancada: declara 0.9.5, 0 claves i18n sin resolver (antes 26), 26 rutas responden 200/302, y la interfaz de la rama sale intacta: 10 categorias y 47 modulos en los hexagonos, topbar con Personal/Community y avatar, 7 accesos rapidos en la barra inferior y 0 etiquetas vacias (antes 1, la de Blogs).
This commit is contained in:
parent
f964e61a1c
commit
82b6c3ae05
43 changed files with 3221 additions and 2416 deletions
|
|
@ -1,9 +1,11 @@
|
|||
const { form, button, div, h2, p, section, input, label, textarea, br, a, span, select, option, img, progress, video, audio, table, tr, td } = require("../server/node_modules/hyperaxe")
|
||||
const { template, i18n, userLink, renderStateChip, renderOpenClosedChip, renderVisibilityChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions } = require("./main_views")
|
||||
const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view");
|
||||
const { template, i18n, userLink, renderStateChip, renderOpenClosedChip, renderVisibilityChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderSpreadEditWarning } = require("./main_views")
|
||||
const moment = require("../server/node_modules/moment")
|
||||
const { config } = require("../server/SSB_server.js")
|
||||
const { renderUrl } = require("../backend/renderUrl")
|
||||
const { renderMapLocationUrl, renderMapEmbed, renderMapLocationVisitLabel } = require("./maps_view")
|
||||
const { resolvePhoto } = require("./inhabitants_view")
|
||||
|
||||
const renderMediaBlob = (value, attrs = {}) => {
|
||||
if (!value) return null
|
||||
|
|
@ -24,6 +26,8 @@ const userId = config.keys.id
|
|||
const FILTERS = [
|
||||
{ key: "ALL", i18n: "jobsFilterAll", title: "jobsAllTitle" },
|
||||
{ key: "MINE", i18n: "jobsFilterMine", title: "jobsMineTitle" },
|
||||
{ key: "RECENT", i18n: "jobsFilterRecent", title: "jobsRecentTitle" },
|
||||
{ key: "TOP", i18n: "jobsFilterTop", title: "jobsTopTitle" },
|
||||
{ key: "APPLIED", i18n: "jobsFilterApplied", title: "jobsAppliedTitle" },
|
||||
{ key: "REMOTE", i18n: "jobsFilterRemote", title: "jobsRemoteTitle" },
|
||||
{ key: "PRESENCIAL", i18n: "jobsFilterPresencial", title: "jobsPresencialTitle" },
|
||||
|
|
@ -32,17 +36,9 @@ const FILTERS = [
|
|||
{ key: "EXCHANGE", i18n: "jobsFilterExchange", title: "jobsExchangeTitle" },
|
||||
{ key: "OPEN", i18n: "jobsFilterOpen", title: "jobsOpenTitle" },
|
||||
{ key: "CLOSED", i18n: "jobsFilterClosed", title: "jobsClosedTitle" },
|
||||
{ key: "RECENT", i18n: "jobsFilterRecent", title: "jobsRecentTitle" },
|
||||
{ key: "TOP", i18n: "jobsFilterTop", title: "jobsTopTitle" },
|
||||
{ key: "CV", i18n: "jobsCV", title: "jobsCVTitle" }
|
||||
]
|
||||
|
||||
function resolvePhoto(photoField, size = 256) {
|
||||
if (typeof photoField === "string" && photoField.startsWith("/image/")) return photoField
|
||||
if (typeof photoField === "string" && /^&[A-Za-z0-9+/=]+\.sha256$/.test(photoField)) return `/image/${size}/${encodeURIComponent(photoField)}`
|
||||
return "/assets/images/default-avatar.png"
|
||||
}
|
||||
|
||||
const safeArr = (v) => (Array.isArray(v) ? v : [])
|
||||
const safeText = (v) => String(v || "").trim()
|
||||
|
||||
|
|
@ -109,7 +105,7 @@ const renderUpdatedLabel = (createdAt, updatedAt) => {
|
|||
const updatedTs = updatedAt ? new Date(updatedAt).getTime() : NaN
|
||||
const showUpdated = Number.isFinite(updatedTs) && (!Number.isFinite(createdTs) || updatedTs !== createdTs)
|
||||
return showUpdated
|
||||
? span({ class: "votations-comment-date" }, ` | ${i18n.jobsUpdatedAt}: ${moment(updatedAt).format("YYYY/MM/DD HH:mm:ss")}`)
|
||||
? span({ class: "votations-comment-date" }, ` | ${i18n.jobsUpdatedAt}: ${moment(updatedAt).format("YYYY/MM/DD HH:mm")}`)
|
||||
: null
|
||||
}
|
||||
|
||||
|
|
@ -245,7 +241,7 @@ const renderJobList = exports.renderJobList = (jobs, filter, params = {}) => {
|
|||
div(
|
||||
{ class: "card-header activity-card-header" },
|
||||
span(),
|
||||
renderContentActions(job.id, `/jobs/${encodeURIComponent(job.id)}`)
|
||||
renderContentActions(job.id, `/jobs/${encodeURIComponent(job.id)}`, { spread: params.spreadMap && params.spreadMap.get(job.id) || null, author: job.author, favKind: 'jobs', isFavorite: job.isFavorite, reportTitle: job.title })
|
||||
),
|
||||
div({ class: "card-section job-card-body" },
|
||||
heroNode,
|
||||
|
|
@ -259,8 +255,7 @@ const renderJobList = exports.renderJobList = (jobs, filter, params = {}) => {
|
|||
div({ class: "card-date-highlight" }, compensationText),
|
||||
div({ class: "tribe-card-members" },
|
||||
span({ class: "tribe-members-count" }, `${i18n.jobSubscribers}: ${subs.length}`)
|
||||
),
|
||||
div({ class: "card-spread-centered" }, renderSpreadButton(job.id, params.spreadMap && params.spreadMap.get(job.id)))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -268,10 +263,11 @@ const renderJobList = exports.renderJobList = (jobs, filter, params = {}) => {
|
|||
)
|
||||
}
|
||||
|
||||
const renderJobForm = (job = {}, mode = "create") => {
|
||||
const renderJobForm = (job = {}, mode = "create", spreadWarning = null) => {
|
||||
const isEdit = mode === "edit"
|
||||
return div(
|
||||
{ class: "div-center job-form" },
|
||||
isEdit ? spreadWarning : null,
|
||||
form(
|
||||
{
|
||||
action: isEdit ? `/jobs/update/${encodeURIComponent(job.id)}` : "/jobs/create",
|
||||
|
|
@ -292,7 +288,7 @@ const renderJobForm = (job = {}, mode = "create") => {
|
|||
br(),
|
||||
label(i18n.jobTitle),
|
||||
br(),
|
||||
input({ type: "text", name: "title", required: true, placeholder: i18n.jobTitlePlaceholder, value: job.title || "" }),
|
||||
input({ type: "text", name: "title", maxlength: "100", required: true, placeholder: i18n.jobTitlePlaceholder, value: job.title || "" }),
|
||||
br(),
|
||||
br(),
|
||||
label(i18n.jobImage),
|
||||
|
|
@ -325,8 +321,8 @@ const renderJobForm = (job = {}, mode = "create") => {
|
|||
br(),
|
||||
select(
|
||||
{ name: "job_time", required: true },
|
||||
option({ value: "partial", selected: job.job_time === "partial" }, i18n.jobTimePartial),
|
||||
option({ value: "complete", selected: job.job_time === "complete" }, i18n.jobTimeComplete)
|
||||
option({ value: "partial", ...(job.job_time === "partial" ? { selected: true } : {})}, i18n.jobTimePartial),
|
||||
option({ value: "complete", ...(job.job_time === "complete" ? { selected: true } : {})}, i18n.jobTimeComplete)
|
||||
),
|
||||
br(),
|
||||
br(),
|
||||
|
|
@ -339,8 +335,8 @@ const renderJobForm = (job = {}, mode = "create") => {
|
|||
br(),
|
||||
select(
|
||||
{ name: "location", required: true },
|
||||
option({ value: "remote", selected: job.location === "remote" }, i18n.jobLocationRemote),
|
||||
option({ value: "presencial", selected: job.location === "presencial" }, i18n.jobLocationPresencial)
|
||||
option({ value: "remote", ...(job.location === "remote" ? { selected: true } : {})}, i18n.jobLocationRemote),
|
||||
option({ value: "presencial", ...(job.location === "presencial" ? { selected: true } : {})}, i18n.jobLocationPresencial)
|
||||
),
|
||||
br(),
|
||||
br(),
|
||||
|
|
@ -353,8 +349,8 @@ const renderJobForm = (job = {}, mode = "create") => {
|
|||
br(),
|
||||
select(
|
||||
{ name: "visibility" },
|
||||
option({ value: "PUBLIC", selected: (job.visibility || "PUBLIC") === "PUBLIC" }, i18n.visibilityPublic || "Public"),
|
||||
option({ value: "HIDDEN", selected: job.visibility === "HIDDEN" }, i18n.visibilityHidden || "Hidden")
|
||||
option({ value: "PUBLIC", ...((job.visibility || "PUBLIC") === "PUBLIC" ? { selected: true } : {})}, i18n.visibilityPublic || "Public"),
|
||||
option({ value: "HIDDEN", ...(job.visibility === "HIDDEN" ? { selected: true } : {})}, i18n.visibilityHidden || "Hidden")
|
||||
),
|
||||
br(),
|
||||
br(),
|
||||
|
|
@ -419,9 +415,23 @@ const renderCVList = (inhabitants) =>
|
|||
{ class: "inhabitant-details" },
|
||||
user.description ? p(...renderUrl(user.description)) : null,
|
||||
p(userLink(user.id)),
|
||||
user.skills && user.skills.length
|
||||
? div({ class: "card-tags" },
|
||||
user.skills.slice(0, 12).map((skill) =>
|
||||
a({ href: `/search?query=%23${encodeURIComponent(skill)}`, class: "tag-link" }, `#${skill}`))
|
||||
)
|
||||
: null,
|
||||
typeof user.matchScore === "number"
|
||||
? div({ class: "cv-match-row" },
|
||||
span({ class: "card-label" }, `${i18n.matchScore}: `),
|
||||
span({ class: "card-value" }, `${Math.round(user.matchScore * 100)}%`)
|
||||
)
|
||||
: null,
|
||||
div(
|
||||
{ class: "cv-actions" },
|
||||
form({ method: "GET", action: `/inhabitant/${encodeURIComponent(user.id)}` }, button({ type: "submit", class: "filter-btn" }, i18n.inhabitantviewDetails)),
|
||||
form({ method: "GET", action: `/inhabitant/${encodeURIComponent(user.id)}` }, button({ type: "submit", class: "filter-btn" }, i18n.cvVisitButton)),
|
||||
form({ method: "GET", action: `/cv/pdf/${encodeURIComponent(user.id)}` }, button({ type: "submit", class: "filter-btn" }, i18n.generatePdf)),
|
||||
form({ method: "POST", action: `/cv/share/${encodeURIComponent(user.id)}` }, button({ type: "submit", class: "filter-btn" }, i18n.sharePm)),
|
||||
!isMe ? renderPmButton(user.id) : null
|
||||
)
|
||||
)
|
||||
|
|
@ -431,6 +441,10 @@ const renderCVList = (inhabitants) =>
|
|||
)
|
||||
|
||||
exports.jobsView = async (jobsOrCVs, filter = "ALL", params = {}) => {
|
||||
if (filter === "EDIT") {
|
||||
const editing = Array.isArray(jobsOrCVs) ? jobsOrCVs[0] : jobsOrCVs
|
||||
params = { ...params, spreadWarning: await renderSpreadEditWarning(editing && editing.id) }
|
||||
}
|
||||
const search = safeText(params.search || "")
|
||||
const minSalary = params.minSalary ?? ""
|
||||
const maxSalary = params.maxSalary ?? ""
|
||||
|
|
@ -459,7 +473,7 @@ exports.jobsView = async (jobsOrCVs, filter = "ALL", params = {}) => {
|
|||
input({ type: "hidden", name: "maxSalary", value: String(maxSalary ?? "") }),
|
||||
input({ type: "hidden", name: "sort", value: sort }),
|
||||
...FILTERS.map((f) =>
|
||||
button({ type: "submit", name: "filter", value: f.key, class: filter === f.key ? "filter-btn active" : "filter-btn" }, i18n[f.i18n])
|
||||
button({ type: "submit", name: "filter", value: f.key, class: filter === f.key ? "filter-btn active" : "filter-btn" }, String(i18n[f.i18n]).toUpperCase())
|
||||
),
|
||||
button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.jobsCreateJob)
|
||||
)
|
||||
|
|
@ -492,7 +506,7 @@ exports.jobsView = async (jobsOrCVs, filter = "ALL", params = {}) => {
|
|||
...(params.tasks ? { tasks: params.tasks } : {}),
|
||||
...(params.salary ? { salary: params.salary } : {})
|
||||
}
|
||||
return renderJobForm(jobToEdit, filter === "EDIT" ? "edit" : "create")
|
||||
return renderJobForm(jobToEdit, filter === "EDIT" ? "edit" : "create", params.spreadWarning)
|
||||
})()
|
||||
: section(
|
||||
div(
|
||||
|
|
@ -510,9 +524,9 @@ exports.jobsView = async (jobsOrCVs, filter = "ALL", params = {}) => {
|
|||
),
|
||||
select(
|
||||
{ name: "sort", class: "filter-box__select" },
|
||||
option({ value: "recent", selected: sort === "recent" }, i18n.jobsSortRecent),
|
||||
option({ value: "salary", selected: sort === "salary" }, i18n.jobsSortSalary),
|
||||
option({ value: "subscribers", selected: sort === "subscribers" }, i18n.jobsSortSubscribers)
|
||||
option({ value: "recent", ...(sort === "recent" ? { selected: true } : {})}, i18n.jobsSortRecent),
|
||||
option({ value: "salary", ...(sort === "salary" ? { selected: true } : {})}, i18n.jobsSortSalary),
|
||||
option({ value: "subscribers", ...(sort === "subscribers" ? { selected: true } : {})}, i18n.jobsSortSubscribers)
|
||||
),
|
||||
button({ type: "submit", class: "filter-box__button" }, i18n.jobsSearchButton)
|
||||
)
|
||||
|
|
@ -526,61 +540,12 @@ exports.jobsView = async (jobsOrCVs, filter = "ALL", params = {}) => {
|
|||
}
|
||||
|
||||
const renderJobCommentsSection = (jobId, returnTo, comments = []) => {
|
||||
const list = safeArr(comments).filter(c => {
|
||||
const t = c && c.value && c.value.content && c.value.content.text
|
||||
return t && String(t).trim()
|
||||
})
|
||||
const commentsCount = list.length
|
||||
|
||||
return div(
|
||||
{ id: "comments", class: "vote-comments-section" },
|
||||
div(
|
||||
{ class: "comments-count" },
|
||||
span({ class: "card-label" }, i18n.voteCommentsLabel + ": "),
|
||||
span({ class: "card-value" }, String(commentsCount))
|
||||
),
|
||||
div(
|
||||
{ class: "comment-form-wrapper" },
|
||||
h2({ class: "comment-form-title" }, i18n.voteNewCommentLabel),
|
||||
form(
|
||||
{ method: "POST", action: `/jobs/${encodeURIComponent(jobId)}/comments`, class: "comment-form", enctype: "multipart/form-data" },
|
||||
input({ type: "hidden", name: "returnTo", value: returnTo }),
|
||||
textarea({ id: "comment-text", name: "text", rows: 4, class: "comment-textarea", placeholder: i18n.voteNewCommentPlaceholder }),
|
||||
div({ class: "comment-file-upload" }, label(i18n.uploadMedia), input({ type: "file", name: "blob" })),
|
||||
br(),
|
||||
button({ type: "submit", class: "comment-submit-btn" }, i18n.voteNewCommentButton)
|
||||
)
|
||||
),
|
||||
list.length
|
||||
? div(
|
||||
{ class: "comments-list" },
|
||||
list.map((c) => {
|
||||
const author = c?.value?.author || ""
|
||||
const ts = c?.value?.timestamp || c?.timestamp
|
||||
const absDate = ts ? moment(ts).format("YYYY/MM/DD HH:mm:ss") : ""
|
||||
const relDate = ts ? moment(ts).fromNow() : ""
|
||||
const userName = author && author.includes("@") ? author.split("@")[1] : author
|
||||
const rootId = c?.value?.content ? (c.value.content.fork || c.value.content.root) : null
|
||||
const text = c?.value?.content?.text || ""
|
||||
|
||||
return div(
|
||||
{ class: "votations-comment-card" },
|
||||
span(
|
||||
{ class: "created-at" },
|
||||
span(i18n.createdBy),
|
||||
author ? userLink(author) : span("(unknown)"),
|
||||
absDate ? span(" | ") : "",
|
||||
absDate ? span({ class: "votations-comment-date" }, absDate) : "",
|
||||
relDate ? span({ class: "votations-comment-date" }, " | ", i18n.sendTime) : "",
|
||||
relDate && rootId ? a({ href: `/thread/${encodeURIComponent(rootId)}#${encodeURIComponent(c.key)}` }, relDate) : ""
|
||||
),
|
||||
p({ class: "votations-comment-text" }, ...renderUrl(text))
|
||||
)
|
||||
})
|
||||
)
|
||||
: p({ class: "votations-no-comments" }, i18n.voteNoCommentsYet)
|
||||
)
|
||||
}
|
||||
return renderSharedCommentsSection({
|
||||
action: `/jobs/${encodeURIComponent(jobId)}/comments`,
|
||||
comments: comments,
|
||||
returnTo: returnTo
|
||||
});
|
||||
};
|
||||
|
||||
const renderCandidates = (candidates, jobId) => {
|
||||
if (!Array.isArray(candidates) || candidates.length === 0) return null;
|
||||
|
|
@ -643,7 +608,6 @@ exports.singleJobsView = async (job, filter = "ALL", comments = [], params = {})
|
|||
|
||||
const ownerActions = renderJobOwnerActions(job, returnTo)
|
||||
const applyToggle = renderJobApplyToggle(job, returnTo)
|
||||
const pmBtn = renderPmButton(job.author)
|
||||
const visibility = (job.visibility || 'PUBLIC').toUpperCase() === 'HIDDEN' ? 'HIDDEN' : 'PUBLIC'
|
||||
const chips = [
|
||||
renderJobStatusChip(job.status),
|
||||
|
|
@ -671,15 +635,16 @@ exports.singleJobsView = async (job, filter = "ALL", comments = [], params = {})
|
|||
|
||||
const sideActions = []
|
||||
if (applyToggle) sideActions.push(applyToggle)
|
||||
if (pmBtn) sideActions.push(pmBtn)
|
||||
for (const a of ownerActions) sideActions.push(a)
|
||||
|
||||
const jobSide = div({ class: "tribe-side" },
|
||||
div({ class: "card-header activity-card-header" },
|
||||
renderContentActions(job.id, null, { spread: params.spreads || null, author: job.author, favKind: 'jobs', isFavorite: job.isFavorite, reportTitle: job.title })
|
||||
),
|
||||
div({ class: "shop-title-row" },
|
||||
h2({ class: "tribe-card-title" }, safeText(job.title) || i18n.jobsTitle)
|
||||
),
|
||||
chips.length ? div({ class: "card-chips-row" }, ...chips) : null,
|
||||
div({ class: "card-spread-centered" }, renderSpreadButton(job.id, params.spreads)),
|
||||
job.image
|
||||
? renderMediaBlob(job.image, { class: "tribe-detail-image" })
|
||||
: null,
|
||||
|
|
@ -720,6 +685,7 @@ exports.singleJobsView = async (job, filter = "ALL", comments = [], params = {})
|
|||
|
||||
return template(
|
||||
i18n.jobsTitle,
|
||||
section(div({ class: "tags-header" }, h2(i18n.jobsTitle), p(i18n.jobsDescription))),
|
||||
section(
|
||||
div(
|
||||
{ class: "filters" },
|
||||
|
|
@ -730,7 +696,7 @@ exports.singleJobsView = async (job, filter = "ALL", comments = [], params = {})
|
|||
input({ type: "hidden", name: "maxSalary", value: String(params.maxSalary ?? "") }),
|
||||
input({ type: "hidden", name: "sort", value: safeText(params.sort || "recent") }),
|
||||
...FILTERS.map((f) =>
|
||||
button({ type: "submit", name: "filter", value: f.key, class: filter === f.key ? "filter-btn active" : "filter-btn" }, i18n[f.i18n])
|
||||
button({ type: "submit", name: "filter", value: f.key, class: filter === f.key ? "filter-btn active" : "filter-btn" }, String(i18n[f.i18n]).toUpperCase())
|
||||
),
|
||||
button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.jobsCreateJob)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue