feat(kontakte): Zustaendigkeit, LinkedIn-Foto, Netzwerkauswahl
Drei Verbesserungen an den Kontakten: * Beim Anlegen ist der angemeldete Benutzer voreingestellt zustaendig. Bisher stand dort der erste Benutzer der Liste - fast immer falsch. * Wird eine LinkedIn-Adresse hinterlegt, holt das CRM das oeffentliche Profilfoto und setzt es als Kontaktfoto: beim Verlassen des Feldes einmal je Adresse, solange kein Foto gesetzt ist, und auf Klick ueber den Knopf daneben. Der Abruf laeuft ueber das Backend (server/src/linkedinPhoto.js, POST /api/leads/linkedin-photo), weil ihn die Same-Origin-Policy im Browser verhindert. Gelesen wird die Open-Graph-Vorschau der Profilseite; Herkunft, Groesse und Antwortzeit des Bildes werden geprueft, Platzhalter-Silhouetten abgewiesen. Schlaegt der Abruf fehl, steht der Grund unter dem Feld. * In der Netzwerkansicht hebt ein Klick auf freie Flaeche die Auswahl auf - das Diagramm sieht danach aus wie beim Wechsel in die Ansicht. Ein Ziehen des Hintergrunds (ab 4 px) laesst die Auswahl stehen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3c9d83161e
commit
0a2cf52e8a
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Pruefungen des LinkedIn-Profilfotos: laeuft mit `npm test` im Verzeichnis
|
||||
* server/. Geprueft wird alles, was ohne Netz geht - die Adresspruefung und das
|
||||
* Herausloesen der Bildadresse aus dem Seitenquelltext. Der Abruf selbst haengt
|
||||
* an LinkedIn und taugt nicht als Test: er kann jederzeit abgewiesen werden.
|
||||
*/
|
||||
const { normalizeProfileUrl, extractImageUrl } = await import('../src/linkedinPhoto.js');
|
||||
|
||||
let failures = 0;
|
||||
|
||||
function check(name, condition, detail = '') {
|
||||
if (condition) {
|
||||
console.log(` ok ${name}${detail ? ` - ${detail}` : ''}`);
|
||||
} else {
|
||||
console.log(` FAIL ${name}${detail ? ` - ${detail}` : ''}`);
|
||||
failures += 1;
|
||||
}
|
||||
}
|
||||
|
||||
function throws(fn) {
|
||||
try {
|
||||
fn();
|
||||
return null;
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\nAdresse pruefen');
|
||||
check(
|
||||
'Vollstaendige Adresse bleibt',
|
||||
normalizeProfileUrl('https://www.linkedin.com/in/max') === 'https://www.linkedin.com/in/max'
|
||||
);
|
||||
check(
|
||||
'Ohne Schema wird https ergaenzt',
|
||||
normalizeProfileUrl('linkedin.com/in/max') === 'https://linkedin.com/in/max'
|
||||
);
|
||||
check(
|
||||
'Landeskennung ist erlaubt',
|
||||
normalizeProfileUrl('https://de.linkedin.com/in/max') === 'https://de.linkedin.com/in/max'
|
||||
);
|
||||
check(
|
||||
'Nachverfolgung faellt weg',
|
||||
normalizeProfileUrl('https://www.linkedin.com/in/max?trk=abc#top') ===
|
||||
'https://www.linkedin.com/in/max'
|
||||
);
|
||||
check(
|
||||
'http wird zu https',
|
||||
normalizeProfileUrl('http://www.linkedin.com/in/max') === 'https://www.linkedin.com/in/max'
|
||||
);
|
||||
|
||||
const fremd = throws(() => normalizeProfileUrl('https://linkedin.com.example.org/in/max'));
|
||||
check('Aehnlich aussehender Host wird abgewiesen', fremd?.status === 400, fremd?.message);
|
||||
check('Fremder Host wird abgewiesen', throws(() => normalizeProfileUrl('https://xing.com/x'))?.status === 400);
|
||||
check('Leere Eingabe wird abgewiesen', throws(() => normalizeProfileUrl(''))?.status === 400);
|
||||
check('Unsinn wird abgewiesen', throws(() => normalizeProfileUrl('kein ://link'))?.status === 400);
|
||||
|
||||
console.log('\nBildadresse aus dem Quelltext');
|
||||
check(
|
||||
'og:image wird gefunden',
|
||||
extractImageUrl('<meta property="og:image" content="https://media.licdn.com/dms/image/v2/abc" />') ===
|
||||
'https://media.licdn.com/dms/image/v2/abc'
|
||||
);
|
||||
check(
|
||||
'Maskierte Und-Zeichen werden zurueckgesetzt',
|
||||
extractImageUrl('<meta property="og:image" content="https://media.licdn.com/dms/image/a?x=1&y=2">') ===
|
||||
'https://media.licdn.com/dms/image/a?x=1&y=2'
|
||||
);
|
||||
check(
|
||||
'twitter:image als Ersatz',
|
||||
extractImageUrl('<meta name="twitter:image" content="https://media.licdn.com/dms/image/t">') ===
|
||||
'https://media.licdn.com/dms/image/t'
|
||||
);
|
||||
check(
|
||||
'Ohne Vorschau greift die Suche im Quelltext',
|
||||
extractImageUrl('var x = "https://media.licdn.com/dms/image/v2/zzz/profile-displayphoto";') ===
|
||||
'https://media.licdn.com/dms/image/v2/zzz/profile-displayphoto'
|
||||
);
|
||||
check('Seite ohne Bild liefert null', extractImageUrl('<html><body>nichts</body></html>') === null);
|
||||
|
||||
console.log(failures === 0 ? '\nAlle Pruefungen bestanden.' : `\n${failures} Pruefung(en) fehlgeschlagen.`);
|
||||
process.exit(failures === 0 ? 0 : 1);
|
||||
Reference in New Issue
Block a user