why here usage "this.": const Headlines = {
	vgroup_last_feed: undefined,
	_headlines_scroll_timeout: 0,
	//_observer_counters_timeout: 0,
	headlines: [],
	current_first_id: 0,
	_scroll_reset_timeout: false,
	default_force_previous: false,
	default_force_to_top: false,
	default_move_on_expand: true,
	line_scroll_offset: 120, /* px */
	sticky_header_observer: new IntersectionObserver(
		(entries, observer) => {
			entries.forEach((entry) => {
				const header = entry.target.closest('.cdm').querySelector(".header");

				if (entry.isIntersecting) {
					header.removeAttribute("data-is-stuck");
				} else {
					header.setAttribute("data-is-stuck", "true");
				}

				//console.log(entry.target, entry.intersectionRatio, entry.isIntersecting, entry.boundingClientRect.top);
			});
		},
		{threshold: [0, 1], root: document.querySelector("#headlines-frame")}
	),
	sticky_content_observer: new IntersectionObserver(
		(entries, observer) => {
			entries.forEach((entry) => {
				const header = entry.target.closest('.cdm').querySelector(".header");

				header.style.position = entry.isIntersecting ? "sticky" : "unset";

				//console.log(entry.target, entry.intersectionRatio, entry.isIntersecting, entry.boundingClientRect.top);
			});
		},
		{threshold: [0, 1], root: document.querySelector("#headlines-frame")}
	),
	unpack_observer: new IntersectionObserver(
		(entries, observer) => {
			entries.forEach((entry) => {
				if (entry.intersectionRatio > 0)
					Article.unpack(entry.target);
			});
		},
		{threshold: [0], root: document.querySelector("#headlines-frame")}
	),
	row_observer: new MutationObserver((mutations) => {
		const modified = [];

		mutations.forEach((m) => {
			if (m.type == 'attributes' && ['class', 'data-score'].indexOf(m.attributeName) != -1) {

				const row = m.target;
				const id = row.getAttribute("data-article-id");

				if (Headlines.headlines[id]) {
					const hl = Headlines.headlines[id];

					if (hl) {
						const hl_old = {...{}, ...hl};

						hl.unread = row.hasClassName("Unread");
						hl.marked = row.hasClassName("marked");
						hl.published = row.hasClassName("published");

						// not sent by backend
						hl.selected = row.hasClassName("Selected");
						hl.active = row.hasClassName("active");

						hl.score = row.getAttribute("data-score");

						modified.push({id: hl.id, new: hl, old: hl_old, row: row});
					}
				}
			}
		});

		PluginHost.run(PluginHost.HOOK_HEADLINE_MUTATIONS, mutations);

		Headlines.updateSelectedPrompt();

		window.requestIdleCallback(() => {
			Headlines.syncModified(modified);
		});
	}),
	syncModified: function (modified) {
		const ops = {
			tmark: [],
			tpub: [],
			read: [],
			unread: [],
			select: [],
			deselect: [],
			activate: [],
			deactivate: [],
			rescore: {},
		};

		modified.forEach(function (m) {
			if (m.old.marked != m.new.marked)
				ops.tmark.push(m.id);

			if (m.old.published != m.new.published)
				ops.tpub.push(m.id);

			if (m.old.unread != m.new.unread)
				m.new.unread ? ops.unread.push(m.id) : ops.read.push(m.id);

			if (m.old.selected != m.new.selected)
				m.new.selected ? ops.select.push(m.row) : ops.deselect.push(m.row);

			if (m.old.active != m.new.active)
				m.new.active ? ops.activate.push(m.row) : ops.deactivate.push(m.row);

			if (m.old.score != m.new.score) {
				const score = m.new.score;

				ops.rescore[score] = ops.rescore[score] || [];
				ops.rescore[score].push(m.id);
			}
		});

		ops.select.forEach((row) => {
			const cb = dijit.getEnclosingWidget(row.querySelector(".rchk"));

			if (cb)
				cb.attr('checked', true);
		});

		ops.deselect.forEach((row) => {
			const cb = dijit.getEnclosingWidget(row.querySelector(".rchk"));

			if (cb && !row.hasClassName("active"))
				cb.attr('checked', false);
		});

		ops.activate.forEach((row) => {
			const cb = dijit.getEnclosingWidget(row.querySelector(".rchk"));

			if (cb)
				cb.attr('checked', true);
		});

		ops.deactivate.forEach((row) => {
			const cb = dijit.getEnclosingWidget(row.querySelector(".rchk"));

			if (cb && !row.hasClassName("Selected"))
				cb.attr('checked', false);
		});

		const promises = [];

		if (ops.tmark.length != 0)
			promises.push(xhr.post("backend.php",
				{op: "RPC", method: "markSelected", "ids[]": ops.tmark, cmode: 2}));

		if (ops.tpub.length != 0)
			promises.push(xhr.post("backend.php",
				{op: "RPC", method: "publishSelected", "ids[]": ops.tpub, cmode: 2}));

		if (ops.read.length != 0)
			promises.push(xhr.post("backend.php",
				{op: "RPC", method: "catchupSelected", "ids[]": ops.read, cmode: 0}));

		if (ops.unread.length != 0)
			promises.push(xhr.post("backend.php",
				{op: "RPC", method: "catchupSelected", "ids[]": ops.unread, cmode: 1}));

		const scores = Object.keys(ops.rescore);

		if (scores.length != 0) {
			scores.forEach((score) => {
				promises.push(xhr.post("backend.php",
					{op: "Article", method: "setScore", "ids[]": ops.rescore[score], score: score}));
			});
		}

		Promise.allSettled(promises).then((results) => {
			let feeds = [];
			let labels = [];

			results.forEach((res) => {
				console.log('requesting counters for: res: ', res);
				if (res) {
					try {
						const obj = JSON.parse(res.value);

						if (obj.feeds)
							feeds = feeds.concat(obj.feeds);

						if (obj.labels)
							labels = labels.concat(obj.labels);

					} catch (e) {
						console.warn(e, res);
					}
				}
			});

			if (feeds.length > 0) {
				console.log('requesting counters for', feeds, labels);
				Feeds.requestCounters(feeds, labels);
			}

			PluginHost.run(PluginHost.HOOK_HEADLINE_MUTATIONS_SYNCED, results);
		});
	},
	click: function (event, id, in_body) {
		in_body = in_body || false;

		if (event.shiftKey && Article.getActive()) {
			Headlines.select('none');

			const ids = Headlines.getRange(Article.getActive(), id);

			console.log(Article.getActive(), id, ids);

			for (let i = 0; i < ids.length; i++)
				Headlines.select('all', ids[i]);

		} else if (event.ctrlKey) {
			Headlines.select('invert', id);
		} else {
			// eslint-disable-next-line no-lonely-if
			if (App.isCombinedMode()) {

				if (event.altKey && !in_body) {

					Article.openInNewWindow(id);
					Headlines.toggleUnread(id, 0);

				} else if (Article.getActive() != id) {

					Headlines.select('none');

					const scroll_position_A = App.byId(`RROW-${id}`).offsetTop - App.byId("headlines-frame").scrollTop;

					Article.setActive(id);

					if (App.getInitParam("cdm_expanded")) {

						if (!in_body)
							Article.openInNewWindow(id);

						Headlines.toggleUnread(id, 0);
					} else {
						const scroll_position_B = App.byId(`RROW-${id}`).offsetTop - App.byId("headlines-frame").scrollTop;

						// this would only work if there's enough space
						App.byId("headlines-frame").scrollTop -= scroll_position_A-scroll_position_B;

						if (this.default_move_on_expand)
							Article.cdmMoveToId(id);
					}

				} else if (in_body) {
					Headlines.toggleUnread(id, 0);
				} else { /* !in body */
					Article.openInNewWindow(id);
				}

				return in_body;
			} else {
				// eslint-disable-next-line no-lonely-if
				if (event.altKey) {
					Article.openInNewWindow(id);
					Headlines.toggleUnread(id, 0);
				} else {
					Headlines.select('none');
					Article.view(id);
				}
			}
		}

		return false;
	},