Skip to content

Commit fb1f540

Browse files
authored
[XWI-142] Enable wiki macros in more editors (#24480)
* Centralise CKEditor wiki macro * Move the `wikisAvailable` check into the central CKEditor macro resolver and add a `wiki` macro preset, so any editor can opt in with a one-liner instead of repeating the logic. * Add `wiki` macro to the CKeditor in many places The places are: - comments (activity) - custom fields (long text) - custom fields help text - meetings: - description - outcomes - agenda item
1 parent 147f82d commit fb1f540

11 files changed

Lines changed: 88 additions & 26 deletions

File tree

app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def initialize(form:, attribute:, model:, show_action_buttons: true, **system_ar
4545

4646
@system_arguments[:rich_text_options] ||= {}
4747
@system_arguments[:rich_text_options][:primerized] = true
48+
# Text custom fields offer the wiki-page-link macros (AC: "custom field of type text").
49+
@system_arguments[:rich_text_options][:macros] ||= "wiki" if custom_field?
4850

4951
@system_arguments[:data] = merge_data(
5052
@system_arguments,

app/forms/custom_fields/inputs/text.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class CustomFields::Inputs::Text < CustomFields::Inputs::Base::Input
3636
def rich_text_options
3737
{
3838
resource: nil,
39-
macros: "none",
39+
macros: "wiki",
4040
data: {
4141
"custom-field-id": @custom_field.id,
4242
"test-selector": test_selector

app/forms/work_packages/activities_tab/journals/notes_form.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ class NotesForm < ApplicationForm
4141
showAttachments: false,
4242
resource:,
4343
storageKey: "work_package-#{object.journable.id}-notes-#{object.id || 'new'}",
44-
editor_type: "constrained"
44+
editor_type: "constrained",
45+
macros: "wiki"
4546
}
4647
)
4748
end

frontend/src/app/features/hal/resources/work-package-resource.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ import { ICKEditorContext } from 'core-app/shared/components/editor/components/c
5252
import isNewResource from 'core-app/features/hal/helpers/is-new-resource';
5353
import { IWorkPackageTimestamp } from 'core-app/features/hal/resources/work-package-timestamp-resource';
5454
import { formatWorkPackageId } from 'core-app/shared/helpers/work-package-id-pattern';
55-
import { ConfigurationService } from 'core-app/core/config/configuration.service';
5655

5756
export interface WorkPackageResourceEmbedded {
5857
activities:CollectionResource;
@@ -192,8 +191,6 @@ export class WorkPackageBaseResource extends HalResource {
192191

193192
@LazyInject() pathHelper:PathHelperService;
194193

195-
@LazyInject() configService:ConfigurationService;
196-
197194
readonly attachmentsBackend = true;
198195

199196
/**
@@ -244,13 +241,15 @@ export class WorkPackageBaseResource extends HalResource {
244241
}
245242

246243
public getEditorContext(fieldName:string):ICKEditorContext {
247-
const wikiPageMacros = ['OpMacroWikiPageLinkAddExisting', 'OpMacroWikiPageLinkCreateNew'];
248-
const macros:boolean|string[] = this.configService.wikisAvailable ? wikiPageMacros : false;
244+
if (fieldName === 'description') {
245+
return { type: 'full', macros: 'wiki' };
246+
}
249247

248+
const isCustomField = fieldName.startsWith('customField');
250249
return {
251-
type: fieldName === 'description' ? 'full' : 'constrained',
252-
macros,
253-
...(fieldName.startsWith('customField') && { disabledMentions: ['user'] }),
250+
type: 'constrained',
251+
macros: isCustomField ? 'wiki' : false,
252+
...(isCustomField && { disabledMentions: ['user'] }),
254253
};
255254
}
256255

frontend/src/app/shared/components/editor/components/ckeditor/ckeditor-setup.service.ts

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,21 @@ import { Constructor } from '@angular/cdk/schematics';
3838
import { ConfigurationService } from 'core-app/core/config/configuration.service';
3939

4040
export type ICKEditorType = 'full'|'constrained';
41-
export type ICKEditorMacroType = 'none'|'resource'|'full'|boolean|string[];
41+
42+
// What editor authors pass; resolveMacros() turns it into an ICKEditorResolvedMacros value.
43+
// The wiki-link macros are appended to any array result when a wiki provider is available.
44+
// 'none' / false → no macros (dropdown hidden)
45+
// 'resource' → ToC, embedded table, WP button/quickinfo (+ wiki links when available)
46+
// 'wiki' → only the wiki links, and only when a provider is available (else none)
47+
// true → keep every macro the build ships, regardless of wiki availability
48+
// string[] → exactly these macro plugin names (+ wiki links when available)
49+
export type ICKEditorMacroType = 'none'|'resource'|'wiki'|boolean|string[];
50+
51+
// What the CKEditor build consumes: false = none, true = all, array = exactly these.
52+
export type ICKEditorResolvedMacros = boolean|string[];
53+
54+
// Wiki-page-link macros, added to any editor when a wiki provider is configured (see resolveMacros).
55+
const wikiLinkMacros = ['OpMacroWikiPageLinkAddExisting', 'OpMacroWikiPageLinkCreateNew'];
4256

4357
declare global {
4458
interface Window {
@@ -132,9 +146,10 @@ export class CKEditorSetupService {
132146
private createConfig(context:ICKEditorContext, initialData:string|null) {
133147
const uiLocale = this.loadedLocale;
134148
const contentLanguage = context.options?.rtl ? 'ar' : 'en';
149+
const resolvedContext:ICKEditorContext = { ...context, macros: this.resolveMacros(context.macros) };
135150

136151
const config = {
137-
openProject: this.createContext(context),
152+
openProject: this.createContext(resolvedContext),
138153
removePlugins: context.removePlugins,
139154
initialData,
140155
ui: {
@@ -148,6 +163,10 @@ export class CKEditorSetupService {
148163
},
149164
link: {},
150165
storageKey: context.storageKey,
166+
// Constrained editors have no macro dropdown by default; add one when macros are present.
167+
...(context.type === 'constrained' && Array.isArray(resolvedContext.macros)
168+
? { toolbar: { items: this.constrainedToolbarWithMacroList() } }
169+
: {}),
151170
};
152171

153172
const allowedLinkProtocols = this.configurationService.allowedLinkProtocols;
@@ -209,22 +228,60 @@ export class CKEditorSetupService {
209228
}
210229

211230
private createContext(context:ICKEditorContext):unknown {
212-
if (context.macros === 'none') {
213-
context.macros = false;
214-
} else if (context.macros === 'resource') {
215-
context.macros = [
231+
return {
232+
context,
233+
helpURL: this.PathHelper.textFormattingHelp(),
234+
pluginContext: window.OpenProject.pluginContext.value,
235+
};
236+
}
237+
238+
// Splice `macroList` into the constrained editor's own toolbar (which omits it by default).
239+
private constrainedToolbarWithMacroList():string[] {
240+
const items = [...(window.OPConstrainedEditor.defaultConfig?.toolbar?.items ?? [])];
241+
242+
if (!items.includes('macroList')) {
243+
const anchor = items.indexOf('blockQuote');
244+
const insertAt = anchor === -1 ? items.length : anchor + 1;
245+
items.splice(insertAt, 0, '|', 'macroList');
246+
}
247+
248+
return items;
249+
}
250+
251+
// Expand macro tokens and add/withhold the wiki macros based on `wikisAvailable`.
252+
// `false`/`'none'` stay macro-free, keeping custom fields and read-only editors untouched.
253+
private resolveMacros(macros:ICKEditorMacroType|undefined):ICKEditorResolvedMacros {
254+
let resolved = macros;
255+
256+
if (!resolved || resolved === 'none') {
257+
return false;
258+
}
259+
if (resolved === true) {
260+
return true;
261+
}
262+
if (resolved === 'resource') {
263+
resolved = [
216264
'OPMacroToc',
217265
'OPMacroEmbeddedTable',
218266
'OPMacroWpButton',
219267
'OPMacroWpQuickinfo',
220268
];
269+
} else if (resolved === 'wiki') {
270+
resolved = [];
221271
}
222272

223-
return {
224-
context,
225-
helpURL: this.PathHelper.textFormattingHelp(),
226-
pluginContext: window.OpenProject.pluginContext.value,
227-
};
273+
if (Array.isArray(resolved)) {
274+
resolved = this.configurationService.wikisAvailable
275+
? [...new Set([...resolved, ...wikiLinkMacros])]
276+
: resolved.filter((name) => !wikiLinkMacros.includes(name));
277+
278+
// Empty set ⇒ false, so no (empty) dropdown is shown.
279+
if (resolved.length === 0) {
280+
return false;
281+
}
282+
}
283+
284+
return resolved;
228285
}
229286

230287
private watchTopLayer() {

frontend/src/app/shared/components/editor/components/ckeditor/ckeditor.types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ export interface ICKEditorStatic {
8989
create(el:HTMLElement, config?:any):Promise<ICKEditorInstance>;
9090

9191
createCustomized(el:string|HTMLElement, config?:any):Promise<ICKEditorInstance>;
92+
93+
defaultConfig?:{ toolbar?:{ items:string[] } };
9294
}
9395

9496
export type ICKEditorState = 'initializing'|'ready'|'crashed'|'crashedPermanently'|'destroyed';

lib/custom_field_form_builder.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def custom_field_input(options = {})
7373
when "date"
7474
date_picker(field, input_options)
7575
when "text"
76-
text_area(field, input_options.merge(with_text_formatting: true, macros: false, editor_type: "constrained"))
76+
text_area(field, input_options.merge(with_text_formatting: true, macros: "wiki", editor_type: "constrained"))
7777
when "bool"
7878
check_box(field, input_options.merge(checked: custom_value.strategy.checked?))
7979
when "list"

modules/grids/app/components/grids/widgets/description.html.erb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ GNU General Public License for more details.
2222
2323
You should have received a copy of the GNU General Public License
2424
along with this program; if not, write to the Free Software
25-
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2626
2727
See COPYRIGHT and LICENSE files for more details.
2828
@@ -37,7 +37,7 @@ See COPYRIGHT and LICENSE files for more details.
3737
rich_text_options: {
3838
showAttachments: false,
3939
editorType: "constrained",
40-
macros: false
40+
macros: "wiki"
4141
}
4242
)
4343
end

modules/grids/app/components/grids/widgets/project_status.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ See COPYRIGHT and LICENSE files for more details.
4242
rich_text_options: {
4343
showAttachments: false,
4444
editorType: "constrained",
45-
macros: false
45+
macros: "wiki"
4646
}
4747
)
4848
end

modules/meeting/app/forms/meeting_agenda_item/outcome/notes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class MeetingAgendaItem::Outcome::Notes < ApplicationForm
3939
rich_text_options: {
4040
resource:,
4141
editor_type: "constrained",
42+
macros: "wiki",
4243
storageKey: "meeting-agenda-#{object.meeting_agenda_item&.id || 'new'}-outcome-#{object.id || 'new'}",
4344
showAttachments: false
4445
}

0 commit comments

Comments
 (0)