Skip to content

Commit 0c84596

Browse files
authored
feat(condo): DOMA-12916 add rich text area in news (#7286)
* feat(condo): DOMA-12914 add markdown editor in news create flow * feat(condo): DOMA-12916 fix after bot review * feat(condo): DOMA-12916 enhance news item display and notifications with markdown support * feat(condo): DOMA-12916 add stripMarkdown utility for processing markdown text * feat(condo): DOMA-12916 add markdown support in telegram news * feat(condo): DOMA-12916 migrate news template placeholders from square brackets to angle brackets * feat(condo): DOMA-12916 fix pointers * feat(condo): DOMA-12916 consolidate stripMarkdown utility and update dependencies * feat(condo): DOMA-12916 update stripMarkdown utility to use CommonJS module syntax * feat(condo): DOMA-12916 update subproject commits for news-greenhouse, news-telegram-api, news-telegram-web, and resident-app
1 parent 318d288 commit 0c84596

21 files changed

Lines changed: 348 additions & 149 deletions
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* Migrates old news template placeholders from square brackets to angle brackets.
3+
*
4+
* Example:
5+
* [address] -> <address>
6+
*
7+
* Usage:
8+
* yarn workspace @app/condo node bin/migrate-news-templates-placeholders
9+
*/
10+
11+
const path = require('path')
12+
13+
const { GraphQLApp } = require('@open-keystone/app-graphql')
14+
15+
16+
const PROCESS_CHUNK_SIZE = 200
17+
const SCRIPT_SENDER = { dv: 1, sender: { dv: 1, fingerprint: 'migrate-news-templates-placeholders' } }
18+
19+
function log (message, payload = '') {
20+
console.log(message, payload)
21+
}
22+
23+
function isLinkBracket (text, matchIndex, matchLength) {
24+
return text[matchIndex + matchLength] === '('
25+
}
26+
27+
function replaceSquarePlaceholders (text) {
28+
if (!text || text.indexOf('[') === -1) return { updatedText: text, replacementsCount: 0 }
29+
30+
let replacementsCount = 0
31+
const updatedText = text.replace(/\[([^[\]]+?)\]/g, (match, placeholder, offset, source) => {
32+
// Keep markdown links untouched: [label](https://...)
33+
if (isLinkBracket(source, offset, match.length)) return match
34+
replacementsCount += 1
35+
return `<${placeholder}>`
36+
})
37+
38+
return { updatedText, replacementsCount }
39+
}
40+
41+
async function getTemplatesBatch (context, cursorId) {
42+
const query = context.adapter.knex('NewsItemTemplate')
43+
.select('id', 'title', 'body')
44+
.whereNull('deletedAt')
45+
.orderBy('id', 'asc')
46+
.limit(PROCESS_CHUNK_SIZE)
47+
48+
if (cursorId) query.andWhere('id', '>', cursorId)
49+
50+
return query
51+
}
52+
53+
async function updateTemplate (context, id, title, body) {
54+
await context.adapter.knex('NewsItemTemplate')
55+
.where({ id })
56+
.update({
57+
title,
58+
body,
59+
...SCRIPT_SENDER,
60+
updatedAt: context.adapter.knex.fn.now(),
61+
})
62+
}
63+
64+
async function main () {
65+
const resolved = path.resolve('./index.js')
66+
const { distDir, keystone, apps } = require(resolved)
67+
const graphqlIndex = apps.findIndex(app => app instanceof GraphQLApp)
68+
69+
await keystone.prepare({ apps: [apps[graphqlIndex]], distDir, dev: true })
70+
await keystone.connect()
71+
72+
const stats = {
73+
scanned: 0,
74+
updated: 0,
75+
titleReplacements: 0,
76+
bodyReplacements: 0,
77+
}
78+
79+
let cursorId = null
80+
let hasMore = true
81+
82+
while (hasMore) {
83+
const templates = await getTemplatesBatch(keystone, cursorId)
84+
hasMore = templates.length === PROCESS_CHUNK_SIZE
85+
86+
for (const template of templates) {
87+
stats.scanned += 1
88+
cursorId = template.id
89+
90+
const { updatedText: title, replacementsCount: titleReplacements } = replaceSquarePlaceholders(template.title)
91+
const { updatedText: body, replacementsCount: bodyReplacements } = replaceSquarePlaceholders(template.body)
92+
93+
if (titleReplacements === 0 && bodyReplacements === 0) continue
94+
95+
await updateTemplate(keystone, template.id, title, body)
96+
stats.updated += 1
97+
stats.titleReplacements += titleReplacements
98+
stats.bodyReplacements += bodyReplacements
99+
}
100+
101+
log('Processed batch', { cursorId, scanned: stats.scanned, updated: stats.updated })
102+
}
103+
104+
log('Migration completed', stats)
105+
process.exit(0)
106+
}
107+
108+
main().catch((error) => {
109+
console.error(error)
110+
process.exit(1)
111+
})

apps/condo/domains/common/constants/featureflags.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const UI_AI_REWRITE_TEXT = 'ui-ai-rewrite-text'
4040
const UI_AI_REWRITE_NEWS_TEXT = 'ui-ai-news-rewrite-text'
4141
const UI_AI_REWRITE_INCIDENT_TEXT_FOR_RESIDENT = 'ui-ai-rewrite-incident-text-for-resident'
4242
const UI_AI_CHAT_WITH_CONDO = 'ui-ai-chat-with-condo'
43+
const UI_NEWS_MARKDOWN = 'ui-news-markdown'
4344
const SUBSCRIPTIONS = 'subscriptions'
4445
const ACTIVE_BANKING_SUBSCRIPTION_PLAN_ID = 'active-banking-subscription-plan-id'
4546
const TICKET_OBSERVERS = 'ticket-observers'
@@ -91,6 +92,7 @@ module.exports = {
9192
UI_AI_REWRITE_NEWS_TEXT,
9293
UI_AI_REWRITE_INCIDENT_TEXT_FOR_RESIDENT,
9394
UI_AI_CHAT_WITH_CONDO,
95+
UI_NEWS_MARKDOWN,
9496
ACTIVE_BANKING_SUBSCRIPTION_PLAN_ID,
9597
TICKET_OBSERVERS,
9698
DEFAULT_TRIAL_SUBSCRIPTION_PLAN_ID,

apps/condo/domains/common/utils/stripMarkdown.ts renamed to apps/condo/domains/common/utils/stripMarkdown.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
export const stripMarkdown = (input?: string | null): string => {
1+
/**
2+
* Removes basic markdown syntax and returns plain text.
3+
*
4+
* @param {string | null | undefined} input
5+
* @returns {string}
6+
*/
7+
const stripMarkdown = (input) => {
28
if (!input) {
39
return ''
410
}
@@ -14,3 +20,5 @@ export const stripMarkdown = (input?: string | null): string => {
1420
.replaceAll(/\s{2,1000}/g, ' ')
1521
.trim()
1622
}
23+
24+
module.exports = { stripMarkdown }

apps/condo/domains/common/utils/stripMarkdown.spec.ts renamed to apps/condo/domains/common/utils/stripMarkdown.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { stripMarkdown } from './stripMarkdown'
1+
const { stripMarkdown } = require('./stripMarkdown')
22

33
describe('stripMarkdown', () => {
44
it('returns empty string for empty input', () => {

apps/condo/domains/news/components/NewsForm/BaseNewsForm.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ const FULL_WIDTH_STYLE: React.CSSProperties = { width: '100%' }
175175
const SMALL_VERTICAL_GUTTER: [Gutter, Gutter] = [0, 24]
176176
const EXTRA_SMALL_VERTICAL_GUTTER: [Gutter, Gutter] = [0, 10]
177177
const BIG_HORIZONTAL_GUTTER: [Gutter, Gutter] = [50, 0]
178-
const ALL_SQUARE_BRACKETS_OCCURRENCES_REGEX = /\[[^\]]*?\]/g
178+
const ANGLE_BRACKETS_REGEX = /<[^>]*?>/
179179
const ADDITIONAL_DISABLED_MINUTES_COUNT = 5
180180
const DATE_FORMAT = 'DD MMMM YYYY HH:mm'
181181

@@ -209,19 +209,19 @@ const getValidBeforeAfterSendAt = (form) => {
209209
return true
210210
}
211211

212-
const containWordsInSquareBrackets = (str) => {
213-
const words = str.match(ALL_SQUARE_BRACKETS_OCCURRENCES_REGEX) || []
214-
return words.length !== 0
212+
const containTemplatePlaceholders = (str?: string) => {
213+
if (!str) return false
214+
return ANGLE_BRACKETS_REGEX.test(str)
215215
}
216216

217217
const getTitleTemplateChanged = (form) => {
218218
const { title } = form.getFieldsValue(['title'])
219-
return !containWordsInSquareBrackets(title)
219+
return !containTemplatePlaceholders(title)
220220
}
221221

222222
const getBodyTemplateChanged = (form) => {
223223
const { body } = form.getFieldsValue(['body'])
224-
return !containWordsInSquareBrackets(body)
224+
return !containTemplatePlaceholders(body)
225225
}
226226

227227
const isDateDisabled = date => {

apps/condo/domains/news/components/NewsForm/InputStep/InputStepForm.module.css

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,3 @@
1717
display: none;
1818
}
1919
}
20-
21-
.custom-form-item-label {
22-
padding-bottom: 8px !important;
23-
}
24-
25-
.custom-form-item-label label {
26-
height: auto !important;
27-
white-space: nowrap !important;
28-
}

0 commit comments

Comments
 (0)